@@ -6,17 +6,10 @@ import { startTransition, useCallback, useEffect, useState } from "react";
66import { AppliedFilter } from "~/components/primitives/AppliedFilter" ;
77import { DateField } from "~/components/primitives/DateField" ;
88import { DateTime } from "~/components/primitives/DateTime" ;
9- import { Input } from "~/components/primitives/Input" ;
109import { Label } from "~/components/primitives/Label" ;
1110import { ComboboxProvider , SelectPopover , SelectProvider } from "~/components/primitives/Select" ;
12- import {
13- Select ,
14- SelectContent ,
15- SelectItem ,
16- SelectTrigger ,
17- SelectValue ,
18- } from "~/components/primitives/SimpleSelect" ;
1911import { useSearchParams } from "~/hooks/useSearchParam" ;
12+ import { cn } from "~/utils/cn" ;
2013import { Button } from "../../primitives/Buttons" ;
2114import { filterIcon } from "./RunFilters" ;
2215
@@ -104,9 +97,9 @@ const timePeriods = [
10497] ;
10598
10699const timeUnits = [
107- { label : "minutes" , value : "m" , singular : "minute" } ,
108- { label : "hours" , value : "h" , singular : "hour" } ,
109- { label : "days" , value : "d" , singular : "day" } ,
100+ { label : "minutes" , value : "m" , singular : "minute" , shortLabel : "mins" } ,
101+ { label : "hours" , value : "h" , singular : "hour" , shortLabel : "hours" } ,
102+ { label : "days" , value : "d" , singular : "day" , shortLabel : "days" } ,
110103] ;
111104
112105// Parse a period string (e.g., "90m", "2h", "7d") into value and unit
@@ -307,34 +300,44 @@ export function TimeDropdown({
307300 const [ fromValue , setFromValue ] = useState ( from ) ;
308301 const [ toValue , setToValue ] = useState ( to ) ;
309302
310- // Custom duration state
303+ // Selection state: preset value, "custom", or null (for date range mode)
311304 const initialCustom = getInitialCustomDuration ( period ) ;
305+ const isInitialCustom =
306+ period && ! timePeriods . some ( ( p ) => p . value === period ) && initialCustom . value !== "" ;
307+ const [ selectedPeriod , setSelectedPeriod ] = useState < string | null > (
308+ isInitialCustom ? "custom" : period ?? defaultPeriod
309+ ) ;
310+
311+ // Custom duration state
312312 const [ customValue , setCustomValue ] = useState ( initialCustom . value ) ;
313313 const [ customUnit , setCustomUnit ] = useState ( initialCustom . unit ) ;
314314
315- // Sync custom duration state when period prop changes
315+ // Sync state when period prop changes
316316 useEffect ( ( ) => {
317317 const parsed = getInitialCustomDuration ( period ) ;
318318 setCustomValue ( parsed . value ) ;
319319 setCustomUnit ( parsed . unit ) ;
320+
321+ const isCustom = period && ! timePeriods . some ( ( p ) => p . value === period ) && parsed . value !== "" ;
322+ setSelectedPeriod ( isCustom ? "custom" : period ?? defaultPeriod ) ;
320323 } , [ period ] ) ;
321324
322- const applyDateRange = useCallback ( ( ) => {
323- replace ( {
324- period : undefined ,
325- cursor : undefined ,
326- direction : undefined ,
327- from : fromValue ?. getTime ( ) . toString ( ) ,
328- to : toValue ?. getTime ( ) . toString ( ) ,
329- } ) ;
325+ const applySelection = useCallback ( ( ) => {
326+ // If a period is selected (preset or custom), apply it
327+ if ( selectedPeriod ) {
328+ let periodToApply = selectedPeriod ;
330329
331- setOpen ( false ) ;
332- } , [ fromValue , toValue , replace ] ) ;
330+ // Build custom period string if custom is selected
331+ if ( selectedPeriod === "custom" ) {
332+ const value = parseInt ( customValue , 10 ) ;
333+ if ( isNaN ( value ) || value <= 0 ) {
334+ return ;
335+ }
336+ periodToApply = `${ value } ${ customUnit } ` ;
337+ }
333338
334- const handlePeriodClick = useCallback (
335- ( period : string ) => {
336339 replace ( {
337- period,
340+ period : periodToApply ,
338341 cursor : undefined ,
339342 direction : undefined ,
340343 from : undefined ,
@@ -343,26 +346,33 @@ export function TimeDropdown({
343346
344347 setFromValue ( undefined ) ;
345348 setToValue ( undefined ) ;
346-
347349 setOpen ( false ) ;
348- } ,
349- [ replace ]
350- ) ;
351-
352- const applyCustomDuration = useCallback ( ( ) => {
353- const value = parseInt ( customValue , 10 ) ;
354- if ( isNaN ( value ) || value <= 0 ) {
355350 return ;
356351 }
357- const periodString = `${ value } ${ customUnit } ` ;
358- handlePeriodClick ( periodString ) ;
359- } , [ customValue , customUnit , handlePeriodClick ] ) ;
352+
353+ // Otherwise apply date range
354+ replace ( {
355+ period : undefined ,
356+ cursor : undefined ,
357+ direction : undefined ,
358+ from : fromValue ?. getTime ( ) . toString ( ) ,
359+ to : toValue ?. getTime ( ) . toString ( ) ,
360+ } ) ;
361+
362+ setOpen ( false ) ;
363+ } , [ selectedPeriod , customValue , customUnit , fromValue , toValue , replace ] ) ;
360364
361365 const isCustomDurationValid = ( ( ) => {
362366 const value = parseInt ( customValue , 10 ) ;
363367 return ! isNaN ( value ) && value > 0 ;
364368 } ) ( ) ;
365369
370+ // Determine if Apply button should be enabled
371+ const canApply =
372+ ( selectedPeriod && selectedPeriod !== "custom" ) ||
373+ ( selectedPeriod === "custom" && isCustomDurationValid ) ||
374+ ( ! selectedPeriod && ( fromValue || toValue ) ) ;
375+
366376 return (
367377 < SelectProvider virtualFocus = { true } open = { open } setOpen = { setOpen } >
368378 { trigger }
@@ -374,86 +384,88 @@ export function TimeDropdown({
374384 >
375385 < div className = "flex flex-col gap-6 p-3" >
376386 < div className = "flex flex-col gap-1" >
377- < Label > Runs created in the last</ Label >
387+ < Label className = "mb-2" > Runs created in the last</ Label >
378388 < div className = "grid grid-cols-4 gap-2" >
379389 { timePeriods . map ( ( p ) => (
380390 < Button
381391 key = { p . value }
382392 variant = "secondary/small"
383393 className = {
384- p . value === period
394+ p . value === selectedPeriod
385395 ? "border-indigo-500 group-hover/button:border-indigo-500"
386396 : undefined
387397 }
388398 onClick = { ( e ) => {
389399 e . preventDefault ( ) ;
390- handlePeriodClick ( p . value ) ;
400+ setSelectedPeriod ( p . value ) ;
401+ // Clear custom value when selecting a preset
402+ setCustomValue ( "" ) ;
391403 } }
392404 fullWidth
393405 type = "button"
394406 >
395407 { p . label }
396408 </ Button >
397409 ) ) }
398- </ div >
399- </ div >
400-
401- < div className = "flex flex-col gap-1" >
402- < Label > Custom duration</ Label >
403- < div className = "flex items-center gap-2" >
404- < Input
405- type = "number"
406- min = "1"
407- step = "1"
408- placeholder = "e.g. 90"
409- value = { customValue }
410- onChange = { ( e ) => setCustomValue ( e . target . value ) }
411- onKeyDown = { ( e ) => {
412- if ( e . key === "Enter" && isCustomDurationValid ) {
413- e . preventDefault ( ) ;
414- applyCustomDuration ( ) ;
415- }
416- } }
417- variant = "small"
418- fullWidth = { false }
419- containerClassName = "w-20"
420- />
421- < Select value = { customUnit } onValueChange = { setCustomUnit } >
422- < SelectTrigger size = "secondary/small" >
423- < SelectValue />
424- </ SelectTrigger >
425- < SelectContent >
410+ { /* Custom duration row */ }
411+ < div
412+ className = { cn (
413+ "col-span-4 flex h-[1.8rem] w-full items-center gap-2 rounded border py-0.5 pl-0 pr-2 transition-colors" ,
414+ selectedPeriod === "custom"
415+ ? "border-indigo-500 bg-charcoal-750"
416+ : "border-charcoal-650 bg-charcoal-750 hover:border-charcoal-600"
417+ ) }
418+ >
419+ < input
420+ type = "number"
421+ min = "1"
422+ step = "1"
423+ placeholder = "Custom"
424+ value = { customValue }
425+ onChange = { ( e ) => {
426+ setCustomValue ( e . target . value ) ;
427+ setSelectedPeriod ( "custom" ) ;
428+ } }
429+ onFocus = { ( ) => setSelectedPeriod ( "custom" ) }
430+ className = "h-full w-full translate-y-px border-none bg-transparent py-0 pl-2 pr-0 text-xs leading-none text-text-bright outline-none placeholder:text-text-dimmed focus:outline-none focus:ring-0 [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
431+ />
432+ < div className = "flex items-center gap-2" >
426433 { timeUnits . map ( ( unit ) => (
427- < SelectItem key = { unit . value } value = { unit . value } >
428- { unit . label }
429- </ SelectItem >
434+ < button
435+ key = { unit . value }
436+ type = "button"
437+ onClick = { ( ) => {
438+ setCustomUnit ( unit . value ) ;
439+ setSelectedPeriod ( "custom" ) ;
440+ } }
441+ className = { cn (
442+ "text-xs transition-colors" ,
443+ customUnit === unit . value
444+ ? "text-indigo-500"
445+ : "text-text-dimmed hover:text-text-bright"
446+ ) }
447+ >
448+ { unit . shortLabel }
449+ </ button >
430450 ) ) }
431- </ SelectContent >
432- </ Select >
433- < Button
434- variant = "secondary/small"
435- disabled = { ! isCustomDurationValid }
436- onClick = { ( e ) => {
437- e . preventDefault ( ) ;
438- applyCustomDuration ( ) ;
439- } }
440- type = "button"
441- >
442- Apply
443- </ Button >
451+ </ div >
452+ </ div >
444453 </ div >
445454 </ div >
446455
447456 < div className = "flex flex-col gap-4 border-t border-grid-bright pt-4" >
448- < Label className = "text-text-dimmed " > Or specify exact time range</ Label >
457+ < Label className = "text-text-bright " > Or specify exact time range</ Label >
449458 < div className = "flex flex-col gap-1" >
450459 < Label >
451460 From < span className = "text-text-dimmed" > (local time)</ span >
452461 </ Label >
453462 < DateField
454463 label = "From time"
455464 defaultValue = { fromValue }
456- onValueChange = { setFromValue }
465+ onValueChange = { ( value ) => {
466+ setFromValue ( value ) ;
467+ if ( value ) setSelectedPeriod ( null ) ;
468+ } }
457469 granularity = "second"
458470 showNowButton
459471 showClearButton
@@ -467,7 +479,10 @@ export function TimeDropdown({
467479 < DateField
468480 label = "To time"
469481 defaultValue = { toValue }
470- onValueChange = { setToValue }
482+ onValueChange = { ( value ) => {
483+ setToValue ( value ) ;
484+ if ( value ) setSelectedPeriod ( null ) ;
485+ } }
471486 granularity = "second"
472487 showNowButton
473488 showClearButton
@@ -494,10 +509,10 @@ export function TimeDropdown({
494509 key : "Enter" ,
495510 enabledOnInputElements : true ,
496511 } }
497- disabled = { ! fromValue && ! toValue }
512+ disabled = { ! canApply }
498513 onClick = { ( e ) => {
499514 e . preventDefault ( ) ;
500- applyDateRange ( ) ;
515+ applySelection ( ) ;
501516 } }
502517 type = "button"
503518 >
0 commit comments