replace vanilla calendar with shadcn/ui one

This commit is contained in:
vchikalkin 2025-02-01 16:59:28 +03:00
parent 1c4560fd2f
commit e93cc51a68
8 changed files with 147 additions and 48 deletions

View File

@ -1,12 +1,13 @@
import { PageHeader } from '@/components/navigation';
import { SlotsCalendar, TimeSlots } from '@/components/schedule';
import { ScheduleSlotsProvider } from '@/context/schedule-slots';
export default function SchedulePage() {
return (
<>
<ScheduleSlotsProvider>
<PageHeader title="График работы" />
<SlotsCalendar />
<TimeSlots />
</>
</ScheduleSlotsProvider>
);
}

View File

@ -1,29 +0,0 @@
'use client';
import { useLocale } from 'next-intl';
import { useEffect, useRef, useState } from 'react';
import 'vanilla-calendar-pro/styles/index.css';
import { Calendar, type Options } from 'vanilla-calendar-pro';
type CalendarProps = React.HTMLAttributes<HTMLDivElement> & {
readonly config?: Options;
};
function VanillaCalendar({ config, ...attributes }: CalendarProps) {
const ref = useRef(null);
const [calendar, setCalendar] = useState<Calendar | null>(null);
const locale = useLocale();
useEffect(() => {
if (!ref.current) return;
setCalendar(new Calendar(ref.current, { ...config, locale }));
}, [config, locale, ref]);
useEffect(() => {
if (!calendar) return;
calendar.init();
}, [calendar]);
return <div {...attributes} ref={ref} />;
}
export default VanillaCalendar;

View File

@ -1,13 +1,24 @@
import VanillaCalendar from '@/components/common/vanilla-calendar';
'use client';
import { ScheduleSlotsContext } from '@/context/schedule-slots';
import { Calendar } from '@repo/ui/components/ui/calendar';
import dayjs from 'dayjs';
import { useContext } from 'react';
export function SlotsCalendar() {
const { selectedDate, setSelectedDate } = useContext(ScheduleSlotsContext);
return (
<div className="p-4">
<VanillaCalendar
config={{
dateMin: new Date(),
selectionYearsMode: false,
<div>
<Calendar
className="bg-background"
disabled={(date) => {
return dayjs().isAfter(dayjs(date), 'day');
}}
mode="single"
onSelect={(date) => {
if (date) setSelectedDate(date);
}}
selected={selectedDate}
/>
</div>
);

View File

@ -0,0 +1,17 @@
'use client';
import { createContext, useMemo, useState } from 'react';
type ContextType = {
selectedDate: Date;
setSelectedDate: (date: Date) => void;
};
export const ScheduleSlotsContext = createContext<ContextType>({} as ContextType);
export function ScheduleSlotsProvider({ children }: { readonly children: React.ReactNode }) {
const [selectedDate, setSelectedDate] = useState(new Date());
const value = useMemo(() => ({ selectedDate, setSelectedDate }), [selectedDate, setSelectedDate]);
return <ScheduleSlotsContext value={value}>{children}</ScheduleSlotsContext>;
}

View File

@ -17,6 +17,7 @@
"@repo/ui": "workspace:*",
"@tanstack/react-query": "^5.64.1",
"@telegram-apps/sdk-react": "^2.0.19",
"dayjs": "^1.11.13",
"graphql": "catalog:",
"lucide-react": "catalog:",
"next": "^15.1.5",
@ -27,7 +28,6 @@
"react": "catalog:",
"react-dom": "catalog:",
"use-debounce": "^10.0.4",
"vanilla-calendar-pro": "^3.0.3",
"zod": "catalog:"
},
"devDependencies": {

View File

@ -26,7 +26,7 @@
"type-check": "tsc --noEmit"
},
"devDependencies": {
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-slot": "^1.1.1",
"@repo/eslint-config": "workspace:*",
"@repo/lint-staged-config": "workspace:*",
"@repo/typescript-config": "workspace:*",
@ -50,7 +50,9 @@
"@radix-ui/react-label": "^2.1.1",
"@radix-ui/react-scroll-area": "^1.2.2",
"@radix-ui/react-select": "^2.1.4",
"date-fns": "^4.1.0",
"react": "catalog:",
"react-day-picker": "8.10.1",
"react-dom": "catalog:"
}
}

View File

@ -0,0 +1,75 @@
/* eslint-disable react/prop-types */
/* eslint-disable @typescript-eslint/no-shadow */
/* eslint-disable react/no-unstable-nested-components */
'use client';
import { buttonVariants } from '@repo/ui/components/ui/button';
import { cn } from '@repo/ui/lib/utils';
import { ru } from 'date-fns/locale';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import * as React from 'react';
import { DayPicker } from 'react-day-picker';
export type CalendarProps = React.ComponentProps<typeof DayPicker> & {
readonly localeName?: 'ru';
};
function Calendar({
className,
classNames,
localeName = 'ru',
showOutsideDays = true,
...props
}: CalendarProps) {
return (
<DayPicker
className={cn('p-3', className)}
classNames={{
caption: 'flex justify-center pt-1 relative items-center',
caption_label: 'text-sm font-medium',
cell: 'h-9 w-full text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&: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',
day: cn(
buttonVariants({ variant: 'ghost' }),
'h-9 w-full p-0 font-normal aria-selected:opacity-100',
),
day_disabled: 'text-muted-foreground opacity-50',
day_hidden: 'invisible',
day_outside:
'day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground',
day_range_end: 'day-range-end',
day_range_middle: 'aria-selected:bg-accent aria-selected:text-accent-foreground',
day_selected:
'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground',
day_today: 'bg-accent text-accent-foreground',
head_cell: 'text-muted-foreground rounded-md w-full font-normal text-[0.8rem]',
head_row: 'flex',
month: 'space-y-4',
months: 'flex flex-col flex-row space-y-4 space-x-4 space-y-0',
nav: 'space-x-1 flex items-center',
nav_button: cn(
buttonVariants({ variant: 'outline' }),
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
),
nav_button_next: 'absolute right-1',
nav_button_previous: 'absolute left-1',
row: 'flex w-full mt-2',
table: 'w-full border-collapse space-y-1',
...classNames,
}}
components={{
IconLeft: ({ className, ...props }) => (
<ChevronLeft className={cn('h-4 w-4', className)} {...props} />
),
IconRight: ({ className, ...props }) => (
<ChevronRight className={cn('h-4 w-4', className)} {...props} />
),
}}
showOutsideDays={showOutsideDays}
{...props}
locale={localeName === 'ru' ? ru : undefined}
/>
);
}
Calendar.displayName = 'Calendar';
export { Calendar };

40
pnpm-lock.yaml generated
View File

@ -147,6 +147,9 @@ importers:
'@telegram-apps/sdk-react':
specifier: ^2.0.19
version: 2.0.19(@types/react@19.0.1)(react@19.0.0)
dayjs:
specifier: ^1.11.13
version: 1.11.13
graphql:
specifier: 'catalog:'
version: 16.9.0
@ -177,9 +180,6 @@ importers:
use-debounce:
specifier: ^10.0.4
version: 10.0.4(react@19.0.0)
vanilla-calendar-pro:
specifier: ^3.0.3
version: 3.0.3
zod:
specifier: 'catalog:'
version: 3.24.1
@ -327,15 +327,21 @@ importers:
'@radix-ui/react-select':
specifier: ^2.1.4
version: 2.1.4(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
date-fns:
specifier: ^4.1.0
version: 4.1.0
react:
specifier: 'catalog:'
version: 19.0.0
react-day-picker:
specifier: 8.10.1
version: 8.10.1(date-fns@4.1.0)(react@19.0.0)
react-dom:
specifier: 'catalog:'
version: 19.0.0(react@19.0.0)
devDependencies:
'@radix-ui/react-slot':
specifier: ^1.1.0
specifier: ^1.1.1
version: 1.1.1(@types/react@19.0.1)(react@19.0.0)
'@repo/eslint-config':
specifier: workspace:*
@ -3281,6 +3287,12 @@ packages:
dataloader@2.2.3:
resolution: {integrity: sha512-y2krtASINtPFS1rSDjacrFgn1dcUuoREVabwlOGOe4SdxenREqwjwjElAdwvbGM7kgZz9a3KVicWR7vcz8rnzA==}
date-fns@4.1.0:
resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
dayjs@1.11.13:
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
debounce@1.2.1:
resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==}
@ -5277,6 +5289,12 @@ packages:
ramda@0.30.1:
resolution: {integrity: sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==}
react-day-picker@8.10.1:
resolution: {integrity: sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==}
peerDependencies:
date-fns: ^2.28.0 || ^3.0.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0
react-dom@19.0.0:
resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==}
peerDependencies:
@ -6124,9 +6142,6 @@ packages:
resolution: {integrity: sha512-Z6Uz+TYwEqE7ZN50gwn+1LCVo9ZVrpxRPOhOLnncYkY1ZzOYtrX8Fwf/rFktZ8R5mJms6EZf5TqNOMeZmnPq9Q==}
engines: {node: '>=12'}
vanilla-calendar-pro@3.0.3:
resolution: {integrity: sha512-bfmeGFaDeakk/OrwgM+22qDiRvIx1Zu+GG3+E/sCPjaKwbJR7AsDyRBfJQzVHRw3eeavU8jQQTRSFt8EPIPOGw==}
vite-node@2.1.8:
resolution: {integrity: sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg==}
engines: {node: ^18.0.0 || >=20.0.0}
@ -9785,6 +9800,10 @@ snapshots:
dataloader@2.2.3: {}
date-fns@4.1.0: {}
dayjs@1.11.13: {}
debounce@1.2.1: {}
debug@3.2.7:
@ -12047,6 +12066,11 @@ snapshots:
ramda@0.30.1: {}
react-day-picker@8.10.1(date-fns@4.1.0)(react@19.0.0):
dependencies:
date-fns: 4.1.0
react: 19.0.0
react-dom@19.0.0(react@19.0.0):
dependencies:
react: 19.0.0
@ -12951,8 +12975,6 @@ snapshots:
value-or-promise@1.0.12: {}
vanilla-calendar-pro@3.0.3: {}
vite-node@2.1.8(@types/node@20.17.8):
dependencies:
cac: 6.7.14