mirror of
https://github.com/hex248/sprint.git
synced 2026-02-07 18:23:03 +00:00
calendar component
This commit is contained in:
10
bun.lock
10
bun.lock
@@ -51,9 +51,11 @@
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"lucide-react": "^0.561.0",
|
||||
"react": "^19.1.0",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-day-picker": "^9.13.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-resizable-panels": "^4.0.15",
|
||||
"react-router-dom": "^7.10.1",
|
||||
@@ -123,6 +125,8 @@
|
||||
|
||||
"@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "7.27.1", "@babel/helper-validator-identifier": "7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
||||
|
||||
"@date-fns/tz": ["@date-fns/tz@1.4.1", "", {}, "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA=="],
|
||||
|
||||
"@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="],
|
||||
|
||||
"@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
|
||||
@@ -501,6 +505,10 @@
|
||||
|
||||
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
||||
|
||||
"date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="],
|
||||
|
||||
"date-fns-jalali": ["date-fns-jalali@4.1.0-0", "", {}, "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
@@ -649,6 +657,8 @@
|
||||
|
||||
"react-colorful": ["react-colorful@5.6.1", "", { "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw=="],
|
||||
|
||||
"react-day-picker": ["react-day-picker@9.13.0", "", { "dependencies": { "@date-fns/tz": "^1.4.1", "date-fns": "^4.1.0", "date-fns-jalali": "^4.1.0-0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-euzj5Hlq+lOHqI53NiuNhCP8HWgsPf/bBAVijR50hNaY1XwjKjShAnIe8jm8RD2W9IJUvihDIZ+KrmqfFzNhFQ=="],
|
||||
|
||||
"react-dom": ["react-dom@19.2.3", "", { "dependencies": { "scheduler": "0.27.0" }, "peerDependencies": { "react": "19.2.3" } }, "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg=="],
|
||||
|
||||
"react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="],
|
||||
|
||||
@@ -23,9 +23,11 @@
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"lucide-react": "^0.561.0",
|
||||
"react": "^19.1.0",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-day-picker": "^9.13.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-resizable-panels": "^4.0.15",
|
||||
"react-router-dom": "^7.10.1",
|
||||
|
||||
172
packages/frontend/src/components/ui/calendar.tsx
Normal file
172
packages/frontend/src/components/ui/calendar.tsx
Normal file
@@ -0,0 +1,172 @@
|
||||
import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { type DayButton, DayPicker, getDefaultClassNames } from "react-day-picker";
|
||||
import { Button, buttonVariants } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
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 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", 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-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
|
||||
defaultClassNames.button_previous,
|
||||
),
|
||||
button_next: cn(
|
||||
buttonVariants({ variant: buttonVariant }),
|
||||
"size-(--cell-size) aria-disabled:opacity-50 p-0 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:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px]",
|
||||
defaultClassNames.dropdown_root,
|
||||
),
|
||||
dropdown: cn("absolute bg-popover inset-0 opacity-0", defaultClassNames.dropdown),
|
||||
caption_label: cn(
|
||||
"select-none font-medium",
|
||||
captionLayout === "label"
|
||||
? "text-sm"
|
||||
: "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 border-collapse",
|
||||
weekdays: cn("flex", defaultClassNames.weekdays),
|
||||
weekday: cn(
|
||||
"text-muted-foreground flex-1 font-normal text-[0.8rem] 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 group/day aspect-square select-none",
|
||||
defaultClassNames.day,
|
||||
),
|
||||
range_start: cn("bg-accent", defaultClassNames.range_start),
|
||||
range_middle: cn(defaultClassNames.range_middle),
|
||||
range_end: cn("bg-accent", defaultClassNames.range_end),
|
||||
today: cn("border border-dashed ", defaultClassNames.today),
|
||||
outside: cn(
|
||||
"text-muted-foreground aria-selected:text-muted-foreground",
|
||||
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(
|
||||
"flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal",
|
||||
"[&>span]:text-xs [&>span]:opacity-70",
|
||||
"hover:bg-primary/90 hover:text-foreground",
|
||||
"data-[selected-single=true]:!bg-foreground data-[selected-single=true]:!text-background data-[selected-single=true]:hover:!bg-foreground/90",
|
||||
"data-[range-start=true]:!bg-foreground data-[range-start=true]:!text-background",
|
||||
"data-[range-middle=true]:!bg-foreground data-[range-middle=true]:!text-background",
|
||||
"data-[range-end=true]:!bg-foreground data-[range-end=true]:!text-background",
|
||||
defaultClassNames.day,
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export { Calendar, CalendarDayButton };
|
||||
Reference in New Issue
Block a user