Skip to content

Commit 1bccf02

Browse files
authored
feat(calendar) add month and year selector calendar (#224)
1 parent 2682d1d commit 1bccf02

File tree

4 files changed

+204
-4
lines changed

4 files changed

+204
-4
lines changed

app/docs/components/calendar/page.tsx

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { calendarMetaData } from "@/lib/metadata";
55
import { Separator } from "@/components/ui/separator";
66

77
import { CalendarExample } from "@/components/examples/calendar";
8+
import { Calendar13 } from "@/components/examples/calendars/calendar-13";
89
import { RangeCalendar } from "@/components/examples/range-calendar";
910

1011
import CodeSnippet from "../code-snippet";
@@ -108,6 +109,80 @@ export function RangeCalendar() {
108109
/>
109110
);
110111
}
112+
`}</CodeSnippet>
113+
114+
<h3 className="text-lg font-bold mt-10">Month and Year Selector</h3>
115+
116+
<Separator />
117+
118+
<div className="flex flex-col gap-4 border rounded-lg p-4 min-h-[450px] relative">
119+
<div className="flex items-center justify-between">
120+
<h2 className="text-sm text-muted-foreground sm:pl-3">
121+
8-bit calendar component
122+
</h2>
123+
</div>
124+
<div className="flex items-center justify-center min-h-[400px] max-w-md mx-auto relative">
125+
<Calendar13 />
126+
</div>
127+
</div>
128+
129+
<CodeSnippet>{`"use client";
130+
131+
import * as React from "react";
132+
133+
import { Calendar } from "@/components/ui/8bit/calendar";
134+
import { Label } from "@/components/ui/8bit/label";
135+
import {
136+
Select,
137+
SelectContent,
138+
SelectItem,
139+
SelectTrigger,
140+
SelectValue,
141+
} from "@/components/ui/8bit/select";
142+
143+
export function Calendar13() {
144+
const [dropdown, setDropdown] =
145+
React.useState<React.ComponentProps<typeof Calendar>["captionLayout"]>(
146+
"dropdown"
147+
);
148+
const [date, setDate] = React.useState<Date | undefined>(
149+
new Date(2025, 5, 12)
150+
);
151+
152+
return (
153+
<div className="flex flex-col gap-4">
154+
<Calendar
155+
mode="single"
156+
defaultMonth={date}
157+
selected={date}
158+
onSelect={setDate}
159+
captionLayout={dropdown}
160+
/>
161+
<div className="flex flex-col gap-3">
162+
<Label htmlFor="dropdown" className="px-1">
163+
Dropdown
164+
</Label>
165+
<Select
166+
value={dropdown}
167+
onValueChange={(value) =>
168+
setDropdown(
169+
value as React.ComponentProps<typeof Calendar>["captionLayout"]
170+
)
171+
}
172+
>
173+
<SelectTrigger id="dropdown" className="bg-background w-full">
174+
<SelectValue placeholder="Dropdown" />
175+
</SelectTrigger>
176+
<SelectContent align="center">
177+
<SelectItem value="dropdown">Month and Year</SelectItem>
178+
<SelectItem value="dropdown-months">Month Only</SelectItem>
179+
<SelectItem value="dropdown-years">Year Only</SelectItem>
180+
</SelectContent>
181+
</Select>
182+
</div>
183+
</div>
184+
);
185+
}
111186
`}</CodeSnippet>
112187
</div>
113188
);
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"use client";
2+
3+
import * as React from "react";
4+
5+
import { Calendar } from "@/components/ui/8bit/calendar";
6+
import { Label } from "@/components/ui/8bit/label";
7+
import {
8+
Select,
9+
SelectContent,
10+
SelectItem,
11+
SelectTrigger,
12+
SelectValue,
13+
} from "@/components/ui/8bit/select";
14+
15+
export function Calendar13() {
16+
const [dropdown, setDropdown] =
17+
React.useState<React.ComponentProps<typeof Calendar>["captionLayout"]>(
18+
"dropdown"
19+
);
20+
const [date, setDate] = React.useState<Date | undefined>(
21+
new Date(2025, 5, 12)
22+
);
23+
24+
return (
25+
<div className="flex flex-col gap-4">
26+
<Calendar
27+
mode="single"
28+
defaultMonth={date}
29+
selected={date}
30+
onSelect={setDate}
31+
captionLayout={dropdown}
32+
/>
33+
<div className="flex flex-col gap-3">
34+
<Label htmlFor="dropdown" className="px-1">
35+
Dropdown
36+
</Label>
37+
<Select
38+
value={dropdown}
39+
onValueChange={(value) =>
40+
setDropdown(
41+
value as React.ComponentProps<typeof Calendar>["captionLayout"]
42+
)
43+
}
44+
>
45+
<SelectTrigger id="dropdown" className="bg-background w-full">
46+
<SelectValue placeholder="Dropdown" />
47+
</SelectTrigger>
48+
<SelectContent align="center">
49+
<SelectItem value="dropdown">Month and Year</SelectItem>
50+
<SelectItem value="dropdown-months">Month Only</SelectItem>
51+
<SelectItem value="dropdown-years">Year Only</SelectItem>
52+
</SelectContent>
53+
</Select>
54+
</div>
55+
</div>
56+
);
57+
}

components/ui/8bit/calendar.tsx

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ import { cn } from "@/lib/utils";
66
import { Calendar as ShadcnCalendar } from "@/components/ui/calendar";
77

88
import { buttonVariants } from "./button";
9+
import {
10+
Select,
11+
SelectContent,
12+
SelectItem,
13+
SelectTrigger,
14+
SelectValue,
15+
} from "./select";
916
import "./styles/retro.css";
1017

1118
export const calendarVariants = cva("", {
@@ -27,7 +34,7 @@ function Calendar({ className, classNames, font, ...props }: CalendarProps) {
2734
return (
2835
<div
2936
className={cn(
30-
"flex bg-popover justify-center relative border-y-6 border-foreground dark:border-ring !p-0",
37+
"bg-popover relative border-y-6 border-foreground dark:border-ring w-max",
3138
className
3239
)}
3340
>
@@ -43,14 +50,75 @@ function Calendar({ className, classNames, font, ...props }: CalendarProps) {
4350
buttonVariants({ variant: "outline" }),
4451
"size-7 bg-transparent p-0 flex items-center justify-center hover:opacity-50 border-2 border-foreground dark:border-ring"
4552
),
46-
day: cn(
53+
day_button: cn(
4754
buttonVariants({ variant: "ghost" }),
48-
"size-9 p-0 font-normal aria-selected:opacity-100"
55+
"h-10 p-2 font-normal aria-selected:opacity-100"
4956
),
5057
caption_label: "text-xs font-medium",
5158
...classNames,
5259
}}
5360
components={{
61+
MonthsDropdown: ({ className, ...props }) => {
62+
const currentMonth = props.value as number;
63+
64+
const months = [
65+
"January",
66+
"February",
67+
"March",
68+
"April",
69+
"May",
70+
"June",
71+
"July",
72+
"August",
73+
"September",
74+
"October",
75+
"November",
76+
"December",
77+
];
78+
79+
const currentMonthName = months[currentMonth];
80+
81+
return (
82+
<div className={cn("flex flex-col gap-3 text-xs", className)}>
83+
<Select defaultValue={currentMonthName}>
84+
<SelectTrigger id="dropdown" className="bg-background w-full">
85+
<SelectValue placeholder="Dropdown" />
86+
</SelectTrigger>
87+
<SelectContent align="center">
88+
{months.map((month) => (
89+
<SelectItem key={month} value={month}>
90+
{month}
91+
</SelectItem>
92+
))}
93+
</SelectContent>
94+
</Select>
95+
</div>
96+
);
97+
},
98+
YearsDropdown: ({ className }) => {
99+
const currentYear = new Date().getFullYear();
100+
const years = Array.from(
101+
{ length: currentYear - 1925 + 1 },
102+
(_, i) => 1925 + i
103+
);
104+
105+
return (
106+
<div className={cn("flex flex-col gap-3 text-xs", className)}>
107+
<Select defaultValue={currentYear?.toString()}>
108+
<SelectTrigger id="dropdown" className="bg-background w-full">
109+
<SelectValue placeholder="Dropdown" />
110+
</SelectTrigger>
111+
<SelectContent align="center">
112+
{years.map((year) => (
113+
<SelectItem key={year} value={year.toString()}>
114+
{year}
115+
</SelectItem>
116+
))}
117+
</SelectContent>
118+
</Select>
119+
</div>
120+
);
121+
},
54122
Chevron: ({ className, ...props }) => {
55123
if (props.orientation === "left") {
56124
return (

0 commit comments

Comments
 (0)