Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ✨ new date picker v9 #4421

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
22 changes: 22 additions & 0 deletions apps/www/__registry__/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1116,6 +1116,17 @@ export const Index: Record<string, any> = {
subcategory: "undefined",
chunks: []
},
"date-picker-year": {
name: "date-picker-year",
type: "components:example",
registryDependencies: ["button","calendar","popover"],
component: React.lazy(() => import("@/registry/default/example/date-picker-year")),
source: "",
files: ["registry/default/example/date-picker-year.tsx"],
category: "undefined",
subcategory: "undefined",
chunks: []
},
"dialog-demo": {
name: "dialog-demo",
type: "components:example",
Expand Down Expand Up @@ -4428,6 +4439,17 @@ export const Index: Record<string, any> = {
subcategory: "undefined",
chunks: []
},
"date-picker-year": {
name: "date-picker-year",
type: "components:example",
registryDependencies: ["button","calendar","popover"],
component: React.lazy(() => import("@/registry/new-york/example/date-picker-year")),
source: "",
files: ["registry/new-york/example/date-picker-year.tsx"],
category: "undefined",
subcategory: "undefined",
chunks: []
},
"dialog-demo": {
name: "dialog-demo",
type: "components:example",
Expand Down
12 changes: 11 additions & 1 deletion apps/www/app/(app)/docs/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,17 @@ export default async function DocPage({ params }: DocPageProps) {
notFound()
}

const toc = await getTableOfContents(doc.body.raw)
const toc = await getTableOfContents(doc.body.raw).then((toc) => ({
...toc,
items: toc.items?.map((item) => ({
...item,
items: item.items?.map((item) => ({
...item,
title: item.title.replace("New", ""),
url: item.url.replace(/span[^]*?span/g, ""),
})),
})),
}))

return (
<main className="relative py-6 lg:gap-10 lg:py-8 xl:grid xl:grid-cols-[1fr_300px]">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export function CalendarDateRangePicker({
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="end">
<Calendar
initialFocus
autoFocus
mode="range"
defaultMonth={date?.from}
selected={date}
Expand Down
2 changes: 1 addition & 1 deletion apps/www/app/(app)/examples/forms/account/account-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ export function AccountForm() {
disabled={(date) =>
date > new Date() || date < new Date("1900-01-01")
}
initialFocus
autoFocus
/>
</PopoverContent>
</Popover>
Expand Down
20 changes: 18 additions & 2 deletions apps/www/components/mdx-components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,28 @@ const components = {
{...props}
/>
),
h3: ({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) => (
h3: ({
className,
id,
...props
}: React.HTMLAttributes<HTMLHeadingElement>) => (
<h3
className={cn(
"font-heading mt-8 scroll-m-20 text-xl font-semibold tracking-tight",
"font-heading mt-8 flex scroll-m-20 items-center text-xl font-semibold tracking-tight",
className
)}
id={
props.children
?.toString()
.toLowerCase()
.replace(/[^a-z0-9]/g, "-")
.replace(/-+/g, "-")
.replace(/span[^]*?span/g, "")
.replaceAll("-object", "")
.replace("New", "")
.replace("new", "")
.replace(/^-/, "") ?? id
}
{...props}
/>
),
Expand Down
4 changes: 4 additions & 0 deletions apps/www/content/docs/components/date-picker.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ See the [React DayPicker](https://react-day-picker.js.org) documentation for mor

<ComponentPreview name="date-picker-with-range" />

### <span className="mr-2 rounded-md bg-[#adfa1d] px-1.5 py-0.5 font-normal text-xs leading-none text-[#000000] no-underline group-hover:no-underline">New</span>With Year Picker

<ComponentPreview name="date-picker-year" />

### With Presets

<ComponentPreview name="date-picker-with-presets" />
Expand Down
4 changes: 2 additions & 2 deletions apps/www/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@
"next": "14.3.0-canary.43",
"next-contentlayer2": "^0.4.6",
"next-themes": "^0.2.1",
"react": "^18.2.0",
"react-day-picker": "^8.7.1",
"react": "^18.3.1",
"react-day-picker": "^9.0.4",
"react-dom": "^18.2.0",
"react-hook-form": "^7.44.2",
"react-resizable-panels": "^2.0.22",
Expand Down
2 changes: 1 addition & 1 deletion apps/www/public/registry/styles/default/calendar.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"files": [
{
"name": "calendar.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronLeft, ChevronRight } from \"lucide-react\"\nimport { DayPicker } from \"react-day-picker\"\n\nimport { cn } from \"@/lib/utils\"\nimport { buttonVariants } from \"@/registry/default/ui/button\"\n\nexport type CalendarProps = React.ComponentProps<typeof DayPicker>\n\nfunction Calendar({\n className,\n classNames,\n showOutsideDays = true,\n ...props\n}: CalendarProps) {\n return (\n <DayPicker\n showOutsideDays={showOutsideDays}\n className={cn(\"p-3\", className)}\n classNames={{\n months: \"flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0\",\n month: \"space-y-4\",\n caption: \"flex justify-center pt-1 relative items-center\",\n caption_label: \"text-sm font-medium\",\n nav: \"space-x-1 flex items-center\",\n nav_button: cn(\n buttonVariants({ variant: \"outline\" }),\n \"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100\"\n ),\n nav_button_previous: \"absolute left-1\",\n nav_button_next: \"absolute right-1\",\n table: \"w-full border-collapse space-y-1\",\n head_row: \"flex\",\n head_cell:\n \"text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]\",\n row: \"flex w-full mt-2\",\n cell: \"h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20\",\n day: cn(\n buttonVariants({ variant: \"ghost\" }),\n \"h-9 w-9 p-0 font-normal aria-selected:opacity-100\"\n ),\n day_range_end: \"day-range-end\",\n day_selected:\n \"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground\",\n day_today: \"bg-accent text-accent-foreground\",\n day_outside:\n \"day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30\",\n day_disabled: \"text-muted-foreground opacity-50\",\n day_range_middle:\n \"aria-selected:bg-accent aria-selected:text-accent-foreground\",\n day_hidden: \"invisible\",\n ...classNames,\n }}\n components={{\n IconLeft: ({ ...props }) => <ChevronLeft className=\"h-4 w-4\" />,\n IconRight: ({ ...props }) => <ChevronRight className=\"h-4 w-4\" />,\n }}\n {...props}\n />\n )\n}\nCalendar.displayName = \"Calendar\"\n\nexport { Calendar }\n"
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { differenceInCalendarDays } from \"date-fns\"\nimport { ChevronLeft, ChevronRight } from \"lucide-react\"\nimport {\n DayPicker,\n labelNext,\n labelPrevious,\n useDayPicker,\n} from \"react-day-picker\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button, buttonVariants } from \"@/registry/default/ui/button\"\n\nexport type CalendarProps = React.ComponentProps<typeof DayPicker> & {\n /**\n * In the year view, the number of years to display at once.\n * @default 12\n */\n yearRange?: number\n /**\n * Wether to let user switch between months and years view.\n * @default false\n */\n showYearSwitcher?: boolean\n}\n\nfunction Calendar({\n className,\n classNames,\n showOutsideDays = true,\n yearRange = 12,\n showYearSwitcher = false,\n numberOfMonths,\n ...props\n}: CalendarProps) {\n const [navView, setNavView] = React.useState<\"days\" | \"years\">(\"days\")\n const [displayYears, setDisplayYears] = React.useState<{\n from: number\n to: number\n }>(\n React.useMemo(() => {\n const currentYear = new Date().getFullYear()\n return {\n from: currentYear - Math.floor(yearRange / 2 - 1),\n to: currentYear + Math.ceil(yearRange / 2),\n }\n }, [yearRange])\n )\n\n const { onNextClick, onPrevClick, startMonth, endMonth } = props\n\n const columnsDisplayed = navView === \"years\" ? 1 : numberOfMonths\n\n return (\n <DayPicker\n showOutsideDays={showOutsideDays}\n className={cn(\"p-3\", className)}\n style={{\n width: 248.8 * (columnsDisplayed ?? 1) + \"px\",\n }}\n classNames={{\n months: \"relative flex flex-col gap-y-4 sm:flex-row sm:gap-y-0\",\n month_caption: \"relative mx-10 flex h-7 items-center justify-center\",\n weekdays: \"flex flex-row\",\n weekday: \"w-8 text-[0.8rem] font-normal text-muted-foreground\",\n month: \"w-full gap-y-4 overflow-x-hidden\",\n caption: \"relative flex items-center justify-center pt-1\",\n caption_label: \"truncate text-sm font-medium\",\n button_next: cn(\n buttonVariants({\n variant: \"outline\",\n className:\n \"absolute right-0 h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100\",\n })\n ),\n button_previous: cn(\n buttonVariants({\n variant: \"outline\",\n className:\n \"absolute left-0 h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100\",\n })\n ),\n nav: \"flex items-start\",\n month_grid: \"mt-4\",\n week: \"mt-2 flex w-full\",\n day: \"flex size-8 flex-1 items-center justify-center rounded-md p-0 text-sm [&:has(button)]:hover:!bg-accent [&:has(button)]:hover:text-accent-foreground [&:has(button)]:hover:aria-selected:!bg-primary [&:has(button)]:hover:aria-selected:text-primary-foreground\",\n day_button: cn(\n buttonVariants({ variant: \"ghost\" }),\n \"h-8 w-8 p-0 font-normal transition-none hover:bg-transparent hover:text-inherit aria-selected:opacity-100\"\n ),\n range_start: \"day-range-start rounded-s-md\",\n range_end: \"day-range-end rounded-e-md\",\n selected:\n \"bg-primary text-primary-foreground hover:!bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground\",\n today: \"bg-accent text-accent-foreground\",\n outside:\n \"day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30\",\n disabled: \"text-muted-foreground opacity-50\",\n range_middle:\n \"rounded-none aria-selected:bg-accent aria-selected:text-accent-foreground hover:aria-selected:!bg-accent hover:aria-selected:text-accent-foreground\",\n hidden: \"invisible hidden\",\n ...classNames,\n }}\n components={{\n Chevron: ({ orientation }) => {\n const Icon = orientation === \"left\" ? ChevronLeft : ChevronRight\n return <Icon className=\"h-4 w-4\" />\n },\n Nav: ({ className, children, ...props }) => {\n const { nextMonth, previousMonth, goToMonth } = useDayPicker()\n\n const isPreviousDisabled = (() => {\n if (navView === \"years\") {\n return (\n (startMonth &&\n differenceInCalendarDays(\n new Date(displayYears.from - 1, 0, 1),\n startMonth\n ) < 0) ||\n (endMonth &&\n differenceInCalendarDays(\n new Date(displayYears.from - 1, 0, 1),\n endMonth\n ) > 0)\n )\n }\n return !previousMonth\n })()\n\n const isNextDisabled = (() => {\n if (navView === \"years\") {\n return (\n (startMonth &&\n differenceInCalendarDays(\n new Date(displayYears.to + 1, 0, 1),\n startMonth\n ) < 0) ||\n (endMonth &&\n differenceInCalendarDays(\n new Date(displayYears.to + 1, 0, 1),\n endMonth\n ) > 0)\n )\n }\n return !nextMonth\n })()\n\n const handlePreviousClick = React.useCallback(() => {\n if (!previousMonth) return\n if (navView === \"years\") {\n setDisplayYears((prev) => ({\n from: prev.from - (prev.to - prev.from + 1),\n to: prev.to - (prev.to - prev.from + 1),\n }))\n onPrevClick?.(\n new Date(\n displayYears.from - (displayYears.to - displayYears.from),\n 0,\n 1\n )\n )\n return\n }\n goToMonth(previousMonth)\n onPrevClick?.(previousMonth)\n }, [previousMonth, goToMonth])\n\n const handleNextClick = React.useCallback(() => {\n if (!nextMonth) return\n if (navView === \"years\") {\n setDisplayYears((prev) => ({\n from: prev.from + (prev.to - prev.from + 1),\n to: prev.to + (prev.to - prev.from + 1),\n }))\n onNextClick?.(\n new Date(\n displayYears.from + (displayYears.to - displayYears.from),\n 0,\n 1\n )\n )\n return\n }\n goToMonth(nextMonth)\n onNextClick?.(nextMonth)\n }, [goToMonth, nextMonth])\n return (\n <nav className={cn(\"flex items-center\", className)} {...props}>\n <Button\n variant=\"outline\"\n className=\"absolute left-0 h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100\"\n type=\"button\"\n tabIndex={isPreviousDisabled ? undefined : -1}\n disabled={isPreviousDisabled}\n aria-label={\n navView === \"years\"\n ? `Go to the previous ${\n displayYears.to - displayYears.from + 1\n } years`\n : labelPrevious(previousMonth)\n }\n onClick={handlePreviousClick}\n >\n <ChevronLeft className=\"h-4 w-4\" />\n </Button>\n\n <Button\n variant=\"outline\"\n className=\"absolute right-0 h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100\"\n type=\"button\"\n tabIndex={isNextDisabled ? undefined : -1}\n disabled={isNextDisabled}\n aria-label={\n navView === \"years\"\n ? `Go to the next ${\n displayYears.to - displayYears.from + 1\n } years`\n : labelNext(nextMonth)\n }\n onClick={handleNextClick}\n >\n <ChevronRight className=\"h-4 w-4\" />\n </Button>\n </nav>\n )\n },\n CaptionLabel: ({ children, ...props }) => {\n if (!showYearSwitcher) return <span {...props}>{children}</span>\n\n return (\n <Button\n className=\"h-7 w-full truncate text-sm font-medium\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() =>\n setNavView((prev) => (prev === \"days\" ? \"years\" : \"days\"))\n }\n >\n {navView === \"days\"\n ? children\n : displayYears.from + \" - \" + displayYears.to}\n </Button>\n )\n },\n MonthGrid: ({ className, children, ...props }) => {\n const { goToMonth } = useDayPicker()\n if (navView === \"years\") {\n return (\n <div\n className={cn(\"grid grid-cols-4 gap-y-2\", className)}\n {...props}\n >\n {Array.from(\n { length: displayYears.to - displayYears.from + 1 },\n (_, i) => {\n const isBefore =\n differenceInCalendarDays(\n new Date(displayYears.from + i, 12, 31),\n startMonth!\n ) < 0\n\n const isAfter =\n differenceInCalendarDays(\n new Date(displayYears.from + i, 0, 0),\n endMonth!\n ) > 0\n\n const isDisabled = isBefore || isAfter\n return (\n <Button\n key={i}\n className={cn(\n \"h-7 w-full\",\n displayYears.from + i === new Date().getFullYear() &&\n \"bg-accent text-accent-foreground\"\n )}\n variant=\"ghost\"\n onClick={() => {\n setNavView(\"days\")\n goToMonth(\n new Date(\n displayYears.from + i,\n new Date().getMonth()\n )\n )\n }}\n disabled={navView === \"years\" ? isDisabled : undefined}\n >\n {displayYears.from + i}\n </Button>\n )\n }\n )}\n </div>\n )\n }\n return (\n <table className={className} {...props}>\n {children}\n </table>\n )\n },\n }}\n numberOfMonths={\n // we need to override the number of months if we are in years view to 1\n columnsDisplayed\n }\n {...props}\n />\n )\n}\nCalendar.displayName = \"Calendar\"\n\nexport { Calendar }\n"
}
],
"type": "components:ui"
Expand Down
Loading