Skip to content

Commit

Permalink
feat: handle date range url state with nuqs
Browse files Browse the repository at this point in the history
  • Loading branch information
nainglinnkhant committed Dec 9, 2024
1 parent d73f633 commit 3c302bf
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 86 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"nanoid": "^5.0.7",
"next": "14.2.4",
"next-themes": "^0.3.0",
"nuqs": "^2.2.3",
"postgres": "^3.4.4",
"react": "18.3.1",
"react-day-picker": "^8.10.1",
Expand Down
27 changes: 27 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/app/_components/create-task-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export function CreateTaskDialog() {
<FormLabel>Title</FormLabel>
<FormControl>
<Textarea
placeholder="Do a kickflip"
placeholder="Build a reusable combobox"
className="resize-none"
{...field}
/>
Expand Down
2 changes: 1 addition & 1 deletion src/app/_components/update-task-sheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export function UpdateTaskSheet({ task, ...props }: UpdateTaskSheetProps) {
<FormLabel>Title</FormLabel>
<FormControl>
<Textarea
placeholder="Do a kickflip"
placeholder="Build a reusable combobox"
className="resize-none"
{...field}
/>
Expand Down
27 changes: 15 additions & 12 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Analytics } from "@vercel/analytics/react"
import { NuqsAdapter } from "nuqs/adapters/next/app"

import { siteConfig } from "@/config/site"
import { cn } from "@/lib/utils"
Expand Down Expand Up @@ -84,18 +85,20 @@ export default function RootLayout({ children }: React.PropsWithChildren) {
fontMono.variable
)}
>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<div className="relative flex min-h-screen flex-col">
<SiteHeader />
<main className="flex-1">{children}</main>
</div>
<TailwindIndicator />
</ThemeProvider>
<NuqsAdapter>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<div className="relative flex min-h-screen flex-col">
<SiteHeader />
<main className="flex-1">{children}</main>
</div>
<TailwindIndicator />
</ThemeProvider>
</NuqsAdapter>
<Toaster richColors />
<Analytics />
</body>
Expand Down
111 changes: 39 additions & 72 deletions src/components/date-range-picker.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
"use client"

import * as React from "react"
import { usePathname, useRouter, useSearchParams } from "next/navigation"
import { CalendarIcon } from "@radix-ui/react-icons"
import { addDays, format } from "date-fns"
import type { DateRange } from "react-day-picker"
import { format } from "date-fns"
import { createParser, useQueryState } from "nuqs"

import { cn } from "@/lib/utils"
import { Button, type ButtonProps } from "@/components/ui/button"
Expand All @@ -15,6 +14,15 @@ import {
PopoverTrigger,
} from "@/components/ui/popover"

const parseAsDate = createParser({
parse(queryValue) {
return new Date(queryValue)
},
serialize(value) {
return format(value, "yyyy-MM-dd")
},
})

interface DateRangePickerProps
extends React.ComponentPropsWithoutRef<typeof PopoverContent> {
/**
Expand All @@ -23,15 +31,15 @@ interface DateRangePickerProps
* @type DateRange
* @example { from: new Date(), to: new Date() }
*/
dateRange?: DateRange
// dateRange?: DateRange

/**
* The number of days to display in the date range picker.
* @default undefined
* @type number
* @example 7
*/
dayCount?: number
// dayCount?: number

/**
* The placeholder text of the calendar trigger button.
Expand Down Expand Up @@ -63,73 +71,26 @@ interface DateRangePickerProps
}

export function DateRangePicker({
dateRange,
dayCount,
// dateRange,
// dayCount,
placeholder = "Pick a date",
triggerVariant = "outline",
triggerSize = "default",
triggerClassName,
className,
...props
}: DateRangePickerProps) {
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()

const fromParam = searchParams.get("from")
const toParam = searchParams.get("to")

function calcDateRange() {
let fromDay: Date | undefined
let toDay: Date | undefined

if (dateRange) {
fromDay = dateRange.from
toDay = dateRange.to
} else if (dayCount) {
toDay = new Date()
fromDay = addDays(toDay, -dayCount)
}

return {
from: fromParam ? new Date(fromParam) : fromDay,
to: toParam ? new Date(toParam) : toDay,
}
}

const [date, setDate] = React.useState<DateRange | undefined>(() =>
calcDateRange()
const [fromDate, setFromDate] = useQueryState(
"from",
parseAsDate.withOptions({ shallow: false, history: "push" })
)
const [toDate, setToDate] = useQueryState(
"to",
parseAsDate.withOptions({ shallow: false, history: "push" })
)

// Update query string
React.useEffect(() => {
const newSearchParams = new URLSearchParams(searchParams)
if (date?.from) {
newSearchParams.set("from", format(date.from, "yyyy-MM-dd"))
} else {
newSearchParams.delete("from")
}

if (date?.to) {
newSearchParams.set("to", format(date.to, "yyyy-MM-dd"))
} else {
newSearchParams.delete("to")
}

router.replace(`${pathname}?${newSearchParams.toString()}`, {
scroll: false,
})

// eslint-disable-next-line react-hooks/exhaustive-deps
}, [date?.from, date?.to])

// React.useEffect(() => {
// const dateRange = calcDateRange()

// setDate(dateRange)

// // eslint-disable-next-line react-hooks/exhaustive-deps
// }, [fromParam, toParam])
const from = fromDate ?? undefined
const to = toDate ?? undefined

return (
<div className="grid gap-2">
Expand All @@ -140,19 +101,19 @@ export function DateRangePicker({
size={triggerSize}
className={cn(
"w-full justify-start truncate text-left font-normal",
!date && "text-muted-foreground",
!fromDate && !toDate && "text-muted-foreground",
triggerClassName
)}
>
<CalendarIcon className="mr-2 size-4" />
{date?.from ? (
date.to ? (
{fromDate ? (
toDate ? (
<>
{format(date.from, "LLL dd, y")} -{" "}
{format(date.to, "LLL dd, y")}
{format(fromDate, "LLL dd, y")} -{" "}
{format(toDate, "LLL dd, y")}
</>
) : (
format(date.from, "LLL dd, y")
format(fromDate, "LLL dd, y")
)
) : (
<span>{placeholder}</span>
Expand All @@ -163,9 +124,15 @@ export function DateRangePicker({
<Calendar
initialFocus
mode="range"
defaultMonth={date?.from}
selected={date}
onSelect={setDate}
defaultMonth={from}
selected={{ from, to }}
disabled={(date) => date > new Date()}
onSelect={(value) => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
setFromDate(value?.from ?? null)
// eslint-disable-next-line @typescript-eslint/no-floating-promises
setToDate(value?.to ?? null)
}}
numberOfMonths={2}
/>
</PopoverContent>
Expand Down

0 comments on commit 3c302bf

Please sign in to comment.