Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9,506 changes: 9,506 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

Large diffs are not rendered by default.

1,274 changes: 1,274 additions & 0 deletions packages/react-components/src/components/data-table-pro-max.tsx

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions packages/react-components/src/components/date-picker.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import * as React from "react";
import { DatePicker } from "./date-picker";

const meta = {
title: "Component/DatePicker",
} satisfies Meta;

export default meta;

type Story = StoryObj<typeof meta>;

export const Demo = {
render: () => {
const [date, setDate] = React.useState<Date | undefined>(undefined);

return (
<div>
<DatePicker value={date} onDateChange={setDate}/>
<span className="mt-4">{ date?.toString() }</span>
</div>
);
},
} satisfies Story;
56 changes: 56 additions & 0 deletions packages/react-components/src/components/date-picker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { useCallback, useId, useState } from "react"
import { format } from "date-fns"
import { CalendarIcon } from "lucide-react"
import { Popover, PopoverContent, PopoverTrigger } from "../shadcn/components/ui/popover"
import { Button } from "../shadcn/components/ui/button"
import { Calendar } from "../shadcn/components/ui/calendar"
import { cn } from "#shadcn/lib/utils.js"

export type DatePickerProps = {
value: Date | undefined
onDateChange?: (value?: Date) => void
formatDate?: (value: Date) => string
}

export function DatePicker(props: DatePickerProps) {
const id = useId();
const [open, setOpen] = useState(false);

const formatDate = useCallback((date: Date) => props.formatDate ? props.formatDate(date) : format(date, "MM/dd/yyyy"), []);

const onSelect = useCallback((value: Date | undefined) => {
setOpen(false);
props.onDateChange && props.onDateChange(value);
}, []);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Stale Closure in onSelect Callback

The onSelect useCallback has an empty dependency array, but it references props.onDateChange. This can lead to stale closure issues, causing the callback to use an outdated onDateChange prop.

Fix in Cursor Fix in Web


return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
id={id}
variant={"ghost"}
className="group bg-background hover:bg-background w-full justify-start px-3 font-normal"
>
<CalendarIcon
size={16}
className="text-muted-foreground/80 group-hover:text-foreground shrink-0 transition-colors"
aria-hidden="true"
/>
<span
className={cn("truncate", !props.value && "text-muted-foreground")}
>
{props.value ? formatDate(props.value) : "Pick a date"}
</span>
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-2" align="start">
<Calendar
mode="single"
selected={props.value}
onSelect={onSelect}
captionLayout="dropdown"
/>
</PopoverContent>
</Popover>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import * as React from "react";
import { DateRangePicker } from "./date-range-picker";

const meta = {
title: "Component/DateRangePicker",
} satisfies Meta;

export default meta;

type Story = StoryObj<typeof meta>;

export const Demo = {
render: () => {
const [dateRange, setDateRange] = React.useState<{ from?: Date; to?: Date } | undefined>(undefined);

return (
<div className="space-y-4">
<DateRangePicker value={dateRange} onDateRangeChange={setDateRange}/>
<div className="text-sm">
{dateRange ? (
<div className="space-y-1">
<div>From: {dateRange.from?.toString() || 'Not set'}</div>
<div>To: {dateRange.to?.toString() || 'Not set'}</div>
</div>
) : (
<div>No date range selected</div>
)}
</div>
</div>
);
},
} satisfies Story;
82 changes: 82 additions & 0 deletions packages/react-components/src/components/date-range-picker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { useCallback, useId, useState } from "react"
import { format } from "date-fns"
import { CalendarIcon } from "lucide-react"
import type { DateRange } from "react-day-picker"
import { Popover, PopoverContent, PopoverTrigger } from "#shadcn/components/ui/popover.js"
import { Button } from "#shadcn/components/ui/button.js"
import { Calendar } from "#shadcn/components/ui/calendar.js"
import { cn } from "#shadcn/lib/utils.js"


type DateRangePickerProps = {
value?: { from?: Date; to?: Date } | undefined
onDateRangeChange?: (value: { from?: Date; to?: Date } | undefined) => void
fromPlaceholder?: string | undefined
toPlaceholder?: string | undefined
formatDate?: (value: Date) => string
}

export function DateRangePicker(props: DateRangePickerProps) {
const id = useId();
const [open, setOpen] = useState(false);

const formatDate = useCallback((date: Date) => props.formatDate ? props.formatDate(date) : format(date, "MM/dd/yyyy"), []);

const onSelect = useCallback((range: DateRange | undefined) => {
if (!range) {
props.onDateRangeChange && props.onDateRangeChange(undefined)
} else {
const result: { from?: Date; to?: Date } = {}
if (range.from) result.from = range.from
if (range.to) result.to = range.to
props.onDateRangeChange && props.onDateRangeChange(result)
}
}, [])
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Stale Closures in Date Picker Callbacks

The formatDate and onSelect useCallback hooks in both DatePicker and DateRangePicker components omit relevant props from their dependency arrays. This can lead to stale closures, causing these callbacks to use outdated prop values if the formatDate, onDateChange, or onDateRangeChange props change.

Additional Locations (2)

Fix in Cursor Fix in Web

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Stale Closure in onSelect Callback

The onSelect useCallback is missing props.onDateRangeChange from its dependency array. This can lead to stale closure issues where the callback uses an outdated onDateRangeChange function if the prop changes.

Fix in Cursor Fix in Web


// Convert to DateRange type for Calendar
let selectedRange: DateRange | undefined = undefined
if (props.value?.from) {
selectedRange = { from: props.value.from }
if (props.value.to) selectedRange.to = props.value.to
}

return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
id={id}
variant="ghost"
className="group bg-background hover:bg-background w-full justify-start px-3 font-normal"
>
<CalendarIcon
size={16}
className="text-muted-foreground/80 group-hover:text-foreground shrink-0 transition-colors"
aria-hidden="true"
/>
<span
className={cn("truncate", !props.value?.from && "text-muted-foreground")}
>
{props.value?.from ? (
props.value.to ? (
<>
{formatDate(props.value.from)} - {formatDate(props.value.to)}
</>
) : (
formatDate(props.value.from)
)
) : (
"Pick a date range"
)}
</span>
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-2" align="start">
<Calendar
mode="range"
selected={selectedRange}
onSelect={onSelect}
/>
</PopoverContent>
</Popover>
)
}
21 changes: 20 additions & 1 deletion packages/react-components/src/shadcn/components/ui/calendar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
"use client";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

import {
ChevronDownIcon,
ChevronLeftIcon,
Expand All @@ -12,6 +11,7 @@ import {
} from "react-day-picker";
import { Button, buttonVariants } from "#shadcn/components/ui/button";
import { cn } from "#shadcn/lib/utils";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./select";

function Calendar({
className,
Expand Down Expand Up @@ -157,6 +157,25 @@ function Calendar({
<ChevronDownIcon className={cn("size-4", className)} {...props} />
);
},
Dropdown: ({ className, ...props }) => {
return (
<Select
defaultValue={props.value?.toString() || ''}
onValueChange={(val) => props.onChange?.({ target: { value: val } } as any)}
>
<SelectTrigger className="relative w-full" size={"regular"}>
<SelectValue />
</SelectTrigger>
<SelectContent className="max-h-60 overflow-auto" side="bottom" sideOffset={4}>
{props.options?.map((opt) => (
<SelectItem key={opt.value} value={opt.value.toString()}>
{opt.label}
</SelectItem>
))}
</SelectContent>
</Select>
)
},
Comment on lines +160 to +178
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not related to data table. Move to separate commit.

DayButton: CalendarDayButton,
WeekNumber: ({ children, ...props }) => {
return (
Expand Down