feat: implement premium blog search and filter UI with improved layout and dynamic post loading.

This commit is contained in:
Ümit Tunç
2026-05-25 06:01:35 +03:00
parent a037917f16
commit afa92d8f4a
3 changed files with 232 additions and 36 deletions
+209 -29
View File
@@ -7,7 +7,7 @@
<!-- Carousel Section -->
@if(isset($carouselPosts) && $carouselPosts->count() > 0)
<div class="swiper-container blog grid-view !mb-24 relative z-10" data-margin="30" data-dots="true" data-items-lg="2" data-items-md="1" data-items-xs="1">
<div class="swiper-container blog grid-view !mb-10 relative z-10" data-margin="30" data-dots="true" data-items-lg="2" data-items-md="1" data-items-xs="1">
<div class="swiper">
<div class="swiper-wrapper">
@foreach($carouselPosts as $cPost)
@@ -21,9 +21,9 @@
$cUrl = route('blog.show', $cPost->slug);
@endphp
<div class="swiper-slide">
<figure class="overlay caption caption-overlay rounded !mb-0">
<a href="{{ $cUrl }}">
<img src="{{ $cImage }}" alt="{{ $cTitle }}" style="height: 380px; object-fit: cover; width: 100%;" loading="lazy">
<figure class="overlay caption caption-overlay rounded !mb-0" style="height: 380px !important; overflow: hidden;">
<a href="{{ $cUrl }}" class="block w-full h-full">
<img src="{{ $cImage }}" alt="{{ $cTitle }}" style="height: 380px !important; object-fit: cover !important; width: 100% !important;" loading="lazy">
</a>
<figcaption class="group-hover:opacity-100 absolute w-full h-full opacity-0 text-center px-4 py-3 inset-0 z-[5] pointer-events-none p-2">
@if($cCategoryName)
@@ -58,7 +58,7 @@
<!-- Welcome Banner Section -->
<div class="flex flex-wrap mx-[-15px]">
<div class="lg:w-full xl:w-10/12 xxl:w-8/12 w-full flex-[0_0_auto] !px-[15px] max-w-full !mx-auto !text-center">
<h2 class="xl:!text-[1.7rem] !text-[calc(1.295rem_+_0.54vw)] !leading-[1.25] font-semibold !text-center !mt-4 !mb-10">
<h2 class="xl:!text-[1.7rem] !text-[calc(1.295rem_+_0.54vw)] !leading-[1.25] font-semibold !text-center !mt-0 !mb-4">
Merhaba! Biz Trunçgil. Blogumuza hoş geldiniz. Burada web tasarım, yazılım geliştirme ve teknolojik yeniliklere dair ipuçlarını ve deneyimlerimizi paylaşıyoruz.
</h2>
</div>
@@ -71,7 +71,7 @@
// Dynamically select column width based on category count
$gridClass = $catCount >= 4 ? 'md:w-6/12 lg:w-3/12 xl:w-3/12' : 'md:w-6/12 lg:w-4/12 xl:w-4/12';
@endphp
<div class="flex flex-wrap mx-[-15px] grid-view md:mx-[-20px] lg:mx-[-20px] xl:mx-[-25px] !mt-[-40px] xl:!mt-0 lg:!mt-0 !text-center !mb-16">
<div class="flex flex-wrap mx-[-15px] grid-view md:mx-[-20px] lg:mx-[-20px] xl:mx-[-25px] !mt-[-40px] xl:!mt-0 lg:!mt-0 !text-center !mb-8">
@foreach($highlightCategories as $index => $hCat)
@php
$images = [
@@ -104,26 +104,40 @@
</div>
@endif
<!-- Main Content & Sidebar Layout -->
<div class="flex flex-wrap mx-[-15px] xl:mx-[-35px] lg:mx-[-20px] !mt-[4rem]">
<!-- Left Column: Posts Feed (8/12) -->
<div class="xl:w-8/12 lg:w-8/12 w-full flex-[0_0_auto] !px-[15px] max-w-full lg:!px-[20px] xl:!px-[35px] xl:!mt-0 lg:!mt-0">
<!-- Category Filter -->
<!-- Category Filter & Search Bar Wrapper -->
<div class="flex flex-wrap justify-between items-center gap-4 !mb-8 blog-filter-search-wrapper">
@if(isset($categories) && $categories->count() > 0)
<div class="flex flex-wrap justify-start !mb-10 gap-2 blog-filter-menu">
<a href="#" class="category-filter-btn active !text-[0.75rem] !font-bold !uppercase !tracking-[0.02rem] !px-4 !py-2 !rounded-full !border !border-[#e31e24] !bg-[#e31e24] !text-white hover:!bg-[#c8191f] hover:!border-[#c8191f] transition-colors" data-category="">
<div class="blog-filter-menu !mb-0">
<a href="#" class="category-filter-btn active" data-category="">
Tümü
</a>
@foreach($categories as $category)
<a href="#" class="category-filter-btn !text-[0.75rem] !font-bold !uppercase !tracking-[0.02rem] !px-4 !py-2 !rounded-full !border !border-[#eef2f6] !bg-[#eef2f6] !text-[#343f52] hover:!bg-[#e31e24] hover:!text-white hover:!border-[#e31e24] transition-colors" data-category="{{ $category->slug }}">
<a href="#" class="category-filter-btn" data-category="{{ $category->slug }}">
{{ $category->name }}
</a>
@endforeach
</div>
@endif
<!-- Premium Search Box -->
<div class="blog-search-box">
<form action="#" class="blog-search-form" id="blog-search-form">
<input type="text" name="search" id="blog-search-input" placeholder="Yazılarda ara..." autocomplete="off">
<button type="submit" class="blog-search-submit-btn">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-search" viewBox="0 0 16 16" style="width: 14px; height: 14px; display: block; color: white;">
<path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001q.044.06.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1 1 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0"/>
</svg>
</button>
</form>
</div>
</div>
<!-- Main Content & Sidebar Layout -->
<div class="flex flex-wrap mx-[-15px] xl:mx-[-35px] lg:mx-[-20px] !mt-[1.5rem]">
<!-- Left Column: Posts Feed (8/12) -->
<div class="xl:w-8/12 lg:w-8/12 w-full flex-[0_0_auto] !px-[15px] max-w-full lg:!px-[20px] xl:!px-[35px] xl:!mt-0 lg:!mt-0">
<div id="blog-posts-container" class="relative min-h-[400px]">
@include('blog.partials.posts')
</div>
@@ -219,6 +233,131 @@
@push('styles')
<style>
/* Premium Blog Filter Menu */
.blog-filter-menu {
display: inline-flex !important;
flex-wrap: wrap !important;
gap: 8px !important;
background: #ffffff !important;
padding: 8px 12px !important;
border-radius: 50px !important;
border: 1px solid #eef2f6 !important;
box-shadow: 0 4px 20px rgba(30, 34, 40, 0.05) !important;
margin-bottom: 0 !important;
align-items: center !important;
}
.category-filter-btn {
font-size: 0.75rem !important;
font-weight: 700 !important;
padding: 8px 20px !important;
border-radius: 50px !important;
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1) !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
border: 1px solid transparent !important;
color: #60697b !important;
background: transparent !important;
text-transform: uppercase !important;
letter-spacing: 0.03em !important;
text-decoration: none !important;
cursor: pointer !important;
}
.category-filter-btn:hover {
color: #e31e24 !important;
background: rgba(227, 30, 36, 0.05) !important;
text-decoration: none !important;
}
.category-filter-btn.active {
color: #ffffff !important;
background: #e31e24 !important;
border-color: #e31e24 !important;
box-shadow: 0 4px 12px rgba(227, 30, 36, 0.25) !important;
text-decoration: none !important;
}
/* Premium Blog Search Box */
.blog-search-box {
background: #ffffff !important;
border-radius: 50px !important;
border: 1px solid #eef2f6 !important;
box-shadow: 0 4px 20px rgba(30, 34, 40, 0.05) !important;
padding: 4px 6px 4px 18px !important;
display: inline-flex !important;
align-items: center !important;
width: 280px !important;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
}
.blog-search-box:focus-within {
border-color: #e31e24 !important;
box-shadow: 0 4px 20px rgba(227, 30, 36, 0.1) !important;
width: 320px !important;
}
.blog-search-form {
display: flex !important;
width: 100% !important;
align-items: center !important;
margin: 0 !important;
}
#blog-search-input {
border: none !important;
background: transparent !important;
outline: none !important;
font-size: 0.8rem !important;
font-weight: 500 !important;
color: #343f52 !important;
width: 100% !important;
padding: 6px 0 !important;
}
#blog-search-input::placeholder {
color: #aab0bc !important;
}
.blog-search-submit-btn {
border: none !important;
background: #e31e24 !important;
color: #ffffff !important;
width: 32px !important;
height: 32px !important;
border-radius: 50% !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
cursor: pointer !important;
transition: all 0.25s ease !important;
box-shadow: 0 4px 10px rgba(227, 30, 36, 0.2) !important;
flex-shrink: 0 !important;
padding: 0 !important;
margin: 0 !important;
}
.blog-search-submit-btn:hover {
background: #c8191f !important;
transform: scale(1.05) !important;
}
@media (max-width: 991px) {
.blog-filter-search-wrapper {
flex-direction: column !important;
align-items: stretch !important;
gap: 15px !important;
}
.blog-filter-menu {
justify-content: center !important;
width: 100% !important;
}
.blog-search-box {
width: 100% !important;
}
}
/* Loading Overlay for AJAX Requests */
.blog-loading-overlay {
position: absolute;
@@ -285,7 +424,10 @@ document.addEventListener('DOMContentLoaded', function() {
window.history.pushState({html: html}, '', url);
document.querySelector('.blog-filter-menu').scrollIntoView({ behavior: 'smooth', block: 'start' });
const scrollTarget = document.querySelector('.blog-filter-search-wrapper') || document.querySelector('.blog-filter-menu');
if (scrollTarget) {
scrollTarget.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
setTimeout(() => {
overlay.classList.remove('active');
@@ -314,11 +456,9 @@ document.addEventListener('DOMContentLoaded', function() {
e.preventDefault();
filterBtns.forEach(b => {
b.classList.remove('!text-white', '!bg-[#e31e24]', '!border-[#e31e24]');
b.classList.add('!text-[#343f52]', '!bg-[#eef2f6]', '!border-[#eef2f6]');
b.classList.remove('active');
});
this.classList.remove('!text-[#343f52]', '!bg-[#eef2f6]', '!border-[#eef2f6]');
this.classList.add('!text-white', '!bg-[#e31e24]', '!border-[#e31e24]');
this.classList.add('active');
const category = this.getAttribute('data-category');
const url = new URL(window.location.href);
@@ -333,6 +473,45 @@ document.addEventListener('DOMContentLoaded', function() {
});
});
// Bind Search Form
const searchForm = document.getElementById('blog-search-form');
const searchInput = document.getElementById('blog-search-input');
if (searchForm && searchInput) {
searchForm.addEventListener('submit', function(e) {
e.preventDefault();
const query = searchInput.value.trim();
const url = new URL(window.location.href);
if (query) {
url.searchParams.set('search', query);
} else {
url.searchParams.delete('search');
}
url.searchParams.delete('page');
fetchPosts(url.toString());
});
// Clear search automatically when input is empty
searchInput.addEventListener('input', function() {
if (this.value.trim() === '') {
const url = new URL(window.location.href);
if (url.searchParams.has('search')) {
url.searchParams.delete('search');
url.searchParams.delete('page');
fetchPosts(url.toString());
}
}
});
// Prepopulate search parameter from URL
const urlParams = new URLSearchParams(window.location.search);
const initialSearch = urlParams.get('search') || '';
if (initialSearch) {
searchInput.value = initialSearch;
}
}
window.addEventListener('popstate', function(e) {
if (e.state && e.state.html) {
container.innerHTML = e.state.html;
@@ -341,15 +520,18 @@ document.addEventListener('DOMContentLoaded', function() {
const urlParams = new URLSearchParams(window.location.search);
const currentCategory = urlParams.get('category') || '';
const currentSearch = urlParams.get('search') || '';
filterBtns.forEach(b => {
b.classList.remove('!text-white', '!bg-[#e31e24]', '!border-[#e31e24]');
b.classList.add('!text-[#343f52]', '!bg-[#eef2f6]', '!border-[#eef2f6]');
b.classList.remove('active');
if (b.getAttribute('data-category') === currentCategory) {
b.classList.remove('!text-[#343f52]', '!bg-[#eef2f6]', '!border-[#eef2f6]');
b.classList.add('!text-white', '!bg-[#e31e24]', '!border-[#e31e24]');
b.classList.add('active');
}
});
if (searchInput) {
searchInput.value = currentSearch;
}
} else {
window.location.reload();
}
@@ -363,11 +545,9 @@ document.addEventListener('DOMContentLoaded', function() {
const activeBtn = Array.from(filterBtns).find(btn => btn.getAttribute('data-category') === initialCategory);
if (activeBtn) {
filterBtns.forEach(b => {
b.classList.remove('!text-white', '!bg-[#e31e24]', '!border-[#e31e24]');
b.classList.add('!text-[#343f52]', '!bg-[#eef2f6]', '!border-[#eef2f6]');
b.classList.remove('active');
});
activeBtn.classList.remove('!text-[#343f52]', '!bg-[#eef2f6]', '!border-[#eef2f6]');
activeBtn.classList.add('!text-white', '!bg-[#e31e24]', '!border-[#e31e24]');
activeBtn.classList.add('active');
}
}
});