@@ -2,6 +2,7 @@ import { motion } from 'motion/react';
22import type React from 'react' ;
33import { useEffect , useMemo , useRef , useState } from 'react' ;
44import { createPortal } from 'react-dom' ;
5+ import StarBorder from '@/components/StarBorder' ;
56import { Kbd , KbdGroup } from '@/components/ui/kbd' ;
67import { Spinner } from '@/components/ui/spinner' ;
78import { cn } from '@/lib/utils' ;
@@ -476,150 +477,158 @@ export const SearchDialog: React.FC<SearchDialogProps> = ({
476477 />
477478
478479 { /* Dialog */ }
479- < motion . div
480- ref = { dialogRef }
481- initial = { { opacity : 0 , scale : 0.96 , y : - 10 } }
482- animate = { { opacity : 1 , scale : 1 , y : 0 } }
483- exit = { { opacity : 0 , scale : 0.96 , y : - 10 } }
484- transition = { { duration : 0.15 } }
485- className = "relative w-full max-w-2xl bg-popover border border-border rounded-lg shadow-2xl overflow-hidden"
480+ < StarBorder
481+ className = "w-full max-w-2xl rounded-lg"
482+ color = "var(--primary)"
483+ speed = "8s"
484+ thickness = { 1 }
485+ isAnimating = { ! query }
486486 >
487- { /* Search input */ }
488- < div className = "flex items-center gap-3 px-4 py-3 border-b border-border" >
489- < svg
490- className = "w-5 h-5 text-muted-foreground flex-shrink-0"
491- fill = "none"
492- viewBox = "0 0 24 24"
493- stroke = "currentColor"
494- >
495- < path
496- strokeLinecap = "round"
497- strokeLinejoin = "round"
498- strokeWidth = { 2 }
499- d = "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
500- />
501- </ svg >
502- < input
503- ref = { inputRef }
504- type = "text"
505- value = { query }
506- onChange = { ( e ) => setQuery ( e . target . value ) }
507- placeholder = {
508- pagefindReady ? 'Search documentation...' : 'Loading search...'
509- }
510- disabled = { ! pagefindReady }
511- className = "flex-1 bg-transparent text-foreground placeholder:text-muted-foreground outline-none text-sm disabled:opacity-50"
512- />
513- { query && (
514- < button
515- type = "button"
516- onClick = { ( ) => setQuery ( '' ) }
517- className = "text-muted-foreground hover:text-foreground transition-colors"
487+ < motion . div
488+ ref = { dialogRef }
489+ initial = { { opacity : 0 , scale : 0.96 , y : - 10 } }
490+ animate = { { opacity : 1 , scale : 1 , y : 0 } }
491+ exit = { { opacity : 0 , scale : 0.96 , y : - 10 } }
492+ transition = { { duration : 0.15 } }
493+ className = "relative w-full bg-popover border border-border rounded-lg shadow-2xl overflow-hidden"
494+ >
495+ { /* Search input */ }
496+ < div className = "flex items-center gap-3 px-4 py-3 border-b border-border" >
497+ < svg
498+ className = "w-5 h-5 text-muted-foreground flex-shrink-0"
499+ fill = "none"
500+ viewBox = "0 0 24 24"
501+ stroke = "currentColor"
518502 >
519- < svg
520- className = "w-4 h-4"
521- fill = "none"
522- viewBox = "0 0 24 24"
523- stroke = "currentColor"
503+ < path
504+ strokeLinecap = "round"
505+ strokeLinejoin = "round"
506+ strokeWidth = { 2 }
507+ d = "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
508+ />
509+ </ svg >
510+ < input
511+ ref = { inputRef }
512+ type = "text"
513+ value = { query }
514+ onChange = { ( e ) => setQuery ( e . target . value ) }
515+ placeholder = {
516+ pagefindReady ? 'Search documentation...' : 'Loading search...'
517+ }
518+ disabled = { ! pagefindReady }
519+ className = "flex-1 bg-transparent text-foreground placeholder:text-muted-foreground outline-none text-sm disabled:opacity-50"
520+ />
521+ { query && (
522+ < button
523+ type = "button"
524+ onClick = { ( ) => setQuery ( '' ) }
525+ className = "text-muted-foreground hover:text-foreground transition-colors"
524526 >
525- < path
526- strokeLinecap = "round"
527- strokeLinejoin = "round"
528- strokeWidth = { 2 }
529- d = "M6 18L18 6M6 6l12 12"
530- />
531- </ svg >
532- </ button >
533- ) }
534- < Kbd className = "hidden sm:inline-flex" > Esc</ Kbd >
535- </ div >
536-
537- { /* Results count */ }
538- { ! isLoading && query && results . length > 0 && (
539- < div className = "px-4 py-2 text-xs text-muted-foreground border-b border-border" >
540- { results . length } result{ results . length !== 1 ? 's' : '' } for "
541- { query } "
527+ < svg
528+ className = "w-4 h-4"
529+ fill = "none"
530+ viewBox = "0 0 24 24"
531+ stroke = "currentColor"
532+ >
533+ < path
534+ strokeLinecap = "round"
535+ strokeLinejoin = "round"
536+ strokeWidth = { 2 }
537+ d = "M6 18L18 6M6 6l12 12"
538+ />
539+ </ svg >
540+ </ button >
541+ ) }
542+ < Kbd className = "hidden sm:inline-flex" > Esc</ Kbd >
542543 </ div >
543- ) }
544544
545- { /* Results - only show when there's a query */ }
546- { query && (
547- < div className = "relative" >
548- < div
549- ref = { listRef }
550- className = "max-h-[60vh] overflow-y-auto p-2 [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:bg-border [&::-webkit-scrollbar-thumb]:rounded-full"
551- onScroll = { handleScroll }
552- style = { {
553- scrollbarWidth : 'thin' ,
554- scrollbarColor : 'var(--border) transparent' ,
555- } }
556- >
557- { isLoading ? (
558- < div className = "py-8 flex items-center justify-center gap-2 text-muted-foreground text-sm" >
559- < Spinner className = "size-4" />
560- < span > Searching...</ span >
561- </ div >
562- ) : selectableItems . length === 0 ? (
563- < div className = "py-8 text-center text-muted-foreground text-sm" >
564- No results found for "{ query } "
565- </ div >
566- ) : (
567- selectableItems . map ( ( item , index ) => (
568- < AnimatedItem
569- key = { item . id }
570- index = { index }
571- delay = { 0.03 }
572- onMouseEnter = { ( ) => setSelectedIndex ( index ) }
573- onClick = { ( ) => {
574- window . location . href = item . url ;
575- onClose ( ) ;
576- } }
577- >
578- { item . type === 'section' ? (
579- < SectionResultItem
580- item = { item }
581- isSelected = { selectedIndex === index }
582- />
583- ) : (
584- < PageResultItem
585- item = { item }
586- isSelected = { selectedIndex === index }
587- />
588- ) }
589- </ AnimatedItem >
590- ) )
591- ) }
545+ { /* Results count */ }
546+ { ! isLoading && query && results . length > 0 && (
547+ < div className = "px-4 py-2 text-xs text-muted-foreground border-b border-border" >
548+ { results . length } result{ results . length !== 1 ? 's' : '' } for "
549+ { query } "
592550 </ div >
551+ ) }
593552
594- { /* Gradient overlays */ }
595- < div
596- className = "absolute top-0 left-0 right-0 h-8 bg-gradient-to-b from-popover to-transparent pointer-events-none transition-opacity duration-300"
597- style = { { opacity : topGradientOpacity } }
598- />
599- < div
600- className = "absolute bottom-0 left-0 right-0 h-12 bg-gradient-to-t from-popover to-transparent pointer-events-none transition-opacity duration-300"
601- style = { { opacity : bottomGradientOpacity } }
602- />
603- </ div >
604- ) }
553+ { /* Results - only show when there's a query */ }
554+ { query && (
555+ < div className = "relative" >
556+ < div
557+ ref = { listRef }
558+ className = "max-h-[60vh] overflow-y-auto p-2 [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:bg-border [&::-webkit-scrollbar-thumb]:rounded-full"
559+ onScroll = { handleScroll }
560+ style = { {
561+ scrollbarWidth : 'thin' ,
562+ scrollbarColor : 'var(--border) transparent' ,
563+ } }
564+ >
565+ { isLoading ? (
566+ < div className = "py-8 flex items-center justify-center gap-2 text-muted-foreground text-sm" >
567+ < Spinner className = "size-4" />
568+ < span > Searching...</ span >
569+ </ div >
570+ ) : selectableItems . length === 0 ? (
571+ < div className = "py-8 text-center text-muted-foreground text-sm" >
572+ No results found for "{ query } "
573+ </ div >
574+ ) : (
575+ selectableItems . map ( ( item , index ) => (
576+ < AnimatedItem
577+ key = { item . id }
578+ index = { index }
579+ delay = { 0.03 }
580+ onMouseEnter = { ( ) => setSelectedIndex ( index ) }
581+ onClick = { ( ) => {
582+ window . location . href = item . url ;
583+ onClose ( ) ;
584+ } }
585+ >
586+ { item . type === 'section' ? (
587+ < SectionResultItem
588+ item = { item }
589+ isSelected = { selectedIndex === index }
590+ />
591+ ) : (
592+ < PageResultItem
593+ item = { item }
594+ isSelected = { selectedIndex === index }
595+ />
596+ ) }
597+ </ AnimatedItem >
598+ ) )
599+ ) }
600+ </ div >
601+
602+ { /* Gradient overlays */ }
603+ < div
604+ className = "absolute top-0 left-0 right-0 h-8 bg-gradient-to-b from-popover to-transparent pointer-events-none transition-opacity duration-300"
605+ style = { { opacity : topGradientOpacity } }
606+ />
607+ < div
608+ className = "absolute bottom-0 left-0 right-0 h-12 bg-gradient-to-t from-popover to-transparent pointer-events-none transition-opacity duration-300"
609+ style = { { opacity : bottomGradientOpacity } }
610+ />
611+ </ div >
612+ ) }
605613
606- { /* Footer */ }
607- { selectableItems . length > 0 && (
608- < div className = "px-4 py-2 border-t border-border text-xs text-muted-foreground flex items-center justify-end gap-4" >
609- < span className = "flex items-center gap-1" >
610- < KbdGroup >
611- < Kbd > ↑</ Kbd >
612- < Kbd > ↓</ Kbd >
613- </ KbdGroup >
614- < span className = "ml-1" > Navigate</ span >
615- </ span >
616- < span className = "flex items-center gap-1" >
617- < Kbd > ↵</ Kbd >
618- < span className = "ml-1" > Open</ span >
619- </ span >
620- </ div >
621- ) }
622- </ motion . div >
614+ { /* Footer */ }
615+ { selectableItems . length > 0 && (
616+ < div className = "px-4 py-2 border-t border-border text-xs text-muted-foreground flex items-center justify-end gap-4" >
617+ < span className = "flex items-center gap-1" >
618+ < KbdGroup >
619+ < Kbd > ↑</ Kbd >
620+ < Kbd > ↓</ Kbd >
621+ </ KbdGroup >
622+ < span className = "ml-1" > Navigate</ span >
623+ </ span >
624+ < span className = "flex items-center gap-1" >
625+ < Kbd > ↵</ Kbd >
626+ < span className = "ml-1" > Open</ span >
627+ </ span >
628+ </ div >
629+ ) }
630+ </ motion . div >
631+ </ StarBorder >
623632 </ div > ,
624633 document . body ,
625634 ) ;
0 commit comments