536 lines
14 KiB
Plaintext
536 lines
14 KiB
Plaintext
---
|
|
// src/components/Header.astro
|
|
import ThemeToggler from './ThemeToggler.astro';
|
|
|
|
// Define navigation items with proper URLs
|
|
const navItems = [
|
|
{ name: 'Home', url: '/' },
|
|
{ name: 'Blog', url: '/blog' },
|
|
{ name: 'Projects', url: '/projects' },
|
|
{ name: 'Tech Stack', url: '/tech-stack' },
|
|
{ name: 'Home Lab', url: '/homelab' }, // Updated URL
|
|
{ name: 'Resources', url: '/resources' },
|
|
{ name: 'About', url: 'https://ArgoBox.com' },
|
|
{ name: 'Contact', url: 'https://ArgoBox.com/index.html#contact' }
|
|
];
|
|
|
|
// Get current URL path for active nav item highlighting
|
|
const currentPath = Astro.url.pathname;
|
|
---
|
|
|
|
<header class="site-header">
|
|
<div class="container header-container">
|
|
<div class="logo-container">
|
|
<a href="/" class="logo-link">
|
|
<div class="logo">LF</div>
|
|
<div class="site-name">
|
|
<span class="site-title">ArgoBox</span>
|
|
<span class="site-subtitle">Infrastructure & Automation</span>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
|
|
<nav class="main-nav">
|
|
<ul class="nav-list">
|
|
{navItems.map(item => (
|
|
<li class="nav-item">
|
|
<a
|
|
href={item.url}
|
|
class={`nav-link ${currentPath === item.url ||
|
|
(currentPath.startsWith(item.url) && item.url !== '/') ? 'active' : ''}`}
|
|
target={item.url.startsWith('http') ? '_blank' : undefined}
|
|
rel={item.url.startsWith('http') ? 'noopener noreferrer' : undefined}
|
|
>
|
|
{item.name}
|
|
</a>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</nav>
|
|
|
|
<div class="header-actions">
|
|
<div class="search-container">
|
|
<button class="search-toggle" aria-label="Toggle search">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<circle cx="11" cy="11" r="8"></circle>
|
|
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
|
</svg>
|
|
</button>
|
|
<div class="search-dropdown">
|
|
<div class="search-input-wrapper">
|
|
<input
|
|
type="search"
|
|
id="header-search"
|
|
placeholder="Search blog posts..."
|
|
class="search-input"
|
|
/>
|
|
<button class="search-submit">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<polyline points="9 18 15 12 9 6"></polyline>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<div class="search-results" id="search-results">
|
|
<!-- Results will be populated by JS -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<ThemeToggler />
|
|
</div>
|
|
|
|
<button class="mobile-menu-toggle" aria-label="Toggle menu">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<line x1="3" y1="12" x2="21" y2="12"></line>
|
|
<line x1="3" y1="6" x2="21" y2="6"></line>
|
|
<line x1="3" y1="18" x2="21" y2="18"></line>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</header>
|
|
|
|
<style>
|
|
.site-header {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 100;
|
|
padding: 0.5rem 0;
|
|
background: rgba(var(--bg-primary-rgb), 0.8);
|
|
backdrop-filter: blur(10px);
|
|
border-bottom: 1px solid var(--border-primary);
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.header-container {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
max-width: 1280px;
|
|
margin: 0 auto;
|
|
padding: 0 var(--container-padding);
|
|
}
|
|
|
|
.logo-container {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.logo-link {
|
|
display: flex;
|
|
align-items: center;
|
|
text-decoration: none;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.logo {
|
|
width: 40px;
|
|
height: 40px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
|
|
color: var(--bg-primary);
|
|
font-weight: bold;
|
|
border-radius: 8px;
|
|
margin-right: 0.75rem;
|
|
font-family: var(--font-sans);
|
|
}
|
|
|
|
.site-name {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.site-title {
|
|
font-weight: 600;
|
|
font-size: 1.25rem;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.site-subtitle {
|
|
font-size: 0.75rem;
|
|
color: var(--text-tertiary);
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.main-nav {
|
|
display: flex;
|
|
margin-left: auto;
|
|
}
|
|
|
|
.nav-list {
|
|
display: flex;
|
|
list-style: none;
|
|
padding: 0;
|
|
margin: 0;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.nav-link {
|
|
color: var(--text-secondary);
|
|
text-decoration: none;
|
|
font-size: 0.9rem;
|
|
padding: 0.5rem 0.75rem;
|
|
border-radius: 6px;
|
|
transition: all 0.2s ease;
|
|
position: relative;
|
|
}
|
|
|
|
.nav-link:hover {
|
|
color: var(--text-primary);
|
|
background: rgba(255, 255, 255, 0.05);
|
|
}
|
|
|
|
.nav-link.active {
|
|
color: var(--accent-primary);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.nav-link.active::after {
|
|
content: '';
|
|
position: absolute;
|
|
bottom: -2px;
|
|
left: 0.75rem;
|
|
right: 0.75rem;
|
|
height: 2px;
|
|
background: var(--accent-primary);
|
|
border-radius: 1px;
|
|
}
|
|
|
|
.header-actions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
margin-left: 1rem;
|
|
}
|
|
|
|
/* Search Dropdown Styles */
|
|
.search-container {
|
|
position: relative;
|
|
}
|
|
|
|
.search-toggle {
|
|
background: none;
|
|
border: none;
|
|
padding: 0.5rem;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: var(--text-secondary);
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.search-toggle:hover {
|
|
color: var(--text-primary);
|
|
background: rgba(255, 255, 255, 0.05);
|
|
}
|
|
|
|
.search-dropdown {
|
|
position: absolute;
|
|
top: calc(100% + 0.5rem);
|
|
right: 0;
|
|
width: 300px;
|
|
background: var(--bg-secondary);
|
|
border: 1px solid var(--border-primary);
|
|
border-radius: 8px;
|
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
|
|
padding: 0.75rem;
|
|
z-index: 10;
|
|
transform-origin: top right;
|
|
transform: scale(0.95);
|
|
opacity: 0;
|
|
visibility: hidden;
|
|
transition: all 0.2s cubic-bezier(0.5, 0, 0, 1.25);
|
|
}
|
|
|
|
.search-dropdown.active {
|
|
transform: scale(1);
|
|
opacity: 1;
|
|
visibility: visible;
|
|
}
|
|
|
|
.search-input-wrapper {
|
|
display: flex;
|
|
align-items: center;
|
|
position: relative;
|
|
}
|
|
|
|
.search-input {
|
|
width: 100%;
|
|
padding: 0.6rem 2.5rem 0.6rem 0.75rem;
|
|
border: 1px solid var(--border-primary);
|
|
border-radius: 6px;
|
|
background: var(--bg-primary);
|
|
color: var(--text-primary);
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.search-input:focus {
|
|
outline: none;
|
|
border-color: var(--accent-primary);
|
|
box-shadow: 0 0 0 3px var(--glow-primary);
|
|
}
|
|
|
|
.search-submit {
|
|
position: absolute;
|
|
right: 0.5rem;
|
|
background: none;
|
|
border: none;
|
|
color: var(--text-secondary);
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 0.25rem;
|
|
}
|
|
|
|
.search-submit:hover {
|
|
color: var(--accent-primary);
|
|
}
|
|
|
|
.search-results {
|
|
max-height: 300px;
|
|
overflow-y: auto;
|
|
margin-top: 0.75rem;
|
|
}
|
|
|
|
.search-result-item {
|
|
padding: 0.5rem 0.75rem;
|
|
border-bottom: 1px solid var(--border-primary);
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.search-result-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.search-result-item:hover {
|
|
background: rgba(var(--bg-tertiary-rgb), 0.5);
|
|
}
|
|
|
|
.search-result-title {
|
|
font-size: 0.9rem;
|
|
font-weight: 500;
|
|
color: var(--text-primary);
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.search-result-snippet {
|
|
font-size: 0.8rem;
|
|
color: var(--text-secondary);
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 2;
|
|
-webkit-box-orient: vertical;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.no-results {
|
|
padding: 1rem;
|
|
text-align: center;
|
|
color: var(--text-secondary);
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
/* Mobile Menu Toggle */
|
|
.mobile-menu-toggle {
|
|
display: none;
|
|
background: none;
|
|
border: none;
|
|
color: var(--text-secondary);
|
|
cursor: pointer;
|
|
padding: 0.5rem;
|
|
}
|
|
|
|
/* Responsive Adjustments */
|
|
@media (max-width: 1024px) {
|
|
.nav-link {
|
|
padding: 0.5rem;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.main-nav {
|
|
display: none;
|
|
position: absolute;
|
|
top: 100%;
|
|
left: 0;
|
|
right: 0;
|
|
background: var(--bg-secondary);
|
|
padding: 1rem;
|
|
border-bottom: 1px solid var(--border-primary);
|
|
}
|
|
|
|
.main-nav.active {
|
|
display: block;
|
|
}
|
|
|
|
.nav-list {
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.nav-link {
|
|
display: block;
|
|
padding: 0.75rem 1rem;
|
|
}
|
|
|
|
.nav-link.active::after {
|
|
display: none;
|
|
}
|
|
|
|
.mobile-menu-toggle {
|
|
display: block;
|
|
}
|
|
|
|
.search-dropdown {
|
|
width: 260px;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
// Mobile menu toggle
|
|
const mobileMenuToggle = document.querySelector('.mobile-menu-toggle');
|
|
const mainNav = document.querySelector('.main-nav');
|
|
|
|
mobileMenuToggle?.addEventListener('click', () => {
|
|
mainNav?.classList.toggle('active');
|
|
});
|
|
|
|
// Search dropdown toggle
|
|
const searchToggle = document.querySelector('.search-toggle');
|
|
const searchDropdown = document.querySelector('.search-dropdown');
|
|
const searchInput = document.querySelector('#header-search');
|
|
|
|
searchToggle?.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
searchDropdown?.classList.toggle('active');
|
|
|
|
if (searchDropdown?.classList.contains('active')) {
|
|
// Focus the search input when dropdown is shown
|
|
setTimeout(() => {
|
|
searchInput?.focus();
|
|
}, 100);
|
|
}
|
|
});
|
|
|
|
// Close search dropdown when clicking outside
|
|
document.addEventListener('click', (e) => {
|
|
if (!e.target.closest('.search-container')) {
|
|
searchDropdown?.classList.remove('active');
|
|
}
|
|
});
|
|
|
|
// Search functionality - client-side site-wide filtering (User provided version)
|
|
const searchResults = document.getElementById('search-results'); // Assuming this ID exists in your dropdown HTML
|
|
|
|
// Function to perform search
|
|
const performSearch = async (query) => {
|
|
if (!query || query.length < 2) {
|
|
// Clear results if query is too short
|
|
if (searchResults) {
|
|
searchResults.innerHTML = '';
|
|
}
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Fetch the search index that contains all site content
|
|
const response = await fetch('/search-index.json'); // Ensure this path is correct based on your build output
|
|
if (!response.ok) throw new Error('Failed to fetch search data');
|
|
|
|
const allContent = await response.json();
|
|
const results = allContent.filter(item => {
|
|
const lowerQuery = query.toLowerCase();
|
|
return (
|
|
item.title.toLowerCase().includes(lowerQuery) ||
|
|
item.description?.toLowerCase().includes(lowerQuery) ||
|
|
item.tags?.some(tag => tag.toLowerCase().includes(lowerQuery)) ||
|
|
item.category?.toLowerCase().includes(lowerQuery)
|
|
);
|
|
}).slice(0, 8); // Limit to 8 results for better UI
|
|
|
|
// Display results
|
|
if (searchResults) {
|
|
if (results.length > 0) {
|
|
searchResults.innerHTML = results.map(item => {
|
|
// Create type badge
|
|
let typeBadge = '';
|
|
switch(item.type) {
|
|
case 'post':
|
|
typeBadge = '<span class="result-type post">Blog</span>';
|
|
break;
|
|
case 'project':
|
|
typeBadge = '<span class="result-type project">Project</span>';
|
|
break;
|
|
case 'configuration':
|
|
typeBadge = '<span class="result-type config">Config</span>';
|
|
break;
|
|
case 'external':
|
|
typeBadge = '<span class="result-type external">External</span>';
|
|
break;
|
|
default:
|
|
typeBadge = '<span class="result-type">Content</span>';
|
|
}
|
|
|
|
return `
|
|
<div class="search-result-item" data-url="${item.url}">
|
|
<div class="search-result-header">
|
|
<div class="search-result-title">${item.title}</div>
|
|
${typeBadge}
|
|
</div>
|
|
<div class="search-result-snippet">${item.description || ''}</div>
|
|
${item.tags && item.tags.length > 0 ?
|
|
`<div class="search-result-tags">
|
|
${item.tags.slice(0, 3).map(tag => `<span class="search-tag">${tag}</span>`).join('')}
|
|
</div>` : ''
|
|
}
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
// Add click handlers to results
|
|
document.querySelectorAll('.search-result-item').forEach(item => {
|
|
item.addEventListener('click', () => {
|
|
window.location.href = item.dataset.url; // Navigate to the item's URL
|
|
});
|
|
});
|
|
} else {
|
|
searchResults.innerHTML = '<div class="no-results">No matching content found</div>';
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Search error:', error);
|
|
if (searchResults) {
|
|
searchResults.innerHTML = '<div class="no-results">Error performing search</div>';
|
|
}
|
|
}
|
|
};
|
|
|
|
// Search input event handler with debounce
|
|
let searchTimeout;
|
|
searchInput?.addEventListener('input', (e) => {
|
|
clearTimeout(searchTimeout);
|
|
searchTimeout = setTimeout(() => {
|
|
performSearch(e.target.value);
|
|
}, 300); // Debounce to avoid too many searches while typing
|
|
});
|
|
|
|
// Handle search form submission (if your input is inside a form)
|
|
const searchForm = searchInput?.closest('form');
|
|
searchForm?.addEventListener('submit', (e) => {
|
|
e.preventDefault(); // Prevent default form submission
|
|
performSearch(searchInput.value);
|
|
});
|
|
|
|
// Handle search-submit button click (if you have a separate submit button)
|
|
const searchSubmit = document.querySelector('.search-submit'); // Adjust selector if needed
|
|
searchSubmit?.addEventListener('click', () => {
|
|
performSearch(searchInput?.value || '');
|
|
});
|
|
|
|
}); // End of DOMContentLoaded
|
|
</script> |