@@ -265,8 +265,8 @@ export function timeFilterRenderValues({
265265 rangeType === "range" || rangeType === "period"
266266 ? labelName
267267 : rangeType === "from"
268- ? `${ labelName } after`
269- : `${ labelName } before` ;
268+ ? `${ labelName } after`
269+ : `${ labelName } before` ;
270270
271271 return { label, valueLabel, rangeType } ;
272272}
@@ -280,56 +280,37 @@ export interface TimeFilterApplyValues {
280280
281281export interface TimeFilterProps {
282282 defaultPeriod ?: string ;
283+ period ?: string ;
284+ from ?: string ;
285+ to ?: string ;
283286 /** Label name used in the filter display, defaults to "Created" */
284287 labelName ?: string ;
285288 applyShortcut ?: ShortcutDefinition | undefined ;
286289 /** Callback when the user applies a time filter selection, receives the applied values */
287- onApply ?: ( values : TimeFilterApplyValues ) => void ;
290+ onValueChange ?: ( values : TimeFilterApplyValues ) => void ;
288291}
289292
290293export function TimeFilter ( {
291294 defaultPeriod,
295+ period,
296+ from,
297+ to,
292298 labelName = "Created" ,
293299 applyShortcut,
294- onApply ,
300+ onValueChange ,
295301} : TimeFilterProps = { } ) {
296302 const { value } = useSearchParams ( ) ;
297- const periodValue = value ( "period" ) ;
298- const fromValue = value ( "from" ) ;
299- const toValue = value ( "to" ) ;
300-
301- //non-optimistic location
302- const location = useLocation ( ) ;
303-
304- const { period, from, to, label, valueLabel } = timeFilters ( {
303+ const periodValue = period ?? value ( "period" ) ;
304+ const fromValue = from ?? value ( "from" ) ;
305+ const toValue = to ?? value ( "to" ) ;
306+
307+ const constrained = timeFilters ( {
305308 period : periodValue ,
306309 from : fromValue ,
307310 to : toValue ,
308311 defaultPeriod,
309312 labelName,
310313 } ) ;
311-
312- // Track the search string, not individual values
313- const previousSearch = useRef < string | null > ( null ) ;
314-
315- useEffect ( ( ) => {
316- // Skip first render - set initial value and return
317- if ( previousSearch . current === null ) {
318- previousSearch . current = location . search ;
319- return ;
320- }
321-
322- // Only call onApply if the URL actually changed
323- if ( previousSearch . current !== location . search ) {
324- const currentSearch = new URLSearchParams ( location . search ) ;
325- onApply ?.( {
326- period : currentSearch . get ( "period" ) ?? undefined ,
327- from : currentSearch . get ( "from" ) ?? undefined ,
328- to : currentSearch . get ( "to" ) ?? undefined
329- } ) ;
330- previousSearch . current = location . search ;
331- }
332- } , [ location . search , onApply ] ) ;
333314
334315 return (
335316 < FilterMenuProvider >
@@ -338,21 +319,21 @@ export function TimeFilter({
338319 trigger = {
339320 < Ariakit . Select render = { < div className = "group cursor-pointer focus-custom" /> } >
340321 < AppliedFilter
341- label = { label }
322+ label = { constrained . label }
342323 icon = { filterIcon ( "period" ) }
343- value = { valueLabel }
324+ value = { constrained . valueLabel }
344325 removable = { false }
345326 variant = "secondary/small"
346327 />
347328 </ Ariakit . Select >
348329 }
349- period = { period }
350- from = { from }
351- to = { to }
330+ period = { constrained . period }
331+ from = { constrained . from }
332+ to = { constrained . to }
352333 defaultPeriod = { defaultPeriod }
353334 labelName = { labelName }
354335 applyShortcut = { applyShortcut }
355- onApply = { onApply }
336+ onValueChange = { onValueChange }
356337 />
357338 ) }
358339 </ FilterMenuProvider >
@@ -379,6 +360,7 @@ export function TimeDropdown({
379360 labelName = "Created" ,
380361 applyShortcut,
381362 onApply,
363+ onValueChange,
382364} : {
383365 trigger : ReactNode ;
384366 period ?: string ;
@@ -388,6 +370,8 @@ export function TimeDropdown({
388370 labelName ?: string ;
389371 applyShortcut ?: ShortcutDefinition | undefined ;
390372 onApply ?: ( values : TimeFilterApplyValues ) => void ;
373+ /** When provided, the component operates in controlled mode and skips URL navigation */
374+ onValueChange ?: ( values : TimeFilterApplyValues ) => void ;
391375} ) {
392376 const [ open , setOpen ] = useState < boolean | undefined > ( ) ;
393377 const { replace } = useSearchParams ( ) ;
@@ -444,18 +428,26 @@ export function TimeDropdown({
444428 periodToApply = `${ customValue } ${ customUnit } ` ;
445429 }
446430
447- replace ( {
448- period : periodToApply ,
449- cursor : undefined ,
450- direction : undefined ,
451- from : undefined ,
452- to : undefined ,
453- } ) ;
431+ const values : TimeFilterApplyValues = { period : periodToApply , from : undefined , to : undefined } ;
432+
433+ if ( onValueChange ) {
434+ // Controlled mode - just call the handler
435+ onValueChange ( values ) ;
436+ } else {
437+ // URL mode - navigate
438+ replace ( {
439+ period : periodToApply ,
440+ cursor : undefined ,
441+ direction : undefined ,
442+ from : undefined ,
443+ to : undefined ,
444+ } ) ;
445+ }
454446
455447 setFromValue ( undefined ) ;
456448 setToValue ( undefined ) ;
457449 setOpen ( false ) ;
458- onApply ?.( { period : periodToApply , from : undefined , to : undefined } ) ;
450+ onApply ?.( values ) ;
459451 } else {
460452 // Validate date range
461453 if ( ! fromValue && ! toValue ) {
@@ -471,16 +463,24 @@ export function TimeDropdown({
471463 const fromStr = fromValue ?. getTime ( ) . toString ( ) ;
472464 const toStr = toValue ?. getTime ( ) . toString ( ) ;
473465
474- replace ( {
475- period : undefined ,
476- cursor : undefined ,
477- direction : undefined ,
478- from : fromStr ,
479- to : toStr ,
480- } ) ;
466+ const values : TimeFilterApplyValues = { period : undefined , from : fromStr , to : toStr } ;
467+
468+ if ( onValueChange ) {
469+ // Controlled mode - just call the handler
470+ onValueChange ( values ) ;
471+ } else {
472+ // URL mode - navigate
473+ replace ( {
474+ period : undefined ,
475+ cursor : undefined ,
476+ direction : undefined ,
477+ from : fromStr ,
478+ to : toStr ,
479+ } ) ;
480+ }
481481
482482 setOpen ( false ) ;
483- onApply ?.( { period : undefined , from : fromStr , to : toStr } ) ;
483+ onApply ?.( values ) ;
484484 }
485485 } , [
486486 activeSection ,
@@ -492,6 +492,7 @@ export function TimeDropdown({
492492 toValue ,
493493 replace ,
494494 onApply ,
495+ onValueChange ,
495496 ] ) ;
496497
497498 return (
@@ -532,9 +533,9 @@ export function TimeDropdown({
532533 ? "border-indigo-500 "
533534 : "border-charcoal-650 hover:border-charcoal-600" ,
534535 validationError &&
535- activeSection === "duration" &&
536- selectedPeriod === "custom" &&
537- "border-error"
536+ activeSection === "duration" &&
537+ selectedPeriod === "custom" &&
538+ "border-error"
538539 ) }
539540 onClick = { ( e ) => e . stopPropagation ( ) }
540541 >
0 commit comments