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(calendar): enhance calendar component with dropdown selector #1680

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
14 changes: 14 additions & 0 deletions apps/www/__registry__/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,13 @@ export const Index: Record<string, any> = {
component: React.lazy(() => import("@/registry/default/example/calendar-demo")),
files: ["registry/default/example/calendar-demo.tsx"],
},
"calendar-dropdown-button": {
name: "calendar-dropdown-button",
type: "components:example",
registryDependencies: ["calendar"],
component: React.lazy(() => import("@/registry/default/example/calendar-dropdown-button")),
files: ["registry/default/example/calendar-dropdown-button.tsx"],
},
"calendar-form": {
name: "calendar-form",
type: "components:example",
Expand Down Expand Up @@ -1427,6 +1434,13 @@ export const Index: Record<string, any> = {
component: React.lazy(() => import("@/registry/new-york/example/calendar-demo")),
files: ["registry/new-york/example/calendar-demo.tsx"],
},
"calendar-dropdown-button": {
name: "calendar-dropdown-button",
type: "components:example",
registryDependencies: ["calendar"],
component: React.lazy(() => import("@/registry/new-york/example/calendar-dropdown-button")),
files: ["registry/new-york/example/calendar-dropdown-button.tsx"],
},
"calendar-form": {
name: "calendar-form",
type: "components:example",
Expand Down
11 changes: 8 additions & 3 deletions apps/www/content/docs/components/calendar.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ npx shadcn-ui@latest add calendar
npm install react-day-picker date-fns
```

<Step>Add the `Button` component to your project.</Step>
<Step>Add the `Button` and `Select` components to your project.</Step>

The `Calendar` component uses the `Button` component. Make sure you have it installed in your project.
The `Calendar` component uses the `Button` and `Select` component. Make sure you have it installed in your project.

<Step>Copy and paste the following code into your project.</Step>

Expand Down Expand Up @@ -71,6 +71,7 @@ return (
)
```


See the [React DayPicker](https://react-day-picker.js.org) documentation for more information.

## Date Picker
Expand All @@ -79,6 +80,10 @@ You can use the `<Calendar>` component to build a date picker. See the [Date Pic

## Examples

### With Dropdown Selector

<ComponentPreview name="calendar-dropdown-button" />

### Form

<ComponentPreview name="calendar-form" />
<ComponentPreview name="calendar-form" />
2 changes: 1 addition & 1 deletion apps/www/content/docs/components/date-picker.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ See the [React DayPicker](https://react-day-picker.js.org) documentation for mor

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

### Date Range Picker
### Date Range Picker With Dropdown Selector

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

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: \"text-center text-sm p-0 relative [&: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_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: \"text-muted-foreground opacity-50\",\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 { ChevronLeftIcon, ChevronRightIcon } from \"@radix-ui/react-icons\"\nimport { DayPicker } from \"react-day-picker\"\nimport { cn } from \"@/lib/utils\"\nimport { buttonVariants } from \"@/registry/default/ui/button\"\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"@/registry/default/ui/select\"\n\nexport type CalendarProps = React.ComponentProps<typeof DayPicker>\n\nfunction Calendar({\n className,\n classNames,\n showOutsideDays = true,\n ...props\n}: CalendarProps & { onChange?: React.ChangeEventHandler<HTMLSelectElement> }) {\n\n const handleCalendarChange = (_value: string | number, _e: React.ChangeEventHandler<HTMLSelectElement>) => {\n const _event = {\n target: {\n value: String(_value)\n },\n } as React.ChangeEvent<HTMLSelectElement>\n _e(_event);\n \n };\n\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_start: \"is-start\",\n caption_between: \"is-between\",\n caption_end: \"is-end\",\n caption: \"flex justify-center pt-1 relative items-center gap-1\",\n caption_label: \"flex h-7 text-sm font-medium justify-center items-center grow [.is-multiple_&]:flex\",\n caption_dropdowns: \"flex justify-center gap-1 grow dropdowns pl-8 pr-9\",\n multiple_months: \"is-multiple\",\n vhidden: \"hidden [.is-between_&]:flex [.is-end_&]:flex [.is-start.is-end_&]:hidden\",\n nav: \"flex items-center [&:has([name='previous-month'])]:order-first [&:has([name='next-month'])]:order-last gap-1\",\n nav_button: cn(\n buttonVariants({ variant: \"outline\" }),\n \"h-7 w-7 bg-transparent p-0 text-muted-foreground\"\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: \"text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]\",\n row: \"flex w-full mt-2\",\n cell: cn(\n \"relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent\",\n props.mode === \"range\"\n ? \"[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md\"\n : \"[&:has([aria-selected])]:rounded-md\"\n ),\n day: cn(\n buttonVariants({ variant: \"ghost\" }),\n \"h-9 w-9 p-0 font-normal aria-selected:opacity-100\"\n ),\n day_range_start: \"day-range-start\",\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: \"text-muted-foreground opacity-50\",\n day_disabled: \"text-muted-foreground opacity-50\",\n day_range_middle: \"aria-selected:bg-accent aria-selected:text-accent-foreground\",\n day_hidden: \"invisible\",\n }}\n components={{\n IconLeft: ({ ...props }) => <ChevronLeftIcon className=\"h-4 w-4\" />,\n IconRight: ({ ...props }) => <ChevronRightIcon className=\"h-4 w-4\" />,\n Dropdown: ({ ...props }) => (\n <Select\n {...props}\n onValueChange={(value) => {\n if (props.onChange) {\n handleCalendarChange(value, props.onChange)\n }\n }}\n value={props.value as string}\n >\n <SelectTrigger className={cn(buttonVariants({ variant: \"ghost\" }),\"pl-2 pr-1 py-2 h-7 w-fit font-medium [.is-between_&]:hidden [.is-end_&]:hidden [.is-start.is-end_&]:flex\")}>\n <SelectValue placeholder={props?.caption} >{props?.caption}</SelectValue>\n </SelectTrigger>\n <SelectContent className=\"max-h-[var(--radix-popper-available-height);] overflow-y-auto scrolling-auto min-w-[var(--radix-popper-anchor-width)]\">\n {props.children &&\n React.Children.map(props.children, (child) =>\n <SelectItem value={(child as React.ReactElement<any>)?.props?.value} className=\"min-w-[var(--radix-popper-anchor-width)]\">{(child as React.ReactElement<any>)?.props?.children}</SelectItem>\n )\n }\n </SelectContent>\n </Select>\n ),\n }}\n {...props}\n />\n )\n}\nCalendar.displayName = \"Calendar\"\n\nexport { Calendar }\n"
}
],
"type": "components:ui"
Expand Down
2 changes: 1 addition & 1 deletion apps/www/public/registry/styles/new-york/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 { ChevronLeftIcon, ChevronRightIcon } from \"@radix-ui/react-icons\"\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-8 font-normal text-[0.8rem]\",\n row: \"flex w-full mt-2\",\n cell: cn(\n \"relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent\",\n props.mode === \"range\"\n ? \"[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md\"\n : \"[&:has([aria-selected])]:rounded-md\"\n ),\n day: cn(\n buttonVariants({ variant: \"ghost\" }),\n \"h-8 w-8 p-0 font-normal aria-selected:opacity-100\"\n ),\n day_range_start: \"day-range-start\",\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: \"text-muted-foreground opacity-50\",\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 }) => <ChevronLeftIcon className=\"h-4 w-4\" />,\n IconRight: ({ ...props }) => <ChevronRightIcon 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 { ChevronLeftIcon, ChevronRightIcon } from \"@radix-ui/react-icons\"\nimport { DayPicker } from \"react-day-picker\"\nimport { cn } from \"@/lib/utils\"\nimport { buttonVariants } from \"@/registry/new-york/ui/button\"\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"@/registry/new-york/ui/select\"\n\nexport type CalendarProps = React.ComponentProps<typeof DayPicker>\n\nfunction Calendar({\n className,\n classNames,\n showOutsideDays = true,\n ...props\n}: CalendarProps & { onChange?: React.ChangeEventHandler<HTMLSelectElement> }) {\n\n const handleCalendarChange = (_value: string | number, _e: React.ChangeEventHandler<HTMLSelectElement>) => {\n const _event = {\n target: {\n value: String(_value)\n },\n } as React.ChangeEvent<HTMLSelectElement>\n _e(_event);\n };\n\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-3\",\n caption_start: \"is-start\",\n caption_between: \"is-between\",\n caption_end: \"is-end\",\n caption: \"flex justify-center pt-1 relative items-center gap-1\",\n caption_label: \"flex h-7 text-sm font-medium justify-center items-center grow [.is-multiple_&]:flex\",\n caption_dropdowns: \"flex justify-center grow dropdowns pl-7 pr-8\",\n multiple_months: \"is-multiple\",\n vhidden: \"hidden [.is-between_&]:flex [.is-end_&]:flex [.is-start.is-end_&]:hidden\",\n nav: \"flex items-center [&:has([name='previous-month'])]:order-first [&:has([name='next-month'])]:order-last\",\n nav_button: cn(\n buttonVariants({ variant: \"outline\" }),\n \"h-6 w-6 bg-transparent p-0 text-muted-foreground\"\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: \"text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]\",\n row: \"flex w-full mt-2\",\n cell: cn(\n \"relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent\",\n props.mode === \"range\"\n ? \"[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md\"\n : \"[&:has([aria-selected])]:rounded-md\"\n ),\n day: cn(\n buttonVariants({ variant: \"ghost\" }),\n \"h-8 w-8 p-0 font-normal aria-selected:opacity-100\"\n ),\n day_range_start: \"day-range-start\",\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: \"text-muted-foreground opacity-50\",\n day_disabled: \"text-muted-foreground opacity-50\",\n day_range_middle: \"aria-selected:bg-accent aria-selected:text-accent-foreground\",\n day_hidden: \"invisible\",\n ...classNames,\n }}\n components={{\n IconLeft: ({ ...props }) => <ChevronLeftIcon className=\"h-4 w-4\" />,\n IconRight: ({ ...props }) => <ChevronRightIcon className=\"h-4 w-4\" />,\n Dropdown: ({ ...props }) => (\n <Select\n {...props}\n onValueChange={(value) => {\n if (props.onChange) {\n handleCalendarChange(value, props.onChange)\n }\n }}\n value={props.value as string}\n >\n <SelectTrigger className={cn(buttonVariants({ variant: \"ghost\" }), \"px-2 py-1 h-7 border-none shadow-none font-medium [.is-between_&]:hidden [.is-end_&]:hidden [.is-start.is-end_&]:flex\")}>\n <SelectValue placeholder={props?.caption}>{props?.caption}</SelectValue>\n </SelectTrigger>\n <SelectContent className=\"max-h-[var(--radix-popper-available-height);] overflow-y-auto scrolling-auto min-w-[var(--radix-popper-anchor-width)]\">\n {props.children &&\n React.Children.map(props.children, (child) =>\n <SelectItem value={(child as React.ReactElement<any>)?.props?.value} className=\"min-w-[var(--radix-popper-anchor-width)] pr-7\">{(child as React.ReactElement<any>)?.props?.children}</SelectItem>\n )\n }\n </SelectContent>\n </Select>\n ),\n }}\n {...props}\n />\n )\n}\nCalendar.displayName = \"Calendar\"\n\nexport { Calendar }\n"
}
],
"type": "components:ui"
Expand Down
22 changes: 22 additions & 0 deletions apps/www/registry/default/example/calendar-dropdown-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"use client"

import * as React from "react"

import { Calendar } from "@/registry/default/ui/calendar"

export default function CalendarDropdown() {
const [date, setDate] = React.useState<Date | undefined>(new Date())

return (
<Calendar
initialFocus
mode="single"
captionLayout="dropdown-buttons"
fromYear={2022}
toYear={2023}
selected={date}
onSelect={setDate}
className="rounded-md border"
/>
)
}
3 changes: 3 additions & 0 deletions apps/www/registry/default/example/calendar-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ export default function CalendarForm() {
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
captionLayout="dropdown-buttons"
fromYear={1900}
toYear={new Date().getFullYear()}
selected={field.value}
onSelect={field.onChange}
disabled={(date) =>
Expand Down
3 changes: 3 additions & 0 deletions apps/www/registry/default/example/date-picker-with-range.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ export default function DatePickerWithRange({
<Calendar
initialFocus
mode="range"
captionLayout="dropdown-buttons"
fromYear={2007}
toYear={2023}
defaultMonth={date?.from}
selected={date}
onSelect={setDate}
Expand Down
Loading
Loading