-
-
- {comment.wrtrNm}
-
-
- {dayjs(comment.regDt).format("YYYY.MM.DD")}
-
-
- {editingCmntNo === comment.cmntNo ? (
-
- ) : (
-
- {comment.cmntCn}
-
+
+ {Array.from({ length: Number(comment.cmntEvl) }).map(
+ (_, index) => (
+ ★
+ ),
)}
-
-
-
+
+
+
+
+ {comment.wrtrNm}
+
+
+ {dayjs(comment.regDt).format("YYYY.MM.DD")}
+
+
+ {editingCmntNo === comment.cmntNo ? (
+
+ ) : (
+
+ {comment.cmntCn}
+
+ )}
+
+
+
+
+
);
diff --git a/packages/common/components/package.json b/packages/common/components/package.json
index 925699de0..0edd7211b 100644
--- a/packages/common/components/package.json
+++ b/packages/common/components/package.json
@@ -61,7 +61,7 @@
"js-cookie": "^3.0.5",
"lucide-react": "^0.474.0",
"react-chartjs-2": "^5.3.0",
- "react-day-picker": "^8.10.1",
+ "react-day-picker": "^9.7.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"tailwind-merge": "^3.0.1",
diff --git a/packages/common/components/src/ui/calendar/button.tsx b/packages/common/components/src/ui/calendar/button.tsx
new file mode 100644
index 000000000..add67a757
--- /dev/null
+++ b/packages/common/components/src/ui/calendar/button.tsx
@@ -0,0 +1,56 @@
+import * as React from "react";
+import { Slot } from "@radix-ui/react-slot";
+import { cva, type VariantProps } from "class-variance-authority";
+
+import { cn } from "../../lib";
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
+ {
+ variants: {
+ variant: {
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
+ destructive:
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90",
+ outline:
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
+ secondary:
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ ghost: "hover:bg-accent hover:text-accent-foreground",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-10 px-4 py-2",
+ sm: "h-9 rounded-md px-3",
+ lg: "h-11 rounded-md px-8",
+ icon: "h-10 w-10",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ },
+);
+
+export interface ButtonProps
+ extends React.ButtonHTMLAttributes
,
+ VariantProps {
+ asChild?: boolean;
+}
+
+const Button = React.forwardRef(
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "button";
+ return (
+
+ );
+ },
+);
+Button.displayName = "Button";
+
+export { Button, buttonVariants };
diff --git a/packages/common/components/src/ui/calendar/index.tsx b/packages/common/components/src/ui/calendar/index.tsx
index c1a6d5204..f2af829e5 100644
--- a/packages/common/components/src/ui/calendar/index.tsx
+++ b/packages/common/components/src/ui/calendar/index.tsx
@@ -1,69 +1,211 @@
import * as React from "react";
-import { ko } from "date-fns/locale";
-import { ChevronLeft, ChevronRight } from "lucide-react";
-import { DayPicker } from "react-day-picker";
+import {
+ ChevronDownIcon,
+ ChevronLeftIcon,
+ ChevronRightIcon,
+} from "lucide-react";
+import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker";
+import { ko } from "react-day-picker/locale";
-import { cn } from "../../lib/utils";
-import { buttonVariants } from "../button";
+import { cn } from "../../lib";
+import { Button, buttonVariants } from "./button";
-export type CalendarProps = React.ComponentProps;
-
-export function Calendar({
+function Calendar({
className,
classNames,
showOutsideDays = true,
+ captionLayout = "dropdown",
+ buttonVariant = "ghost",
+ formatters,
+ components,
...props
-}: CalendarProps) {
+}: React.ComponentProps & {
+ buttonVariant?: React.ComponentProps["variant"];
+}) {
+ const defaultClassNames = getDefaultClassNames();
+
return (
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={{
- months:
- "flex flex-col sm:flex-row space-y-[1.6rem] sm:space-x-[1.6rem] sm:space-y-0",
- month: "space-y-[1.6rem]",
- caption: "flex justify-center pt-[0.4rem] relative items-center",
- caption_label: "text-[1.4rem] font-medium",
- nav: "space-x-[0.4rem] flex items-center",
- nav_button: cn(
- buttonVariants({ variant: "outline" }),
- "size-[2.8rem] bg-transparent p-0 opacity-50 hover:opacity-100",
+ root: cn("w-fit", defaultClassNames.root),
+ months: cn(
+ "relative flex flex-col gap-4 md:flex-row",
+ defaultClassNames.months,
+ ),
+ month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
+ nav: cn(
+ "absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
+ defaultClassNames.nav,
+ ),
+ button_previous: cn(
+ buttonVariants({ variant: buttonVariant }),
+ "h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50 [&_svg]:size-6",
+ defaultClassNames.button_previous,
+ ),
+ button_next: cn(
+ buttonVariants({ variant: buttonVariant }),
+ "h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50 [&_svg]:size-6",
+ defaultClassNames.button_next,
+ ),
+ month_caption: cn(
+ "flex h-[--cell-size] w-full items-center justify-center px-[--cell-size]",
+ defaultClassNames.month_caption,
+ ),
+ dropdowns: cn(
+ "flex h-[--cell-size] w-full items-center justify-center gap-1.5 text-xl font-medium",
+ captionLayout === "dropdown" && "flex-row-reverse",
+ defaultClassNames.dropdowns,
+ ),
+ dropdown_root: cn(
+ "has-focus:border-ring border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] relative rounded-md border",
+ defaultClassNames.dropdown_root,
+ ),
+ dropdown: cn("absolute inset-0 opacity-0", defaultClassNames.dropdown),
+ caption_label: cn(
+ "select-none font-medium",
+ captionLayout === "label"
+ ? "text-xl"
+ : "[&>svg]:text-muted-foreground flex h-10 items-center gap-1 rounded-md pl-4 pr-3 text-xl [&>svg]:size-3.5",
+ defaultClassNames.caption_label,
+ ),
+ table: "w-full border-collapse",
+ weekdays: cn("flex", defaultClassNames.weekdays),
+ weekday: cn(
+ "text-muted-foreground flex-1 select-none rounded-md text-xl font-normal",
+ defaultClassNames.weekday,
+ ),
+ week: cn("mt-2 flex w-full", defaultClassNames.week),
+ week_number_header: cn(
+ "w-[--cell-size] select-none",
+ defaultClassNames.week_number_header,
+ ),
+ week_number: cn(
+ "text-muted-foreground select-none text-[0.8rem]",
+ defaultClassNames.week_number,
),
- nav_button_previous: "absolute left-[0.4rem]",
- nav_button_next: "absolute right-[0.4rem]",
- table: "w-full border-collapse space-y-[0.4rem]",
- head_row: "flex",
- head_cell:
- "text-muted-foreground rounded-md w-[3.6rem] font-normal text-[1.28rem]",
- row: "flex w-full mt-[0.8rem]",
- cell: "size-[3.6rem] text-center text-[1.4rem] 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" }),
- "size-[3.6rem] p-0 font-normal aria-selected:opacity-100 text-[1.3rem]",
+ "group/day relative aspect-square h-full w-full size-12 select-none p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md",
+ defaultClassNames.day,
),
- day_range_end: "day-range-end",
- 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",
- day_outside:
- "day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground",
- day_disabled: "text-muted-foreground opacity-50",
- day_range_middle:
- "aria-selected:bg-accent aria-selected:text-accent-foreground",
- day_hidden: "invisible",
+ range_start: cn(
+ "bg-accent rounded-l-md",
+ defaultClassNames.range_start,
+ ),
+ range_middle: cn("rounded-none", defaultClassNames.range_middle),
+ range_end: cn("bg-accent rounded-r-md", defaultClassNames.range_end),
+ today: cn(
+ "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
+ 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={{
- IconLeft: ({ className, ...props }) => (
-
- ),
- IconRight: ({ className, ...props }) => (
-
- ),
+ Root: ({ className, rootRef, ...props }) => {
+ return (
+
+ );
+ },
+ Chevron: ({ className, orientation, ...props }) => {
+ if (orientation === "left") {
+ return (
+
+ );
+ }
+
+ if (orientation === "right") {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+ },
+ DayButton: CalendarDayButton,
+ WeekNumber: ({ children, ...props }) => {
+ return (
+
+
+ {children}
+
+ |
+ );
+ },
+ ...components,
}}
{...props}
/>
);
}
-Calendar.displayName = "Calendar";
+
+function CalendarDayButton({
+ className,
+ day,
+ modifiers,
+ ...props
+}: React.ComponentProps) {
+ const defaultClassNames = getDefaultClassNames();
+
+ const ref = React.useRef(null);
+ React.useEffect(() => {
+ if (modifiers.focused) ref.current?.focus();
+ }, [modifiers.focused]);
+
+ return (
+
-
+
);
diff --git a/packages/common/components/src/ui/date-range-picker/index.tsx b/packages/common/components/src/ui/date-range-picker/index.tsx
index 3572ffb7b..1e8cdfe4c 100644
--- a/packages/common/components/src/ui/date-range-picker/index.tsx
+++ b/packages/common/components/src/ui/date-range-picker/index.tsx
@@ -3,19 +3,20 @@
import { useEffect, useState } from "react";
import { format } from "date-fns";
import { CalendarIcon } from "lucide-react";
-import type { DateRange } from "react-day-picker";
+import type { DateRange, DayPicker } from "react-day-picker";
import { cn } from "../../lib/utils";
import { Button } from "../button";
import { Calendar } from "../calendar";
import { Popover, PopoverContent, PopoverTrigger } from "../popover";
-type DateRangePickerProps = React.HTMLAttributes & {
- value?: DateRange;
- defaultValue?: DateRange;
- disabled?: boolean;
- onValueChange?: (date: DateRange | undefined) => void;
-};
+type DateRangePickerProps = React.HTMLAttributes &
+ React.ComponentProps & {
+ value?: DateRange;
+ defaultValue?: DateRange;
+ disabled?: boolean;
+ onValueChange?: (date: DateRange | undefined) => void;
+ };
export function DateRangePicker({
value,
@@ -24,6 +25,7 @@ export function DateRangePicker({
className,
children,
onValueChange,
+ ...props
}: DateRangePickerProps) {
const [dateState, setDateState] = useState(
defaultValue,
@@ -77,7 +79,7 @@ export function DateRangePicker({
{children}
-
-

-
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 509d3349c..534b42639 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -759,8 +759,8 @@ importers:
specifier: ^5.3.0
version: 5.3.0(chart.js@4.4.8)(react@18.3.1)
react-day-picker:
- specifier: ^8.10.1
- version: 8.10.1(date-fns@3.6.0)(react@18.3.1)
+ specifier: ^9.7.0
+ version: 9.7.0(react@18.3.1)
react-dnd:
specifier: ^16.0.1
version: 16.0.1(@types/node@22.13.5)(@types/react@18.3.18)(react@18.3.1)
@@ -1109,6 +1109,10 @@ packages:
sisteransi: 1.0.5
dev: true
+ /@date-fns/tz@1.2.0:
+ resolution: {integrity: sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==}
+ dev: false
+
/@dependents/detective-less@5.0.1:
resolution: {integrity: sha512-Y6+WUMsTFWE5jb20IFP4YGa5IrGY/+a/FbOSjDF/wz9gepU2hwCYSXRHP/vPwBvwcY3SVMASt4yXxbXNXigmZQ==}
engines: {node: '>=18'}
@@ -2481,7 +2485,7 @@ packages:
dnd-multi-backend: 8.1.2(dnd-core@16.0.1)
framer-motion: 11.18.2(react-dom@18.3.1)(react@18.3.1)
react: 18.3.1
- react-dnd: 16.0.1(@types/node@22.13.9)(@types/react@18.3.18)(react@18.3.1)
+ react-dnd: 16.0.1(@types/node@22.13.5)(@types/react@18.3.18)(react@18.3.1)
react-dnd-html5-backend: 16.0.1
react-dnd-touch-backend: 16.0.1
react-dom: 18.3.1(react@18.3.1)
@@ -5526,10 +5530,18 @@ packages:
is-data-view: 1.0.2
dev: true
+ /date-fns-jalali@4.1.0-0:
+ resolution: {integrity: sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==}
+ dev: false
+
/date-fns@3.6.0:
resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==}
dev: false
+ /date-fns@4.1.0:
+ resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
+ dev: false
+
/dayjs@1.11.13:
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
dev: false
@@ -8257,6 +8269,18 @@ packages:
react: 18.3.1
dev: false
+ /react-day-picker@9.7.0(react@18.3.1):
+ resolution: {integrity: sha512-urlK4C9XJZVpQ81tmVgd2O7lZ0VQldZeHzNejbwLWZSkzHH498KnArT0EHNfKBOWwKc935iMLGZdxXPRISzUxQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ react: ^18.3.1
+ dependencies:
+ '@date-fns/tz': 1.2.0
+ date-fns: 4.1.0
+ date-fns-jalali: 4.1.0-0
+ react: 18.3.1
+ dev: false
+
/react-dnd-html5-backend@16.0.1:
resolution: {integrity: sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==}
dependencies: