Skip to content

Commit 83ddac6

Browse files
committed
feat: add fullscreen calendar component
1 parent f07c7ad commit 83ddac6

File tree

18 files changed

+1337
-0
lines changed

18 files changed

+1337
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { FullScreenCalendar } from "@/registry/default/blocks/fullscreen-calendar/components/fullscreen-calendar"
2+
3+
export default function Page() {
4+
return (
5+
<div className="flex h-screen flex-1 flex-col">
6+
<FullScreenCalendar />
7+
</div>
8+
)
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { FullScreenCalendar } from "@/registry/new-york/blocks/fullscreen-calendar/components/fullscreen-calendar"
2+
3+
export default function Page() {
4+
return (
5+
<div className="flex h-screen flex-1 flex-col">
6+
<FullScreenCalendar />
7+
</div>
8+
)
9+
}

Diff for: apps/www/public/r/styles/default/fullscreen-calendar.json

+50
Large diffs are not rendered by default.
Loading
Loading

Diff for: apps/www/public/r/styles/new-york/fullscreen-calendar.json

+50
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
"use client"
2+
3+
import React from "react"
4+
import {
5+
format,
6+
getDay,
7+
isEqual,
8+
isSameDay,
9+
isSameMonth,
10+
isToday,
11+
} from "date-fns"
12+
13+
import { cn } from "@/lib/utils"
14+
import { useMediaQuery } from "@/hooks/use-media-query"
15+
16+
interface FullscreenCalendarDayProps {
17+
day: Date
18+
selectedDay: Date
19+
setSelectedDay: React.Dispatch<React.SetStateAction<Date>>
20+
firstDayCurrentMonth: Date
21+
dayIndex: number
22+
data: {
23+
day: Date
24+
events: {
25+
id: number
26+
name: string
27+
time: string
28+
datetime: string
29+
}[]
30+
}[]
31+
}
32+
33+
const colStartClasses = [
34+
"",
35+
"col-start-2",
36+
"col-start-3",
37+
"col-start-4",
38+
"col-start-5",
39+
"col-start-6",
40+
"col-start-7",
41+
]
42+
43+
export function FullScreenCalendarDay({
44+
day,
45+
selectedDay,
46+
setSelectedDay,
47+
firstDayCurrentMonth,
48+
dayIndex,
49+
data,
50+
}: FullscreenCalendarDayProps) {
51+
const isDesktop = useMediaQuery("(min-width: 768px)")
52+
53+
if (!isDesktop) {
54+
return (
55+
<button
56+
onClick={() => setSelectedDay(day)}
57+
key={dayIndex}
58+
type="button"
59+
className={cn(
60+
isEqual(day, selectedDay) && "text-primary-foreground",
61+
!isEqual(day, selectedDay) &&
62+
!isToday(day) &&
63+
isSameMonth(day, firstDayCurrentMonth) &&
64+
"text-foreground",
65+
!isEqual(day, selectedDay) &&
66+
!isToday(day) &&
67+
!isSameMonth(day, firstDayCurrentMonth) &&
68+
"text-muted-foreground",
69+
(isEqual(day, selectedDay) || isToday(day)) && "font-semibold",
70+
"flex h-14 flex-col border-b border-r px-3 py-2 hover:bg-muted focus:z-10"
71+
)}
72+
>
73+
<time
74+
dateTime={format(day, "yyyy-MM-dd")}
75+
className={cn(
76+
"ml-auto flex size-6 items-center justify-center rounded-full",
77+
isEqual(day, selectedDay) &&
78+
isToday(day) &&
79+
"bg-primary text-primary-foreground",
80+
isEqual(day, selectedDay) &&
81+
!isToday(day) &&
82+
"bg-primary text-primary-foreground"
83+
)}
84+
>
85+
{format(day, "d")}
86+
</time>
87+
{data.filter((date) => isSameDay(date.day, day)).length > 0 && (
88+
<div>
89+
{data
90+
.filter((date) => isSameDay(date.day, day))
91+
.map((date) => (
92+
<div
93+
key={date.day.toString()}
94+
className="-mx-0.5 mt-auto flex flex-wrap-reverse"
95+
>
96+
{date.events.map((event) => (
97+
<span
98+
key={event.id}
99+
className="mx-0.5 mt-1 h-1.5 w-1.5 rounded-full bg-muted-foreground"
100+
/>
101+
))}
102+
</div>
103+
))}
104+
</div>
105+
)}
106+
</button>
107+
)
108+
}
109+
110+
return (
111+
<div
112+
key={dayIndex}
113+
onClick={() => setSelectedDay(day)}
114+
className={cn(
115+
dayIndex === 0 && colStartClasses[getDay(day)],
116+
!isEqual(day, selectedDay) &&
117+
!isToday(day) &&
118+
!isSameMonth(day, firstDayCurrentMonth) &&
119+
"bg-accent/50 text-muted-foreground",
120+
"relative flex flex-col border-b border-r hover:bg-muted focus:z-10",
121+
!isEqual(day, selectedDay) && "hover:bg-accent/75"
122+
)}
123+
>
124+
<header className="flex items-center justify-between p-2.5">
125+
<button
126+
type="button"
127+
className={cn(
128+
isEqual(day, selectedDay) && "text-primary-foreground",
129+
!isEqual(day, selectedDay) &&
130+
!isToday(day) &&
131+
isSameMonth(day, firstDayCurrentMonth) &&
132+
"text-foreground",
133+
!isEqual(day, selectedDay) &&
134+
!isToday(day) &&
135+
!isSameMonth(day, firstDayCurrentMonth) &&
136+
"text-muted-foreground",
137+
isEqual(day, selectedDay) &&
138+
isToday(day) &&
139+
"border-none bg-primary",
140+
isEqual(day, selectedDay) && !isToday(day) && "bg-foreground",
141+
(isEqual(day, selectedDay) || isToday(day)) && "font-semibold",
142+
"flex h-7 w-7 items-center justify-center rounded-full text-xs hover:border"
143+
)}
144+
>
145+
<time dateTime={format(day, "yyyy-MM-dd")}>{format(day, "d")}</time>
146+
</button>
147+
</header>
148+
<div className="flex-1 p-2.5">
149+
{data
150+
.filter((event) => isSameDay(event.day, day))
151+
.map((day) => (
152+
<div key={day.day.toString()} className="space-y-1.5">
153+
{day.events.slice(0, 1).map((event) => (
154+
<div
155+
key={event.id}
156+
className="flex flex-col items-start gap-1 rounded-lg border bg-muted/50 p-2 text-xs leading-tight"
157+
>
158+
<p className="font-medium leading-none">{event.name}</p>
159+
<p className="leading-none text-muted-foreground">
160+
{event.time}
161+
</p>
162+
</div>
163+
))}
164+
{day.events.length > 1 && (
165+
<div className="text-xs text-muted-foreground">
166+
+ {day.events.length - 1} more
167+
</div>
168+
)}
169+
</div>
170+
))}
171+
</div>
172+
</div>
173+
)
174+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
"use client"
2+
3+
import React from "react"
4+
import { format, startOfToday } from "date-fns"
5+
import {
6+
ChevronLeftIcon,
7+
ChevronRightIcon,
8+
PlusCircleIcon,
9+
SearchIcon,
10+
} from "lucide-react"
11+
12+
import { Button } from "@/registry/default/ui/button"
13+
import { Separator } from "@/registry/default/ui/separator"
14+
15+
interface FullScreenCalendarHeaderProps {
16+
startOfMonth: Date
17+
endOfMonth: Date
18+
nextMonth: () => void
19+
goToToday: () => void
20+
previousMonth: () => void
21+
}
22+
23+
export function FullScreenCalendarHeader({
24+
nextMonth,
25+
goToToday,
26+
previousMonth,
27+
startOfMonth,
28+
endOfMonth,
29+
}: FullScreenCalendarHeaderProps) {
30+
const today = startOfToday()
31+
32+
return (
33+
<div className="flex flex-col space-y-4 p-4 md:flex-row md:items-center md:justify-between md:space-y-0 lg:flex-none">
34+
<div className="flex flex-auto">
35+
<div className="flex items-center gap-4">
36+
<div className="hidden w-20 flex-col items-center justify-center rounded-lg border bg-muted p-0.5 md:flex">
37+
<h1 className="p-1 text-xs uppercase text-muted-foreground">
38+
{format(today, "MMM")}
39+
</h1>
40+
<div className="flex w-full items-center justify-center rounded-lg border bg-background p-0.5 text-lg font-bold">
41+
<span>{format(today, "d")}</span>
42+
</div>
43+
</div>
44+
<div className="flex flex-col">
45+
<h2 className="text-lg font-semibold text-foreground">
46+
{format(startOfMonth, "MMMM, yyyy")}
47+
</h2>
48+
<p className="text-sm text-muted-foreground">
49+
{format(startOfMonth, "MMM d, yyyy")} -{" "}
50+
{format(endOfMonth, "MMM d, yyyy")}
51+
</p>
52+
</div>
53+
</div>
54+
</div>
55+
56+
<div className="flex flex-col items-center gap-4 md:flex-row md:gap-6">
57+
<Button variant="outline" size="icon" className="hidden lg:flex">
58+
<SearchIcon size={16} strokeWidth={2} aria-hidden="true" />
59+
</Button>
60+
61+
<Separator orientation="vertical" className="hidden h-6 lg:block" />
62+
63+
<div className="inline-flex w-full -space-x-px rounded-lg shadow-sm shadow-black/5 md:w-auto rtl:space-x-reverse">
64+
<Button
65+
onClick={previousMonth}
66+
className="rounded-none shadow-none first:rounded-s-lg last:rounded-e-lg focus-visible:z-10"
67+
variant="outline"
68+
size="icon"
69+
aria-label="Navigate to previous month"
70+
>
71+
<ChevronLeftIcon size={16} strokeWidth={2} aria-hidden="true" />
72+
</Button>
73+
<Button
74+
onClick={goToToday}
75+
className="w-full rounded-none shadow-none first:rounded-s-lg last:rounded-e-lg focus-visible:z-10 md:w-auto"
76+
variant="outline"
77+
>
78+
Today
79+
</Button>
80+
<Button
81+
onClick={nextMonth}
82+
className="rounded-none shadow-none first:rounded-s-lg last:rounded-e-lg focus-visible:z-10"
83+
variant="outline"
84+
size="icon"
85+
aria-label="Navigate to next month"
86+
>
87+
<ChevronRightIcon size={16} strokeWidth={2} aria-hidden="true" />
88+
</Button>
89+
</div>
90+
91+
<Separator orientation="vertical" className="hidden h-6 md:block" />
92+
93+
<Separator
94+
orientation="horizontal"
95+
className="block w-full md:hidden"
96+
/>
97+
98+
<Button className="w-full gap-2 md:w-auto">
99+
<PlusCircleIcon size={16} strokeWidth={2} aria-hidden="true" />
100+
<span>New Event</span>
101+
</Button>
102+
</div>
103+
</div>
104+
)
105+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React from "react"
2+
3+
export function FullScreenCalendarWeekDays() {
4+
return (
5+
<div className="grid grid-cols-7 border text-center text-xs font-semibold leading-6 lg:flex-none">
6+
<div className="border-r py-2.5">Sun</div>
7+
<div className="border-r py-2.5">Mon</div>
8+
<div className="border-r py-2.5">Tue</div>
9+
<div className="border-r py-2.5">Wed</div>
10+
<div className="border-r py-2.5">Thu</div>
11+
<div className="border-r py-2.5">Fri</div>
12+
<div className="py-2.5">Sat</div>
13+
</div>
14+
)
15+
}

0 commit comments

Comments
 (0)