@sonamu-kit/react-components 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/dist/components/index.d.ts +65 -0
  2. package/dist/components/ui/accordion.d.ts +7 -0
  3. package/dist/components/ui/alert-dialog.d.ts +20 -0
  4. package/dist/components/ui/alert.d.ts +8 -0
  5. package/dist/components/ui/aspect-ratio.d.ts +3 -0
  6. package/dist/components/ui/async-select.d.ts +36 -0
  7. package/dist/components/ui/avatar.d.ts +6 -0
  8. package/dist/components/ui/badge.d.ts +9 -0
  9. package/dist/components/ui/breadcrumb.d.ts +19 -0
  10. package/dist/components/ui/button.d.ts +13 -0
  11. package/dist/components/ui/calendar.d.ts +5 -0
  12. package/dist/components/ui/card.d.ts +9 -0
  13. package/dist/components/ui/carousel.d.ts +18 -0
  14. package/dist/components/ui/checkbox.d.ts +8 -0
  15. package/dist/components/ui/collapsible.d.ts +5 -0
  16. package/dist/components/ui/combobox.d.ts +20 -0
  17. package/dist/components/ui/command.d.ts +80 -0
  18. package/dist/components/ui/common-modal.d.ts +28 -0
  19. package/dist/components/ui/context-menu.d.ts +27 -0
  20. package/dist/components/ui/date-input.d.ts +7 -0
  21. package/dist/components/ui/date-picker.d.ts +26 -0
  22. package/dist/components/ui/date-selector-multiple.d.ts +38 -0
  23. package/dist/components/ui/dialog.d.ts +19 -0
  24. package/dist/components/ui/drawer.d.ts +22 -0
  25. package/dist/components/ui/dropdown-menu.d.ts +27 -0
  26. package/dist/components/ui/form.d.ts +23 -0
  27. package/dist/components/ui/hover-card.d.ts +6 -0
  28. package/dist/components/ui/image-uploader.d.ts +14 -0
  29. package/dist/components/ui/input-otp.d.ts +34 -0
  30. package/dist/components/ui/input.d.ts +7 -0
  31. package/dist/components/ui/label.d.ts +5 -0
  32. package/dist/components/ui/menubar.d.ts +28 -0
  33. package/dist/components/ui/month-picker-multiple.d.ts +41 -0
  34. package/dist/components/ui/multi-image-uploader.d.ts +15 -0
  35. package/dist/components/ui/multi-select.d.ts +229 -0
  36. package/dist/components/ui/navigation-menu.d.ts +12 -0
  37. package/dist/components/ui/pagination.d.ts +10 -0
  38. package/dist/components/ui/popover.d.ts +7 -0
  39. package/dist/components/ui/progress.d.ts +4 -0
  40. package/dist/components/ui/radio-group.d.ts +5 -0
  41. package/dist/components/ui/resizable.d.ts +23 -0
  42. package/dist/components/ui/scroll-area.d.ts +5 -0
  43. package/dist/components/ui/select.d.ts +20 -0
  44. package/dist/components/ui/separator.d.ts +4 -0
  45. package/dist/components/ui/sheet.d.ts +25 -0
  46. package/dist/components/ui/sidebar.d.ts +69 -0
  47. package/dist/components/ui/skeleton.d.ts +2 -0
  48. package/dist/components/ui/slider.d.ts +8 -0
  49. package/dist/components/ui/sonner.d.ts +4 -0
  50. package/dist/components/ui/switch.d.ts +8 -0
  51. package/dist/components/ui/table.d.ts +24 -0
  52. package/dist/components/ui/tabs.d.ts +7 -0
  53. package/dist/components/ui/textarea.d.ts +7 -0
  54. package/dist/components/ui/toast.d.ts +15 -0
  55. package/dist/components/ui/toaster.d.ts +1 -0
  56. package/dist/components/ui/toggle-group.d.ts +12 -0
  57. package/dist/components/ui/toggle.d.ts +12 -0
  58. package/dist/components/ui/tooltip.d.ts +7 -0
  59. package/dist/hooks/index.d.ts +1 -0
  60. package/dist/hooks/use-toast.d.ts +44 -0
  61. package/dist/index.d.ts +3 -0
  62. package/dist/lib/caster.d.ts +3 -0
  63. package/dist/lib/helpers.d.ts +72 -0
  64. package/dist/lib/index.d.ts +6 -0
  65. package/{src/lib/lazy-upload.ts → dist/lib/lazy-upload.d.ts} +1 -12
  66. package/dist/lib/use-mobile.d.ts +1 -0
  67. package/dist/lib/utils.d.ts +2 -0
  68. package/dist/react-components.es.js +28375 -0
  69. package/package.json +105 -76
  70. package/COMPONENTS_LIST.md +0 -106
  71. package/COMPONENTS_STATUS.md +0 -114
  72. package/HELPERS_GUIDE.md +0 -489
  73. package/MIGRATION_PLAN.md +0 -404
  74. package/SETUP_GUIDE.md +0 -125
  75. package/components.json +0 -21
  76. package/postcss.config.js +0 -6
  77. package/src/components/index.ts +0 -315
  78. package/src/components/ui/accordion.tsx +0 -54
  79. package/src/components/ui/alert-dialog.tsx +0 -115
  80. package/src/components/ui/alert.tsx +0 -49
  81. package/src/components/ui/aspect-ratio.tsx +0 -5
  82. package/src/components/ui/async-select.tsx +0 -186
  83. package/src/components/ui/avatar.tsx +0 -45
  84. package/src/components/ui/badge.tsx +0 -38
  85. package/src/components/ui/breadcrumb.tsx +0 -102
  86. package/src/components/ui/button.tsx +0 -54
  87. package/src/components/ui/calendar.tsx +0 -193
  88. package/src/components/ui/card.tsx +0 -65
  89. package/src/components/ui/carousel.tsx +0 -243
  90. package/src/components/ui/checkbox.tsx +0 -67
  91. package/src/components/ui/collapsible.tsx +0 -9
  92. package/src/components/ui/combobox.tsx +0 -135
  93. package/src/components/ui/command.tsx +0 -143
  94. package/src/components/ui/common-modal.tsx +0 -95
  95. package/src/components/ui/context-menu.tsx +0 -189
  96. package/src/components/ui/date-picker.tsx +0 -112
  97. package/src/components/ui/date-selector-multiple.tsx +0 -197
  98. package/src/components/ui/dialog.tsx +0 -104
  99. package/src/components/ui/drawer.tsx +0 -100
  100. package/src/components/ui/dropdown-menu.tsx +0 -189
  101. package/src/components/ui/form.tsx +0 -171
  102. package/src/components/ui/hover-card.tsx +0 -27
  103. package/src/components/ui/image-uploader.tsx +0 -251
  104. package/src/components/ui/input-otp.tsx +0 -69
  105. package/src/components/ui/input.tsx +0 -38
  106. package/src/components/ui/label.tsx +0 -19
  107. package/src/components/ui/menubar.tsx +0 -231
  108. package/src/components/ui/month-picker-multiple.tsx +0 -351
  109. package/src/components/ui/multi-image-uploader.tsx +0 -283
  110. package/src/components/ui/multi-select.tsx +0 -1143
  111. package/src/components/ui/navigation-menu.tsx +0 -120
  112. package/src/components/ui/pagination.tsx +0 -72
  113. package/src/components/ui/popover.tsx +0 -42
  114. package/src/components/ui/progress.tsx +0 -25
  115. package/src/components/ui/radio-group.tsx +0 -38
  116. package/src/components/ui/resizable.tsx +0 -42
  117. package/src/components/ui/scroll-area.tsx +0 -46
  118. package/src/components/ui/select.tsx +0 -235
  119. package/src/components/ui/separator.tsx +0 -24
  120. package/src/components/ui/sheet.tsx +0 -119
  121. package/src/components/ui/sidebar.tsx +0 -683
  122. package/src/components/ui/skeleton.tsx +0 -7
  123. package/src/components/ui/slider.tsx +0 -57
  124. package/src/components/ui/sonner.tsx +0 -39
  125. package/src/components/ui/switch.tsx +0 -63
  126. package/src/components/ui/table.tsx +0 -94
  127. package/src/components/ui/tabs.tsx +0 -53
  128. package/src/components/ui/textarea.tsx +0 -34
  129. package/src/components/ui/toast.tsx +0 -122
  130. package/src/components/ui/toaster.tsx +0 -29
  131. package/src/components/ui/toggle-group.tsx +0 -55
  132. package/src/components/ui/toggle.tsx +0 -41
  133. package/src/components/ui/tooltip.tsx +0 -28
  134. package/src/hooks/index.ts +0 -2
  135. package/src/hooks/use-toast.ts +0 -189
  136. package/src/icons.d.ts +0 -1
  137. package/src/index.ts +0 -4
  138. package/src/lib/caster.ts +0 -66
  139. package/src/lib/helpers.ts +0 -394
  140. package/src/lib/index.ts +0 -31
  141. package/src/lib/use-mobile.ts +0 -19
  142. package/src/lib/utils.ts +0 -6
  143. package/src/styles/globals.css +0 -658
  144. package/tailwind.config.ts +0 -8
  145. package/tsconfig.json +0 -31
  146. package/tsconfig.node.json +0 -11
@@ -1,38 +0,0 @@
1
- import * as React from "react";
2
- import type { Override } from "../../lib/helpers";
3
- import { cn } from "../../lib/utils";
4
-
5
- export type InputProps = Override<
6
- React.ComponentProps<"input">,
7
- {
8
- onValueChange?: (value: string) => void;
9
- }
10
- >;
11
-
12
- const Input = React.forwardRef<HTMLInputElement, InputProps>(
13
- ({ className, type, onValueChange, onChange, ...props }, ref) => {
14
- const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
15
- onValueChange?.(e.target.value);
16
- onChange?.(e);
17
- };
18
-
19
- return (
20
- <input
21
- type={type}
22
- data-slot="input"
23
- className={cn(
24
- "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border px-3 py-1 text-base bg-input-background transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
25
- "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
26
- "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
27
- className,
28
- )}
29
- ref={ref}
30
- {...props}
31
- onChange={handleChange}
32
- />
33
- );
34
- },
35
- );
36
- Input.displayName = "Input";
37
-
38
- export { Input };
@@ -1,19 +0,0 @@
1
- import * as LabelPrimitive from "@radix-ui/react-label";
2
- import { cva, type VariantProps } from "class-variance-authority";
3
- import * as React from "react";
4
-
5
- import { cn } from "../../lib/utils";
6
-
7
- const labelVariants = cva(
8
- "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
9
- );
10
-
11
- const Label = React.forwardRef<
12
- React.ElementRef<typeof LabelPrimitive.Root>,
13
- React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & VariantProps<typeof labelVariants>
14
- >(({ className, ...props }, ref) => (
15
- <LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />
16
- ));
17
- Label.displayName = LabelPrimitive.Root.displayName;
18
-
19
- export { Label };
@@ -1,231 +0,0 @@
1
- import * as MenubarPrimitive from "@radix-ui/react-menubar";
2
- import * as React from "react";
3
- import CheckIcon from "~icons/lucide/check";
4
- import ChevronRightIcon from "~icons/lucide/chevron-right";
5
- import CircleIcon from "~icons/lucide/circle";
6
-
7
- import { cn } from "../../lib/utils";
8
-
9
- function MenubarMenu({ ...props }: React.ComponentProps<typeof MenubarPrimitive.Menu>) {
10
- return <MenubarPrimitive.Menu {...props} />;
11
- }
12
-
13
- function MenubarGroup({ ...props }: React.ComponentProps<typeof MenubarPrimitive.Group>) {
14
- return <MenubarPrimitive.Group {...props} />;
15
- }
16
-
17
- function MenubarPortal({ ...props }: React.ComponentProps<typeof MenubarPrimitive.Portal>) {
18
- return <MenubarPrimitive.Portal {...props} />;
19
- }
20
-
21
- function MenubarRadioGroup({ ...props }: React.ComponentProps<typeof MenubarPrimitive.RadioGroup>) {
22
- return <MenubarPrimitive.RadioGroup {...props} />;
23
- }
24
-
25
- function MenubarSub({ ...props }: React.ComponentProps<typeof MenubarPrimitive.Sub>) {
26
- return <MenubarPrimitive.Sub data-slot="menubar-sub" {...props} />;
27
- }
28
-
29
- const Menubar = React.forwardRef<
30
- React.ElementRef<typeof MenubarPrimitive.Root>,
31
- React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>
32
- >(({ className, ...props }, ref) => (
33
- <MenubarPrimitive.Root
34
- ref={ref}
35
- className={cn(
36
- "flex h-10 items-center space-x-1 rounded-md border bg-background p-1",
37
- className,
38
- )}
39
- {...props}
40
- />
41
- ));
42
- Menubar.displayName = MenubarPrimitive.Root.displayName;
43
-
44
- const MenubarTrigger = React.forwardRef<
45
- React.ElementRef<typeof MenubarPrimitive.Trigger>,
46
- React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>
47
- >(({ className, ...props }, ref) => (
48
- <MenubarPrimitive.Trigger
49
- ref={ref}
50
- className={cn(
51
- "flex cursor-default select-none items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
52
- className,
53
- )}
54
- {...props}
55
- />
56
- ));
57
- MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName;
58
-
59
- const MenubarSubTrigger = React.forwardRef<
60
- React.ElementRef<typeof MenubarPrimitive.SubTrigger>,
61
- React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {
62
- inset?: boolean;
63
- }
64
- >(({ className, inset, children, ...props }, ref) => (
65
- <MenubarPrimitive.SubTrigger
66
- ref={ref}
67
- className={cn(
68
- "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
69
- inset && "pl-8",
70
- className,
71
- )}
72
- {...props}
73
- >
74
- {children}
75
- <ChevronRightIcon className="ml-auto h-4 w-4" />
76
- </MenubarPrimitive.SubTrigger>
77
- ));
78
- MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName;
79
-
80
- const MenubarSubContent = React.forwardRef<
81
- React.ElementRef<typeof MenubarPrimitive.SubContent>,
82
- React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>
83
- >(({ className, ...props }, ref) => (
84
- <MenubarPrimitive.SubContent
85
- ref={ref}
86
- className={cn(
87
- "z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-menubar-content-transform-origin]",
88
- className,
89
- )}
90
- {...props}
91
- />
92
- ));
93
- MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName;
94
-
95
- const MenubarContent = React.forwardRef<
96
- React.ElementRef<typeof MenubarPrimitive.Content>,
97
- React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>
98
- >(({ className, align = "start", alignOffset = -4, sideOffset = 8, ...props }, ref) => (
99
- <MenubarPrimitive.Portal>
100
- <MenubarPrimitive.Content
101
- ref={ref}
102
- align={align}
103
- alignOffset={alignOffset}
104
- sideOffset={sideOffset}
105
- className={cn(
106
- "z-50 min-w-48 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-menubar-content-transform-origin]",
107
- className,
108
- )}
109
- {...props}
110
- />
111
- </MenubarPrimitive.Portal>
112
- ));
113
- MenubarContent.displayName = MenubarPrimitive.Content.displayName;
114
-
115
- const MenubarItem = React.forwardRef<
116
- React.ElementRef<typeof MenubarPrimitive.Item>,
117
- React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {
118
- inset?: boolean;
119
- }
120
- >(({ className, inset, ...props }, ref) => (
121
- <MenubarPrimitive.Item
122
- ref={ref}
123
- className={cn(
124
- "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
125
- inset && "pl-8",
126
- className,
127
- )}
128
- {...props}
129
- />
130
- ));
131
- MenubarItem.displayName = MenubarPrimitive.Item.displayName;
132
-
133
- const MenubarCheckboxItem = React.forwardRef<
134
- React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,
135
- React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>
136
- >(({ className, children, checked, ...props }, ref) => (
137
- <MenubarPrimitive.CheckboxItem
138
- ref={ref}
139
- className={cn(
140
- "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
141
- className,
142
- )}
143
- checked={checked}
144
- {...props}
145
- >
146
- <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
147
- <MenubarPrimitive.ItemIndicator>
148
- <CheckIcon className="h-4 w-4" />
149
- </MenubarPrimitive.ItemIndicator>
150
- </span>
151
- {children}
152
- </MenubarPrimitive.CheckboxItem>
153
- ));
154
- MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName;
155
-
156
- const MenubarRadioItem = React.forwardRef<
157
- React.ElementRef<typeof MenubarPrimitive.RadioItem>,
158
- React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>
159
- >(({ className, children, ...props }, ref) => (
160
- <MenubarPrimitive.RadioItem
161
- ref={ref}
162
- className={cn(
163
- "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
164
- className,
165
- )}
166
- {...props}
167
- >
168
- <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
169
- <MenubarPrimitive.ItemIndicator>
170
- <CircleIcon className="h-2 w-2 fill-current" />
171
- </MenubarPrimitive.ItemIndicator>
172
- </span>
173
- {children}
174
- </MenubarPrimitive.RadioItem>
175
- ));
176
- MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName;
177
-
178
- const MenubarLabel = React.forwardRef<
179
- React.ElementRef<typeof MenubarPrimitive.Label>,
180
- React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {
181
- inset?: boolean;
182
- }
183
- >(({ className, inset, ...props }, ref) => (
184
- <MenubarPrimitive.Label
185
- ref={ref}
186
- className={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
187
- {...props}
188
- />
189
- ));
190
- MenubarLabel.displayName = MenubarPrimitive.Label.displayName;
191
-
192
- const MenubarSeparator = React.forwardRef<
193
- React.ElementRef<typeof MenubarPrimitive.Separator>,
194
- React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>
195
- >(({ className, ...props }, ref) => (
196
- <MenubarPrimitive.Separator
197
- ref={ref}
198
- className={cn("-mx-1 my-1 h-px bg-muted", className)}
199
- {...props}
200
- />
201
- ));
202
- MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName;
203
-
204
- const MenubarShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
205
- return (
206
- <span
207
- className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)}
208
- {...props}
209
- />
210
- );
211
- };
212
- MenubarShortcut.displayname = "MenubarShortcut";
213
-
214
- export {
215
- Menubar,
216
- MenubarMenu,
217
- MenubarTrigger,
218
- MenubarContent,
219
- MenubarItem,
220
- MenubarSeparator,
221
- MenubarLabel,
222
- MenubarCheckboxItem,
223
- MenubarRadioGroup,
224
- MenubarRadioItem,
225
- MenubarPortal,
226
- MenubarSubContent,
227
- MenubarSubTrigger,
228
- MenubarGroup,
229
- MenubarSub,
230
- MenubarShortcut,
231
- };
@@ -1,351 +0,0 @@
1
- "use client";
2
-
3
- import { format } from "date-fns";
4
- import * as React from "react";
5
- import type { DateRange } from "react-day-picker";
6
- import { Button } from "./button";
7
- import { Popover, PopoverContent, PopoverTrigger } from "./popover";
8
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./select";
9
-
10
- /** 단일 월 또는 월 범위 값 */
11
- export type MonthPickerValue =
12
- | { type: "single"; date: Date }
13
- | { type: "range"; from: Date; to: Date };
14
-
15
- interface MonthPickerMultipleProps {
16
- /** Calendar icon component */
17
- CalendarIcon: React.ComponentType<{ className?: string }>;
18
- /** ChevronDown icon component */
19
- ChevronDownIcon: React.ComponentType<{ className?: string }>;
20
- /** Current value */
21
- value?: MonthPickerValue;
22
- /** Callback when value changes */
23
- onChange?: (event: null, data: { value: MonthPickerValue | undefined }) => void;
24
- /** Placeholder text when no value */
25
- placeholder?: string;
26
- /** Date format string for display */
27
- dateFormat?: string;
28
- /** Custom className for trigger button */
29
- className?: string;
30
- /** Year range for selection */
31
- yearRange?: { start: number; end: number };
32
- /** Default to range mode when opening */
33
- defaultRangeMode?: boolean;
34
- }
35
-
36
- const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
37
-
38
- export function MonthPickerMultiple({
39
- CalendarIcon,
40
- ChevronDownIcon,
41
- value,
42
- onChange,
43
- placeholder,
44
- dateFormat = "MMM/yyyy",
45
- className = "",
46
- yearRange = { start: 2020, end: 2030 },
47
- defaultRangeMode = false,
48
- }: MonthPickerMultipleProps) {
49
- // Popover open state
50
- const [isOpen, setIsOpen] = React.useState(false);
51
-
52
- // Temp states (managed internally)
53
- const [tempIsRangeMode, setTempIsRangeMode] = React.useState(defaultRangeMode);
54
- const [tempDate, setTempDate] = React.useState<Date | undefined>();
55
- const [tempDateRange, setTempDateRange] = React.useState<DateRange | undefined>();
56
- const [tempYear, setTempYear] = React.useState<number>(new Date().getFullYear());
57
- const [tempRangeStartYear, setTempRangeStartYear] = React.useState<number>(
58
- new Date().getFullYear(),
59
- );
60
- const [tempRangeEndYear, setTempRangeEndYear] = React.useState<number>(new Date().getFullYear());
61
-
62
- const years = React.useMemo(
63
- () =>
64
- Array.from({ length: yearRange.end - yearRange.start + 1 }, (_, i) => yearRange.start + i),
65
- [yearRange.start, yearRange.end],
66
- );
67
-
68
- // Initialize temp states when popover opens
69
- React.useEffect(() => {
70
- if (isOpen) {
71
- if (value?.type === "range") {
72
- setTempIsRangeMode(true);
73
- setTempDateRange({ from: value.from, to: value.to });
74
- setTempDate(undefined);
75
- setTempRangeStartYear(value.from.getFullYear());
76
- setTempRangeEndYear(value.to.getFullYear());
77
- } else if (value?.type === "single") {
78
- setTempIsRangeMode(false);
79
- setTempDate(value.date);
80
- setTempDateRange(undefined);
81
- setTempYear(value.date.getFullYear());
82
- } else {
83
- setTempIsRangeMode(defaultRangeMode);
84
- setTempDate(undefined);
85
- setTempDateRange(undefined);
86
- setTempYear(new Date().getFullYear());
87
- setTempRangeStartYear(new Date().getFullYear());
88
- setTempRangeEndYear(new Date().getFullYear());
89
- }
90
- }
91
- }, [isOpen, value, defaultRangeMode]);
92
-
93
- const getDisplayDate = () => {
94
- if (!value) {
95
- return placeholder ?? `- ${format(new Date(), dateFormat)}`;
96
- }
97
- if (value.type === "single") {
98
- return format(value.date, dateFormat);
99
- }
100
- return `${format(value.from, dateFormat)} - ${format(value.to, dateFormat)}`;
101
- };
102
-
103
- const isSaveEnabled = () => {
104
- if (tempIsRangeMode) {
105
- return !!(tempDateRange?.from && tempDateRange?.to);
106
- }
107
- return tempDate !== undefined;
108
- };
109
-
110
- const handleModeChange = (isRangeMode: boolean) => {
111
- setTempIsRangeMode(isRangeMode);
112
- if (isRangeMode) {
113
- setTempDate(undefined);
114
- } else {
115
- setTempDateRange(undefined);
116
- }
117
- };
118
-
119
- const handleSave = () => {
120
- if (onChange) {
121
- if (tempIsRangeMode && tempDateRange?.from && tempDateRange?.to) {
122
- onChange(null, {
123
- value: {
124
- type: "range",
125
- from: tempDateRange.from,
126
- to: tempDateRange.to,
127
- },
128
- });
129
- } else if (!tempIsRangeMode && tempDate) {
130
- onChange(null, { value: { type: "single", date: tempDate } });
131
- }
132
- }
133
- setIsOpen(false);
134
- };
135
-
136
- const handleCancel = () => {
137
- setIsOpen(false);
138
- };
139
-
140
- return (
141
- <div className="flex items-center gap-4">
142
- <Popover open={isOpen} onOpenChange={setIsOpen}>
143
- <PopoverTrigger asChild>
144
- <Button variant="outline" className={`gap-2 h-8 text-xs ${className}`}>
145
- <CalendarIcon className="h-4 w-4" />
146
- {getDisplayDate()}
147
- <ChevronDownIcon className="h-4 w-4" />
148
- </Button>
149
- </PopoverTrigger>
150
- <PopoverContent className="w-auto p-0" align="start">
151
- <div className="p-3 border-b space-y-2">
152
- <div className="flex gap-2">
153
- <Button
154
- variant={!tempIsRangeMode ? "default" : "outline"}
155
- size="sm"
156
- onClick={() => handleModeChange(false)}
157
- className="flex-1 h-7 text-xs"
158
- >
159
- Single Date
160
- </Button>
161
- <Button
162
- variant={tempIsRangeMode ? "default" : "outline"}
163
- size="sm"
164
- onClick={() => handleModeChange(true)}
165
- className="flex-1 h-7 text-xs"
166
- >
167
- Date Range
168
- </Button>
169
- </div>
170
- </div>
171
- {tempIsRangeMode ? (
172
- <div className="flex">
173
- {/* Start Date */}
174
- <div className="p-4 space-y-2">
175
- <div className="text-xs text-muted-foreground">Start Date</div>
176
- <Select
177
- value={tempRangeStartYear.toString()}
178
- onValueChange={(value) => {
179
- if (value) setTempRangeStartYear(parseInt(value));
180
- }}
181
- >
182
- <SelectTrigger className="h-8 text-xs">
183
- <SelectValue />
184
- </SelectTrigger>
185
- <SelectContent>
186
- {years.map((year) => {
187
- const isDisabled = tempDateRange?.to
188
- ? year > tempDateRange.to.getFullYear()
189
- : false;
190
- return (
191
- <SelectItem key={year} value={year.toString()} disabled={isDisabled}>
192
- {year}
193
- </SelectItem>
194
- );
195
- })}
196
- </SelectContent>
197
- </Select>
198
- <div className="grid grid-cols-3 gap-2">
199
- {MONTHS.map((month, idx) => {
200
- const isDisabled = tempDateRange?.to
201
- ? tempRangeStartYear > tempDateRange.to.getFullYear() ||
202
- (tempRangeStartYear === tempDateRange.to.getFullYear() &&
203
- idx > tempDateRange.to.getMonth())
204
- : false;
205
- return (
206
- <Button
207
- key={month}
208
- variant={
209
- tempDateRange?.from?.getMonth() === idx &&
210
- tempDateRange?.from?.getFullYear() === tempRangeStartYear
211
- ? "default"
212
- : "outline"
213
- }
214
- size="sm"
215
- onClick={() => {
216
- const newDate = new Date(tempRangeStartYear, idx, 1);
217
- setTempDateRange({
218
- from: newDate,
219
- to: tempDateRange?.to,
220
- });
221
- }}
222
- disabled={isDisabled}
223
- className="h-8 text-xs"
224
- >
225
- {month}
226
- </Button>
227
- );
228
- })}
229
- </div>
230
- </div>
231
- {/* Divider */}
232
- <div className="border-r" />
233
- {/* End Date */}
234
- <div className="p-4 space-y-2">
235
- <div className="text-xs text-muted-foreground">End Date</div>
236
- <Select
237
- value={tempRangeEndYear.toString()}
238
- onValueChange={(value) => {
239
- if (value) setTempRangeEndYear(parseInt(value));
240
- }}
241
- >
242
- <SelectTrigger className="h-8 text-xs">
243
- <SelectValue />
244
- </SelectTrigger>
245
- <SelectContent>
246
- {years.map((year) => {
247
- const isDisabled = tempDateRange?.from
248
- ? year < tempDateRange.from.getFullYear()
249
- : false;
250
- return (
251
- <SelectItem key={year} value={year.toString()} disabled={isDisabled}>
252
- {year}
253
- </SelectItem>
254
- );
255
- })}
256
- </SelectContent>
257
- </Select>
258
- <div className="grid grid-cols-3 gap-2">
259
- {MONTHS.map((month, idx) => {
260
- const isDisabled = tempDateRange?.from
261
- ? tempRangeEndYear < tempDateRange.from.getFullYear() ||
262
- (tempRangeEndYear === tempDateRange.from.getFullYear() &&
263
- idx < tempDateRange.from.getMonth())
264
- : false;
265
- return (
266
- <Button
267
- key={month}
268
- variant={
269
- tempDateRange?.to?.getMonth() === idx &&
270
- tempDateRange?.to?.getFullYear() === tempRangeEndYear
271
- ? "default"
272
- : "outline"
273
- }
274
- size="sm"
275
- onClick={() => {
276
- const newDate = new Date(tempRangeEndYear, idx, 1);
277
- setTempDateRange({
278
- from: tempDateRange?.from,
279
- to: newDate,
280
- });
281
- }}
282
- disabled={isDisabled}
283
- className="h-8 text-xs"
284
- >
285
- {month}
286
- </Button>
287
- );
288
- })}
289
- </div>
290
- </div>
291
- </div>
292
- ) : (
293
- <div className="p-4 space-y-2">
294
- <Select
295
- value={tempYear.toString()}
296
- onValueChange={(value) => {
297
- if (value) setTempYear(parseInt(value));
298
- }}
299
- >
300
- <SelectTrigger className="h-8 text-xs">
301
- <SelectValue />
302
- </SelectTrigger>
303
- <SelectContent>
304
- {years.map((year) => (
305
- <SelectItem key={year} value={year.toString()}>
306
- {year}
307
- </SelectItem>
308
- ))}
309
- </SelectContent>
310
- </Select>
311
- <div className="grid grid-cols-3 gap-2">
312
- {MONTHS.map((month, idx) => (
313
- <Button
314
- key={month}
315
- variant={
316
- tempDate?.getMonth() === idx && tempDate?.getFullYear() === tempYear
317
- ? "default"
318
- : "outline"
319
- }
320
- size="sm"
321
- onClick={() => {
322
- const newDate = new Date(tempYear, idx, 1);
323
- setTempDate(newDate);
324
- }}
325
- className="h-8 text-xs"
326
- >
327
- {month}
328
- </Button>
329
- ))}
330
- </div>
331
- </div>
332
- )}
333
- <div className="p-3 border-t flex justify-end gap-2">
334
- <Button variant="outline" size="sm" onClick={handleCancel} className="h-8 text-xs">
335
- Cancel
336
- </Button>
337
- <Button
338
- variant="default"
339
- size="sm"
340
- onClick={handleSave}
341
- disabled={!isSaveEnabled()}
342
- className="h-8 text-xs"
343
- >
344
- Save
345
- </Button>
346
- </div>
347
- </PopoverContent>
348
- </Popover>
349
- </div>
350
- );
351
- }