Skip to content

Commit 660b649

Browse files
committed
feat: disable dates in view
1 parent 1cb689b commit 660b649

11 files changed

Lines changed: 149 additions & 47 deletions

File tree

apps/www/src/components/demos.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { BasicCalendar } from "./ui/calendar.tsx";
22
import "dayjs/locale/en-gb.js";
3+
import { CalendarDisabledDates } from "./ui/calendar-disable-dates.tsx";
34
import { MultiViewCalendar } from "./ui/calendar-multi-view.tsx";
45
import { CalendarStartEndSeparate } from "./ui/calendar-start-end-separate.tsx";
56
import { CalendarWithYearSelect } from "./ui/calendar-with-year-select.tsx";
@@ -48,3 +49,13 @@ export function DemoWithYearSelect() {
4849
/>
4950
);
5051
}
52+
53+
export function DemoDisabledDates() {
54+
return (
55+
<CalendarDisabledDates
56+
locale="en-gb"
57+
mode="single"
58+
className="grow rounded-md border border-border"
59+
/>
60+
);
61+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"use client";
2+
3+
import { cn } from "@/lib/utils.ts";
4+
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
5+
import * as Calendar from "react-composable-calendar";
6+
import { Button } from "./button.tsx";
7+
8+
export function CalendarDisabledDates(props: Calendar.RootProps) {
9+
const { className, ...rest } = props;
10+
11+
return (
12+
<Calendar.Root className={cn("max-w-72 p-3", className)} {...rest}>
13+
<Calendar.View
14+
isDateSelectableFn={(date) => {
15+
return date.day() !== 0 && date.day() !== 6;
16+
}}
17+
>
18+
<div className="mb-4 flex items-center justify-between">
19+
<Calendar.OffsetViewButton asChild offset={-1}>
20+
<Button size="icon" variant="outline" className="size-8">
21+
<ChevronLeftIcon className="size-3" />
22+
</Button>
23+
</Calendar.OffsetViewButton>
24+
<Calendar.MonthTitle className="flex items-center justify-center text-sm" />
25+
<Calendar.OffsetViewButton asChild offset={1}>
26+
<Button size="icon" variant="outline" className="size-8">
27+
<ChevronRightIcon className="size-3" />
28+
</Button>
29+
</Calendar.OffsetViewButton>
30+
</div>
31+
<Calendar.Weekdays className="mb-3 grid grid-cols-7 font-light text-muted-foreground text-xs">
32+
<Calendar.WeekdayLabel className="flex items-center justify-center" />
33+
</Calendar.Weekdays>
34+
<Calendar.Days className="mb-1 grid grid-cols-7 gap-y-1">
35+
<Calendar.Day className="group relative aspect-square w-full cursor-pointer ">
36+
<Calendar.DayInRange className="absolute top-0 right-0 bottom-0 left-0 bg-foreground/10 data-end:rounded-r-lg data-start:rounded-l-lg" />
37+
<div className="absolute top-0 right-0 bottom-0 left-0 z-20 flex items-center justify-center rounded-lg group-data-[neighboring]:invisible group-data-[is-today]:bg-muted group-data-[selected]:bg-foreground">
38+
<Calendar.DayLabel className="group-disabled:text-muted-foreground/50 group-data-[selected]:text-background" />
39+
</div>
40+
</Calendar.Day>
41+
</Calendar.Days>
42+
<Calendar.FormInput />
43+
</Calendar.View>
44+
</Calendar.Root>
45+
);
46+
}

apps/www/src/components/ui/calendar-with-year-select.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { cn } from "@/lib/utils.ts";
44
import dayjs from "dayjs";
55
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
66
import * as Calendar from "react-composable-calendar";
7-
import { useView } from "react-composable-calendar/hooks";
7+
import { useViewState } from "react-composable-calendar/hooks";
88
import { Button } from "./button.tsx";
99
import { Select, SelectContent, SelectItem, SelectTrigger } from "./select.tsx";
1010

@@ -19,7 +19,7 @@ const yearRange = range(
1919
);
2020

2121
function YearSelect() {
22-
const [view, setView] = useView();
22+
const [view, setView] = useViewState();
2323

2424
return (
2525
<Select

apps/www/src/components/ui/calendar.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,7 @@ export function CalendarBody() {
3737
);
3838
}
3939

40-
export type CalendarProps = Calendar.RootProps;
41-
42-
export function BasicCalendar(props: CalendarProps) {
40+
export function BasicCalendar(props: Calendar.RootProps) {
4341
const { className, ...rest } = props;
4442

4543
return (
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { CodeBlock } from "@/components/ui/code-block.tsx"
2+
import { ExternalLink } from "@/components/ui/external-link"
3+
import { markdownComponents } from "@/components/markdown/components"
4+
import { disableDates } from "@/examples/code-examples.ts";
5+
6+
export const components = markdownComponents;
7+
8+
## Installation
9+
* Setup <ExternalLink text="shadcn/ui" href="https://ui.shadcn.com/docs/installation" />.
10+
* Add <ExternalLink text="Button" href="https://ui.shadcn.com/docs/components/button" />.
11+
* Copy paste this code block into `src/components/ui/calendar.tsx`.
12+
13+
<CodeBlock client:load code={disableDates} />

apps/www/src/examples/code-examples.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ export const basicCalendar = await fs.readFile(
66
"utf-8",
77
);
88

9+
export const disableDates = await fs.readFile(
10+
path.join("./src/components/ui/calendar-disable-dates.tsx"),
11+
"utf-8",
12+
);
13+
914
export const datePicker = await fs.readFile(
1015
path.join("./src/components/ui/date-picker.tsx"),
1116
"utf-8",

apps/www/src/pages/index.astro

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,16 @@ import * as DatePickerDocs from "@/docs/date-picker.mdx";
66
import * as MultipleViewsDocs from "@/docs/multiple-views.mdx";
77
import * as SeparateViewsDocs from "@/docs/separate-views.mdx";
88
import * as WithYearSelectDocs from "@/docs/with-year-select.mdx";
9+
import * as DisableDatesDocs from "@/docs/disable-dates.mdx";
910
import * as codeExamples from "@/examples/code-examples.ts";
1011
import {
1112
ExampleSection,
1213
ExampleSectionCode,
1314
ExampleSectionPreview,
1415
} from "../components/layout/example-section.tsx";
15-
import { MultiViewCalendar } from "../components/ui/calendar-multi-view.tsx";
16-
import { CalendarStartEndSeparate } from "../components/ui/calendar-start-end-separate.tsx";
17-
import { BasicCalendar } from "../components/ui/calendar.tsx";
1816
import { Card, CardContent } from "../components/ui/card.tsx";
1917
import { CodeBlock } from "../components/ui/code-block.tsx";
2018
import { Container } from "../components/ui/container.tsx";
21-
import { DatePicker } from "../components/ui/date-picker.tsx";
2219
import { Typography } from "../components/ui/typography.tsx";
2320
import Layout from "../layouts/Layout.astro";
2421
---
@@ -145,5 +142,19 @@ import Layout from "../layouts/Layout.astro";
145142
<WithYearSelectDocs.Content />
146143
</ExampleSectionCode>
147144
</ExampleSection>
145+
146+
<ExampleSection
147+
client:load
148+
id="disabled-dates"
149+
title="Disabled Dates"
150+
description="You can disable dates in the view."
151+
>
152+
<ExampleSectionPreview>
153+
<Demos.DemoDisabledDates client:load />
154+
</ExampleSectionPreview>
155+
<ExampleSectionCode>
156+
<DisableDatesDocs.Content />
157+
</ExampleSectionCode>
158+
</ExampleSection>
148159
</div>
149160
</Layout>

packages/react-composable-calendar/src/contexts/view.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import type { Dayjs } from "../extended-dayjs.js";
22
import { createContext, useContext } from "react";
33

4+
export type IsDateSelectableFn = (date: Dayjs) => boolean;
5+
46
export type ViewContextValue = {
57
viewState: [view: Dayjs, setView: (day: Dayjs) => void];
8+
isDateSelectableFn?: IsDateSelectableFn;
69
};
710

811
export const ViewContext = createContext<ViewContextValue | null>(null);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { dayjs } from "./extended-dayjs.js";
2+
3+
export function getToday(timezone: string | null) {
4+
if (timezone?.toLowerCase() === "utc") {
5+
return dayjs().utc();
6+
}
7+
if (timezone) {
8+
return dayjs().tz(timezone);
9+
}
10+
return dayjs();
11+
}

packages/react-composable-calendar/src/hooks.ts

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@ import { useMemo } from "react";
22
import { useCalendarContext } from "./contexts/calendar.js";
33
import { useDayContext } from "./contexts/day.js";
44
import { useViewContext } from "./contexts/view.js";
5-
import { dayjs } from "./extended-dayjs.js";
5+
import { getToday } from "./helpers.js";
66

7-
export function useView() {
7+
export function useViewState() {
88
const viewContext = useViewContext();
99
return viewContext.viewState;
10-
// const [view, setView] = context.viewState;
11-
// const viewWithOffset = view.add(viewContext.viewOffset, "month");
12-
// return [viewWithOffset, setView] as const;
1310
}
1411

12+
export function useCalendarView() {
13+
const context = useViewContext();
14+
return context;
15+
}
1516
export function useCalendarValue() {
1617
const context = useCalendarContext();
1718
return context.valueState;
@@ -76,13 +77,7 @@ export function useTodaysDate() {
7677
const timezone = useCalendarTimezone();
7778

7879
return useMemo(() => {
79-
if (timezone === null) {
80-
return dayjs();
81-
}
82-
if (timezone === "UTC") {
83-
return dayjs().utc();
84-
}
85-
return dayjs().tz(timezone);
80+
return getToday(timezone);
8681
}, [timezone]);
8782
}
8883

@@ -97,7 +92,7 @@ export function useIsToday() {
9792

9893
export function useIsNeighboringMonth() {
9994
const { day } = useDayContext();
100-
const [view] = useView();
95+
const [view] = useViewState();
10196

10297
return useMemo(() => {
10398
return !day.isSame(view, "month");

0 commit comments

Comments
 (0)