Skip to content
Merged
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
3 changes: 2 additions & 1 deletion components/retroui/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { cva, VariantProps } from "class-variance-authority";
import React, { ButtonHTMLAttributes } from "react";
import { Slot } from "@radix-ui/react-slot";

const buttonVariants = cva(
export const buttonVariants = cva(
"font-head transition-all rounded outline-hidden cursor-pointer duration-200 font-medium flex items-center",
{
variants: {
Expand All @@ -15,6 +15,7 @@ const buttonVariants = cva(
outline:
"shadow-md hover:shadow active:shadow-none bg-transparent border-2 transition hover:translate-y-1 active:translate-y-2 active:translate-x-1",
link: "bg-transparent hover:underline",
ghost: "bg-transparent hover:bg-accent"
},
size: {
sm: "px-3 py-1 text-sm shadow hover:shadow-none",
Expand Down
210 changes: 210 additions & 0 deletions components/retroui/Calendar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
"use client"

import * as React from "react"
import {
ChevronDownIcon,
ChevronLeftIcon,
ChevronRightIcon,
} from "lucide-react"
import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker"

import { cn } from "@/lib/utils"
import { Button, buttonVariants } from "@/components/retroui/Button"

function Calendar({
className,
classNames,
showOutsideDays = true,
captionLayout = "label",
buttonVariant = "ghost",
formatters,
components,
...props
}: React.ComponentProps<typeof DayPicker> & {
buttonVariant?: React.ComponentProps<typeof Button>["variant"]
}) {
const defaultClassNames = getDefaultClassNames()

return (
<DayPicker
showOutsideDays={showOutsideDays}
className={cn(
"bg-background w-full outline-2 shadow-md group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
className
)}
captionLayout={captionLayout}
formatters={{
formatMonthDropdown: (date) =>
date.toLocaleString("default", { month: "short" }),
...formatters,
}}
classNames={{
root: cn("w-fit", defaultClassNames.root),
months: cn(
"flex gap-4 flex-col md:flex-row relative",
defaultClassNames.months
),
month: cn("flex flex-col w-full gap-4 font-head", defaultClassNames.month),
nav: cn(
"flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between",
defaultClassNames.nav
),
button_previous: cn(
buttonVariants({ variant: buttonVariant }),
"size-8 p-2 border-2 rounded select-none",
defaultClassNames.button_previous
),
button_next: cn(
buttonVariants({ variant: buttonVariant }),
"size-8 p-2 border-2 rounded select-none",
defaultClassNames.button_next
),
month_caption: cn(
"flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)",
defaultClassNames.month_caption
),
dropdowns: cn(
"w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5",
defaultClassNames.dropdowns
),
dropdown_root: cn(
"relative has-focus:outline-ring outline outline-input has-focus:ring-ring/50 has-focus:ring-[3px] rounded",
defaultClassNames.dropdown_root
),
dropdown: cn(
"absolute bg-popover inset-0 opacity-0",
defaultClassNames.dropdown
),
caption_label: cn(
"select-none font-medium",
captionLayout === "label"
? "text-base"
: "rounded-none pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5",
defaultClassNames.caption_label
),
table: "w-full outline-collapse",
weekdays: cn("flex", defaultClassNames.weekdays),
weekday: cn(
"flex-1 font-normal text-sm select-none",
defaultClassNames.weekday
),
week: cn("flex w-full mt-2", defaultClassNames.week),
week_number_header: cn(
"select-none w-(--cell-size)",
defaultClassNames.week_number_header
),
week_number: cn(
"text-[0.8rem] select-none text-muted-foreground",
defaultClassNames.week_number
),
day: cn(
"relative w-full h-full p-0 text-center [&:last-child[data-selected=true]_button]:rounded-r group/day aspect-square select-none",
props.showWeekNumber
? "[&:nth-child(2)[data-selected=true]_button]:rounded-l"
: "[&:first-child[data-selected=true]_button]:rounded-l",
defaultClassNames.day
),
today: cn(
"bg-accent text-accent-foreground rounded data-[selected=true]:rounded-none",
defaultClassNames.today
),
outside: cn(
"text-muted-foreground aria-selected:text-muted-foreground opacity-80",
defaultClassNames.outside
),
disabled: cn(
"text-muted-foreground opacity-50",
defaultClassNames.disabled
),
hidden: cn("invisible", defaultClassNames.hidden),
...classNames,
}}
components={{
Root: ({ className, rootRef, ...props }) => {
return (
<div
data-slot="calendar"
ref={rootRef}
className={cn(className)}
{...props}
/>
)
},
Chevron: ({ className, orientation, ...props }) => {
if (orientation === "left") {
return (
<ChevronLeftIcon className={cn("size-4", className)} {...props} />
)
}

if (orientation === "right") {
return (
<ChevronRightIcon
className={cn("size-4", className)}
{...props}
/>
)
}

return (
<ChevronDownIcon className={cn("size-4", className)} {...props} />
)
},
DayButton: CalendarDayButton,
WeekNumber: ({ children, ...props }) => {
return (
<td {...props}>
<div className="flex size-(--cell-size) items-center justify-center text-center">
{children}
</div>
</td>
)
},
...components,
}}
{...props}
/>
)
}

function CalendarDayButton({
className,
day,
modifiers,
...props
}: React.ComponentProps<typeof DayButton>) {
const defaultClassNames = getDefaultClassNames()

const ref = React.useRef<HTMLButtonElement>(null)
React.useEffect(() => {
if (modifiers.focused) ref.current?.focus()
}, [modifiers.focused])

return (
<Button
ref={ref}
variant="ghost"
size="icon"
data-day={day.date.toLocaleDateString()}
data-selected-single={
modifiers.selected &&
!modifiers.range_start &&
!modifiers.range_end &&
!modifiers.range_middle
}
data-range-start={modifiers.range_start}
data-range-end={modifiers.range_end}
data-range-middle={modifiers.range_middle}
className={cn(
"font-sans flex justify-center items-center data-[selected-single=true]:shadow-md data-[selected-single=true]:outline-2 outline-border data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-secondary data-[range-middle=true]:hover:text-secondary-foreground data-[range-middle=true]:text-secondary-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring-1 group-data-[focused=true]/day:ring-ring/50 dark:hover:text-accent-foreground flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[2px] data-[range-end=true]:rounded-none data-[range-end=true]:rounded-none data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-none [&>span]:text-xs",
defaultClassNames.day,
className
)}
{...props}
/>
)
}

export { Calendar, CalendarDayButton }
5 changes: 5 additions & 0 deletions config/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,11 @@ export const componentConfig: {
() => import("@/preview/components/button-style-with-icon"),
),
},
"calendar-style-default": {
name: "calendar-style-default",
filePath: "preview/components/calendar-style-default.tsx",
preview: lazy(() => import("@/preview/components/calendar-style-default")),
},
"card-style-default": {
name: "card-style-default",
filePath: "preview/components/card-style-default.tsx",
Expand Down
11 changes: 6 additions & 5 deletions config/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@ export const navConfig: INavigationConfig = {
{ title: "Avatar", href: `${componentsRoute}/avatar` },
{ title: "Badge", href: `${componentsRoute}/badge` },
{ title: "Breadcrumb", href: `${componentsRoute}/breadcrumb` },
{ title: "Button", href: `${componentsRoute}/button`, tag: "Updated" },
{ title: "Button", href: `${componentsRoute}/button` },
{ title: "Card", href: `${componentsRoute}/card` },
{ title: "Calendar", href: `${componentsRoute}/calendar`, tag: "New" },
{ title: "Checkbox", href: `${componentsRoute}/checkbox` },
{ title: "Command", href: `${componentsRoute}/command` },
{ title: "Dialog", href: `${componentsRoute}/dialog` },
Expand Down Expand Up @@ -93,10 +94,10 @@ export const navConfig: INavigationConfig = {
{
title: "Chart",
children: [
{ title: "Bar Chart", href: `${chartsRoute}/bar-chart`, tag: "New" },
{ title: "Line Chart", href: `${chartsRoute}/line-chart`, tag: "New" },
{ title: "Area Chart", href: `${chartsRoute}/area-chart`, tag: "New" },
{ title: "Pie Chart", href: `${chartsRoute}/pie-chart`, tag: "New" },
{ title: "Bar Chart", href: `${chartsRoute}/bar-chart` },
{ title: "Line Chart", href: `${chartsRoute}/line-chart` },
{ title: "Area Chart", href: `${chartsRoute}/area-chart` },
{ title: "Pie Chart", href: `${chartsRoute}/pie-chart` },
],
},
{
Expand Down
39 changes: 39 additions & 0 deletions content/docs/components/calendar.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
title: Calendar
description: Let your users select a date to cancel subscription.
lastUpdated: 14 Nov, 2025
---

<ComponentShowcase name="calendar-style-default" />
<br />
<br />

## Installation

<ComponentInstall>
<ComponentInstall.Cli npmCommand="npx shadcn@latest add @retroui/calendar" />
<ComponentInstall.Manual>

#### 1. Install dependencies:

```sh
npm install react-day-picker lucide-react
```

<br />

#### 2. Copy the code 👇 into your project:

<ComponentSource name="calendar" />

</ComponentInstall.Manual>
</ComponentInstall>

<br />
<br />

## Examples

### Default

<ComponentShowcase name="calendar-style-default" />
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"next": "14.2.7",
"next-contentlayer": "^0.3.4",
"react": "^18",
"react-day-picker": "^9.11.1",
"react-dom": "^18",
"recharts": "^3.1.2",
"rehype-pretty-code": "^0.14.0",
Expand Down
26 changes: 26 additions & 0 deletions pnpm-lock.yaml

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

Loading