Move the serach box to teh sidebar

This commit is contained in:
Urtzi Alfaro
2025-09-24 19:40:51 +02:00
parent be1fec17c4
commit e978d04800
3 changed files with 174 additions and 104 deletions

View File

@@ -33,7 +33,8 @@ import {
Menu,
LogOut,
MoreHorizontal,
X
X,
Search
} from 'lucide-react';
export interface SidebarProps {
@@ -143,6 +144,9 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set());
const [hoveredItem, setHoveredItem] = useState<string | null>(null);
const [isProfileMenuOpen, setIsProfileMenuOpen] = useState(false);
const [isSearchFocused, setIsSearchFocused] = useState(false);
const [searchValue, setSearchValue] = useState('');
const searchInputRef = React.useRef<HTMLInputElement>(null);
const sidebarRef = React.useRef<HTMLDivElement>(null);
// Get subscription-aware navigation routes
@@ -260,6 +264,29 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
}
}, [navigate, onClose]);
// Handle search
const handleSearchChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setSearchValue(e.target.value);
}, []);
const handleSearchSubmit = useCallback((e: React.FormEvent) => {
e.preventDefault();
if (searchValue.trim()) {
// TODO: Implement search functionality
console.log('Search:', searchValue);
}
}, [searchValue]);
const clearSearch = useCallback(() => {
setSearchValue('');
searchInputRef.current?.focus();
}, []);
// Focus search input
const focusSearch = useCallback(() => {
searchInputRef.current?.focus();
}, []);
// Handle logout
const handleLogout = useCallback(async () => {
await logout();
@@ -389,6 +416,42 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
}
}, [isProfileMenuOpen]);
// Keyboard shortcuts for search and other functionality
React.useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
// Cmd/Ctrl + K for search
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
e.preventDefault();
focusSearch();
}
// Escape to close menus
if (e.key === 'Escape') {
setIsProfileMenuOpen(false);
if (onClose) onClose(); // Close mobile sidebar
if (isSearchFocused) {
searchInputRef.current?.blur();
}
}
};
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, [focusSearch, isSearchFocused, setIsProfileMenuOpen, onClose]);
// Close search and menus when clicking outside
React.useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
const target = event.target as Element;
if (!target.closest('[data-profile-menu]') && !target.closest('[data-search]')) {
setIsProfileMenuOpen(false);
}
};
document.addEventListener('click', handleClickOutside);
return () => document.removeEventListener('click', handleClickOutside);
}, []);
// Render submenu overlay for collapsed sidebar
const renderSubmenuOverlay = (item: NavigationItem) => {
if (!item.children || item.children.length === 0) return null;
@@ -597,6 +660,55 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
)}
aria-label={t('common:accessibility.menu', 'Main navigation')}
>
{/* Search */}
{!isCollapsed && (
<div className="px-4 pt-4" data-search>
<form
onSubmit={handleSearchSubmit}
className="relative"
>
<div className="absolute left-3 top-0 bottom-0 flex items-center pointer-events-none">
<Search className="h-4 w-4 text-[var(--text-tertiary)]" />
</div>
<input
ref={searchInputRef}
type="text"
value={searchValue}
onChange={handleSearchChange}
onFocus={() => setIsSearchFocused(true)}
onBlur={() => setIsSearchFocused(false)}
placeholder={t('common:forms.search_placeholder', 'Search...')}
className={clsx(
'w-full pl-10 pr-12 py-2.5 text-sm',
'bg-[var(--bg-secondary)] border border-[var(--border-primary)]',
'rounded-lg transition-colors duration-200',
'focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]/20',
'focus:border-[var(--color-primary)]',
'placeholder:text-[var(--text-tertiary)]',
'h-9'
)}
aria-label={t('common:accessibility.search', 'Search in the application')}
/>
{searchValue ? (
<button
type="button"
onClick={clearSearch}
className="absolute right-3 top-0 bottom-0 flex items-center p-1 hover:bg-[var(--bg-tertiary)] rounded-full transition-colors"
aria-label={t('common:actions.clear', 'Clear search')}
>
<X className="h-3 w-3 text-[var(--text-tertiary)]" />
</button>
) : (
<div className="absolute right-3 top-0 bottom-0 flex items-center pointer-events-none">
<kbd className="hidden lg:inline-flex items-center justify-center h-5 px-1.5 text-xs text-[var(--text-tertiary)] font-mono bg-[var(--bg-tertiary)] rounded border border-[var(--border-primary)]">
K
</kbd>
</div>
)}
</form>
</div>
)}
{/* Navigation */}
<nav className={clsx('flex-1 overflow-y-auto', isCollapsed ? 'px-2 py-4' : 'p-4')}>
<ul className={clsx(isCollapsed ? 'space-y-2' : 'space-y-2')}>
@@ -750,6 +862,53 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
</Button>
</div>
{/* Mobile search - always visible in mobile view */}
<div className="p-4 border-b border-[var(--border-primary)]">
<form
onSubmit={handleSearchSubmit}
className="relative"
>
<div className="absolute left-3 top-0 bottom-0 flex items-center pointer-events-none">
<Search className="h-4 w-4 text-[var(--text-tertiary)]" />
</div>
<input
ref={searchInputRef}
type="text"
value={searchValue}
onChange={handleSearchChange}
onFocus={() => setIsSearchFocused(true)}
onBlur={() => setIsSearchFocused(false)}
placeholder={t('common:forms.search_placeholder', 'Search...')}
className={clsx(
'w-full pl-10 pr-12 py-2.5 text-sm',
'bg-[var(--bg-secondary)] border border-[var(--border-primary)]',
'rounded-lg transition-colors duration-200',
'focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]/20',
'focus:border-[var(--color-primary)]',
'placeholder:text-[var(--text-tertiary)]',
'h-9'
)}
aria-label={t('common:accessibility.search', 'Search in the application')}
/>
{searchValue ? (
<button
type="button"
onClick={clearSearch}
className="absolute right-3 top-0 bottom-0 flex items-center p-1 hover:bg-[var(--bg-tertiary)] rounded-full transition-colors"
aria-label={t('common:actions.clear', 'Clear search')}
>
<X className="h-3 w-3 text-[var(--text-tertiary)]" />
</button>
) : (
<div className="absolute right-3 top-0 bottom-0 flex items-center pointer-events-none">
<kbd className="hidden lg:inline-flex items-center justify-center h-5 px-1.5 text-xs text-[var(--text-tertiary)] font-mono bg-[var(--bg-tertiary)] rounded border border-[var(--border-primary)]">
K
</kbd>
</div>
)}
</form>
</div>
{/* Navigation */}
<nav className="flex-1 p-4 overflow-y-auto scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-transparent">
<ul className="space-y-2 pb-4">