77 DialogActions ,
88 DialogClose ,
99 DialogContent ,
10- DialogDescription ,
1110 DialogTitle ,
1211 Flex ,
1312 Grid ,
@@ -16,6 +15,8 @@ import {
1615 List ,
1716 ListItem ,
1817 rawTheme ,
18+ ScrollArea ,
19+ SearchField ,
1920 Select ,
2021 SmallIconButton ,
2122 Text ,
@@ -115,13 +116,23 @@ export const SectionRedirects = () => {
115116 const [ toPathErrors , setToPathErrors ] = useState < string [ ] > ( [ ] ) ;
116117 const [ isImportDialogOpen , setIsImportDialogOpen ] = useState ( false ) ;
117118 const [ isDeleteAllDialogOpen , setIsDeleteAllDialogOpen ] = useState ( false ) ;
119+ const [ searchQuery , setSearchQuery ] = useState ( "" ) ;
118120 const pages = useStore ( $pages ) ;
119121 const existingPaths = getExistingRoutePaths ( pages ) ;
120122 const fromPathRef = useRef < HTMLInputElement > ( null ) ;
121123
122124 const isValidRedirects =
123125 fromPathErrors . length === 0 && toPathErrors . length === 0 ;
124126
127+ // Filter redirects based on search query
128+ const filteredRedirects = searchQuery
129+ ? redirects . filter (
130+ ( redirect ) =>
131+ redirect . old . toLowerCase ( ) . includes ( searchQuery . toLowerCase ( ) ) ||
132+ redirect . new . toLowerCase ( ) . includes ( searchQuery . toLowerCase ( ) )
133+ )
134+ : redirects ;
135+
125136 // Get all page paths for combobox suggestions
126137 const pagePaths = Array . from ( existingPaths ) . sort ( ) ;
127138
@@ -220,44 +231,51 @@ export const SectionRedirects = () => {
220231 onImport = { handleImportRedirects }
221232 />
222233
223- < Grid gap = { 2 } css = { sectionSpacing } >
224- < Flex gap = { 1 } align = "center" justify = "between" >
225- < Flex gap = { 1 } align = "center" >
226- < Text variant = "titles" > Redirects</ Text >
227- < Tooltip
228- variant = "wrapped"
229- content = {
230- < Flex direction = "column" gap = "2" >
231- < Text color = "subtle" >
232- Redirect old URLs to new ones so you don't lose traffic or
233- search engine rankings.
234- </ Text >
235- < Flex direction = "column" gap = "1" >
236- < Text variant = "regularBold" > Supported patterns:</ Text >
237- < Text > /path → Exact match</ Text >
238- < Text > /blog/* → All paths under /blog/</ Text >
239- < Text > /:slug → Dynamic segment</ Text >
240- < Text > /:id? → Optional segment</ Text >
241- </ Flex >
234+ < Grid gap = { 3 } css = { sectionSpacing } >
235+ < Flex gap = { 1 } align = "center" >
236+ < Text variant = "titles" > Redirects</ Text >
237+ < Tooltip
238+ variant = "wrapped"
239+ content = {
240+ < Flex direction = "column" gap = "2" >
241+ < Text >
242+ Redirect old URLs to new ones so you don't lose traffic or
243+ search engine rankings.
244+ </ Text >
245+ < Flex direction = "column" gap = "1" >
246+ < Text > Supported patterns:</ Text >
247+ < Text > /path → Exact match</ Text >
248+ < Text > /blog/* → All paths under /blog/</ Text >
249+ < Text > /:slug → Dynamic segment</ Text >
250+ < Text > /:id? → Optional segment</ Text >
242251 </ Flex >
243- }
252+ </ Flex >
253+ }
254+ >
255+ < InfoCircleIcon
256+ color = { rawTheme . colors . foregroundSubtle }
257+ tabIndex = { 0 }
258+ />
259+ </ Tooltip >
260+ </ Flex >
261+
262+ < Flex gap = "2" justify = "between" >
263+ < SearchField
264+ placeholder = "Search"
265+ value = { searchQuery }
266+ onChange = { ( event ) => setSearchQuery ( event . target . value ) }
267+ onAbort = { ( ) => setSearchQuery ( "" ) }
268+ disabled = { redirects . length === 0 }
269+ />
270+ < Flex gap = "2" >
271+ < Button
272+ color = "ghost"
273+ prefix = { < TrashIcon /> }
274+ onClick = { ( ) => setIsDeleteAllDialogOpen ( true ) }
275+ disabled = { redirects . length === 0 }
244276 >
245- < InfoCircleIcon
246- color = { rawTheme . colors . foregroundSubtle }
247- tabIndex = { 0 }
248- />
249- </ Tooltip >
250- </ Flex >
251- < Flex gap = "1" >
252- { redirects . length > 0 && (
253- < Button
254- color = "ghost"
255- prefix = { < TrashIcon /> }
256- onClick = { ( ) => setIsDeleteAllDialogOpen ( true ) }
257- >
258- Delete all
259- </ Button >
260- ) }
277+ Delete all
278+ </ Button >
261279 < Button
262280 color = "ghost"
263281 prefix = { < UploadIcon /> }
@@ -274,10 +292,13 @@ export const SectionRedirects = () => {
274292 >
275293 < DialogContent >
276294 < DialogTitle > Delete all redirects</ DialogTitle >
277- < DialogDescription >
278- Are you sure you want to delete all { redirects . length } redirect
279- { redirects . length !== 1 ? "s" : "" } ? This action cannot be undone.
280- </ DialogDescription >
295+ < Flex css = { { padding : theme . panel . padding } } >
296+ < Text >
297+ Are you sure you want to delete all { redirects . length } redirect
298+ { redirects . length !== 1 ? "s" : "" } ? This action cannot be
299+ undone.
300+ </ Text >
301+ </ Flex >
281302 < DialogActions >
282303 < Button color = "destructive" onClick = { handleDeleteAll } >
283304 Delete all
@@ -323,8 +344,6 @@ export const SectionRedirects = () => {
323344 } }
324345 />
325346
326- < ArrowRightIcon size = { 16 } style = { { flexShrink : 0 } } />
327-
328347 < InputErrorsTooltip
329348 errors = { toPathErrors . length > 0 ? toPathErrors : undefined }
330349 side = "top"
@@ -358,24 +377,37 @@ export const SectionRedirects = () => {
358377 </ Flex >
359378 ) }
360379 </ Grid >
361-
362380 { redirects . length > 0 ? (
363- < Grid css = { sectionSpacing } >
364- < List asChild >
365- < Flex direction = "column" gap = "1" align = "stretch" >
366- { redirects . map ( ( redirect , index ) => {
367- return (
368- < ListItem asChild key = { redirect . old } index = { index } >
369- < Flex
370- justify = "between"
371- align = "center"
372- gap = "2"
373- css = { {
374- p : theme . spacing [ 3 ] ,
375- overflow : "hidden" ,
376- } }
377- >
378- < Flex gap = "2" align = "center" >
381+ < ScrollArea >
382+ < Grid css = { sectionSpacing } >
383+ < List asChild >
384+ < Flex direction = "column" gap = "1" align = "stretch" >
385+ { filteredRedirects . map ( ( redirect , index ) => {
386+ return (
387+ < ListItem asChild key = { redirect . old } >
388+ < Grid
389+ align = "center"
390+ gap = "2"
391+ css = { {
392+ p : theme . spacing [ 3 ] ,
393+ overflow : "hidden" ,
394+ gridTemplateColumns : `1fr auto auto 1fr` ,
395+ position : "relative" ,
396+ "& > button" : {
397+ opacity : 0 ,
398+ position : "absolute" ,
399+ right : 0 ,
400+ top : 0 ,
401+ bottom : 0 ,
402+ height : "auto" ,
403+ borderRadius : 0 ,
404+ background : theme . colors . backgroundPanel ,
405+ } ,
406+ "&:hover > button, &:focus-within > button" : {
407+ opacity : 1 ,
408+ } ,
409+ } }
410+ >
379411 < Tooltip content = { redirect . old } >
380412 < Link
381413 underline = "hover"
@@ -384,18 +416,17 @@ export const SectionRedirects = () => {
384416 publishedOrigin
385417 ) . toString ( ) }
386418 css = { {
387- width : theme . spacing [ 18 ] ,
388419 wordBreak : "break-all" ,
420+ overflow : "hidden" ,
421+ textOverflow : "ellipsis" ,
389422 } }
390423 target = "_blank"
391424 >
392425 { redirect . old }
393426 </ Link >
394427 </ Tooltip >
395428 < Text > { redirect . status ?? "301" } </ Text >
396- < Flex shrink = { false } align = "center" >
397- < ArrowRightIcon size = { 16 } aria-disabled />
398- </ Flex >
429+ < ArrowRightIcon size = { 16 } />
399430 < Tooltip content = { redirect . new } >
400431 < Link
401432 underline = "hover"
@@ -405,29 +436,28 @@ export const SectionRedirects = () => {
405436 ) . toString ( ) }
406437 css = { {
407438 wordBreak : "break-all" ,
408- maxWidth : theme . spacing [ 30 ] ,
439+ overflow : "hidden" ,
440+ textOverflow : "ellipsis" ,
409441 } }
410442 target = "_blank"
411443 >
412444 { redirect . new }
413445 </ Link >
414446 </ Tooltip >
415- </ Flex >
416- < Flex gap = "2" >
417447 < SmallIconButton
418448 variant = "destructive"
419449 icon = { < TrashIcon /> }
420450 aria-label = { `Delete redirect from ${ redirect . old } ` }
421451 onClick = { ( ) => handleDeleteRedirect ( index ) }
422452 />
423- </ Flex >
424- </ Flex >
425- </ ListItem >
426- ) ;
427- } ) }
428- </ Flex >
429- </ List >
430- </ Grid >
453+ </ Grid >
454+ </ ListItem >
455+ ) ;
456+ } ) }
457+ </ Flex >
458+ </ List >
459+ </ Grid >
460+ </ ScrollArea >
431461 ) : null }
432462 </ >
433463 ) ;
0 commit comments