fix(sidebar): move search below Header, remove section chevrons
All checks were successful
Build & Publish / publish (push) Successful in 1m3s
All checks were successful
Build & Publish / publish (push) Successful in 1m3s
- Search input now renders between Sidebar.Header and first Section instead of above Header (fixes cameleer3-server#120) - Remove ChevronRight/ChevronDown from section headers — the entire row is already clickable, chevrons added visual noise Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,9 @@
|
|||||||
import { type ReactNode } from 'react'
|
import { type ReactNode, Children, isValidElement } from 'react'
|
||||||
import {
|
import {
|
||||||
Search,
|
Search,
|
||||||
X,
|
X,
|
||||||
ChevronsLeft,
|
ChevronsLeft,
|
||||||
ChevronsRight,
|
ChevronsRight,
|
||||||
ChevronRight,
|
|
||||||
ChevronDown,
|
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import styles from './Sidebar.module.css'
|
import styles from './Sidebar.module.css'
|
||||||
import { SidebarContext, useSidebarContext } from './SidebarContext'
|
import { SidebarContext, useSidebarContext } from './SidebarContext'
|
||||||
@@ -124,9 +122,6 @@ function SidebarSection({
|
|||||||
aria-label={open ? `Collapse ${label}` : `Expand ${label}`}
|
aria-label={open ? `Collapse ${label}` : `Expand ${label}`}
|
||||||
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onToggle() } }}
|
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onToggle() } }}
|
||||||
>
|
>
|
||||||
<span className={styles.treeSectionChevronBtn} aria-hidden="true">
|
|
||||||
{open ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
|
|
||||||
</span>
|
|
||||||
{icon && <span className={styles.sectionIcon}>{icon}</span>}
|
{icon && <span className={styles.sectionIcon}>{icon}</span>}
|
||||||
<span className={styles.treeSectionLabel}>{label}</span>
|
<span className={styles.treeSectionLabel}>{label}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -199,35 +194,50 @@ function SidebarRoot({
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Search (only when expanded and handler provided) */}
|
{/* Render Header first, then search, then remaining children */}
|
||||||
{onSearchChange && !collapsed && (
|
{(() => {
|
||||||
<div className={styles.searchWrap}>
|
const childArray = Children.toArray(children)
|
||||||
<div className={styles.searchInner}>
|
const headerIdx = childArray.findIndex(
|
||||||
<span className={styles.searchIcon} aria-hidden="true">
|
(child) => isValidElement(child) && child.type === SidebarHeader,
|
||||||
<Search size={12} />
|
)
|
||||||
</span>
|
const header = headerIdx >= 0 ? childArray[headerIdx] : null
|
||||||
<input
|
const rest = headerIdx >= 0
|
||||||
className={styles.searchInput}
|
? [...childArray.slice(0, headerIdx), ...childArray.slice(headerIdx + 1)]
|
||||||
type="text"
|
: childArray
|
||||||
placeholder="Filter..."
|
|
||||||
value={searchValue ?? ''}
|
|
||||||
onChange={(e) => onSearchChange(e.target.value)}
|
|
||||||
/>
|
|
||||||
{searchValue && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={styles.searchClear}
|
|
||||||
onClick={() => onSearchChange('')}
|
|
||||||
aria-label="Clear search"
|
|
||||||
>
|
|
||||||
<X size={12} />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{children}
|
return (
|
||||||
|
<>
|
||||||
|
{header}
|
||||||
|
{onSearchChange && !collapsed && (
|
||||||
|
<div className={styles.searchWrap}>
|
||||||
|
<div className={styles.searchInner}>
|
||||||
|
<span className={styles.searchIcon} aria-hidden="true">
|
||||||
|
<Search size={12} />
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
className={styles.searchInput}
|
||||||
|
type="text"
|
||||||
|
placeholder="Filter..."
|
||||||
|
value={searchValue ?? ''}
|
||||||
|
onChange={(e) => onSearchChange(e.target.value)}
|
||||||
|
/>
|
||||||
|
{searchValue && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={styles.searchClear}
|
||||||
|
onClick={() => onSearchChange('')}
|
||||||
|
aria-label="Clear search"
|
||||||
|
>
|
||||||
|
<X size={12} />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{rest}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})()}
|
||||||
</aside>
|
</aside>
|
||||||
</SidebarContext.Provider>
|
</SidebarContext.Provider>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user