snack-datepicker 0.0.1
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.
- package/README.md +186 -0
- package/dist/index.d.cts +31 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +411 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +405 -0
- package/dist/index.mjs.map +1 -0
- package/dist/style.css +678 -0
- package/package.json +89 -0
package/README.md
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# 📅 Snack Datepicker
|
|
2
|
+
|
|
3
|
+
A modern **React Date Picker & Date Range Picker** built for flexibility, performance, and developer experience.
|
|
4
|
+
|
|
5
|
+
**Snack Datepicker** provides a clean, customizable, and lightweight component for selecting **single dates** or **date ranges**, with powerful configuration options such as presets, disabled dates, multi-month view, and custom day rendering.
|
|
6
|
+
|
|
7
|
+
Ideal for **analytics dashboards, booking systems, reporting tools, admin panels, data filters, and forms**.
|
|
8
|
+
|
|
9
|
+
👉 [Try it](https://snack-calendar.vercel.app/)
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<img src="https://github.com/hemanth-user13/Snack-DatePicker/blob/master/src/assests/Screenshot%202026-03-15%20183307.png" width="900"/>
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
<p align="center">
|
|
16
|
+
<img src="https://github.com/hemanth-user13/Snack-DatePicker/blob/master/src/assests/Screenshot%202026-03-15%20183352.png" width="900"/>
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- 📅 **Single Date Picker** — clean, minimal single date selection
|
|
22
|
+
- 📆 **Date Range Picker** — intuitive start and end date selection
|
|
23
|
+
- 🎯 **Preset date ranges** — quickly jump to common ranges like Last 7 Days or Last 30 Days
|
|
24
|
+
- 📊 **Multi-month view** — display multiple months simultaneously
|
|
25
|
+
- 🔒 **Min / Max date restrictions** — constrain selectable dates to a valid range
|
|
26
|
+
- 🚫 **Disabled dates support** — block out specific unavailable dates
|
|
27
|
+
- 🎨 **Custom day renderer** — full control over how each day cell looks
|
|
28
|
+
- ⚡ **Lightweight & fast** — minimal bundle impact, tree-shakable
|
|
29
|
+
- 🧩 **Highly customizable** — adapt to any design system
|
|
30
|
+
- ⚛️ **Built for modern React** — hooks-first, no legacy patterns
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install snack-datepicker
|
|
36
|
+
# or
|
|
37
|
+
yarn add snack-datepicker
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Peer Dependencies
|
|
41
|
+
|
|
42
|
+
Ensure your project has these installed:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install react react-dom
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
> [!IMPORTANT]
|
|
51
|
+
> You must import the stylesheet for the datepicker to display correctly:
|
|
52
|
+
>
|
|
53
|
+
> ```tsx
|
|
54
|
+
> import "snack-datepicker/dist/style.css";
|
|
55
|
+
> ```
|
|
56
|
+
|
|
57
|
+
### Basic
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
import { DatePicker } from "snack-datepicker";
|
|
61
|
+
import "snack-datepicker/dist/style.css";
|
|
62
|
+
|
|
63
|
+
function App() {
|
|
64
|
+
return <DatePicker mode="single" onChange={(date) => console.log(date)} />;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export default App;
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Date Range Example
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
import { DatePicker } from "snack-datepicker";
|
|
74
|
+
import { useState } from "react";
|
|
75
|
+
|
|
76
|
+
function App() {
|
|
77
|
+
const [range, setRange] = useState({
|
|
78
|
+
start: null,
|
|
79
|
+
end: null,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<DatePicker
|
|
84
|
+
mode="range"
|
|
85
|
+
value={range}
|
|
86
|
+
onChange={(value) => setRange(value)}
|
|
87
|
+
numberOfMonths={2}
|
|
88
|
+
/>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Preset Range Example
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
import { DatePicker } from "snack-datepicker";
|
|
97
|
+
|
|
98
|
+
const presets = [
|
|
99
|
+
{
|
|
100
|
+
label: "Last 7 Days",
|
|
101
|
+
getValue: () => {
|
|
102
|
+
const end = new Date();
|
|
103
|
+
const start = new Date();
|
|
104
|
+
start.setDate(end.getDate() - 7);
|
|
105
|
+
return { start, end };
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
label: "Last 30 Days",
|
|
110
|
+
getValue: () => {
|
|
111
|
+
const end = new Date();
|
|
112
|
+
const start = new Date();
|
|
113
|
+
start.setDate(end.getDate() - 30);
|
|
114
|
+
return { start, end };
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
function App() {
|
|
120
|
+
return <DatePicker mode="range" presets={presets} />;
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Props API
|
|
125
|
+
|
|
126
|
+
| Prop | Type | Description |
|
|
127
|
+
| ---------------- | --------------------------- | --------------------------------------- |
|
|
128
|
+
| `mode` | `"single" \| "range"` | Date picker mode |
|
|
129
|
+
| `value` | `Date \| DateRange` | Current selected value |
|
|
130
|
+
| `onChange` | `(value) => void` | Triggered when date changes |
|
|
131
|
+
| `onApply` | `(value) => void` | Triggered when apply button is clicked |
|
|
132
|
+
| `onReset` | `() => void` | Reset the current selection |
|
|
133
|
+
| `minDate` | `Date` | Minimum selectable date |
|
|
134
|
+
| `maxDate` | `Date` | Maximum selectable date |
|
|
135
|
+
| `disabledDates` | `Date[]` | Disable specific dates |
|
|
136
|
+
| `weekStart` | `0 \| 1` | Week start day (0 = Sunday, 1 = Monday) |
|
|
137
|
+
| `numberOfMonths` | `number` | Number of months displayed at once |
|
|
138
|
+
| `showFooter` | `boolean` | Show apply / reset footer |
|
|
139
|
+
| `className` | `string` | Custom CSS class for styling |
|
|
140
|
+
| `renderDay` | `(date: Date) => ReactNode` | Custom day cell renderer |
|
|
141
|
+
| `presets` | `PresetRange[]` | Preset date range options |
|
|
142
|
+
|
|
143
|
+
### Types
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
export type DatePickerMode = "single" | "range";
|
|
147
|
+
|
|
148
|
+
export interface DateRange {
|
|
149
|
+
start: Date | null;
|
|
150
|
+
end: Date | null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export interface PresetRange {
|
|
154
|
+
label: string;
|
|
155
|
+
getValue: () => DateRange;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface DatePickerProps {
|
|
159
|
+
mode?: DatePickerMode;
|
|
160
|
+
value?: Date | DateRange;
|
|
161
|
+
onChange?: (value: Date | DateRange) => void;
|
|
162
|
+
onApply?: (value: Date | DateRange | undefined) => void;
|
|
163
|
+
onReset?: () => void;
|
|
164
|
+
minDate?: Date;
|
|
165
|
+
maxDate?: Date;
|
|
166
|
+
disabledDates?: Date[];
|
|
167
|
+
weekStart?: 0 | 1;
|
|
168
|
+
numberOfMonths?: number;
|
|
169
|
+
showFooter?: boolean;
|
|
170
|
+
className?: string;
|
|
171
|
+
renderDay?: (date: Date) => ReactNode;
|
|
172
|
+
presets?: PresetRange[];
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Built With
|
|
177
|
+
|
|
178
|
+
[React](https://react.dev) · [Radix UI](https://www.radix-ui.com) · [date-fns](https://date-fns.org) · [TailwindCSS](https://tailwindcss.com) · [Framer Motion](https://www.framer.com/motion) · [Lucide Icons](https://lucide.dev)
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
|
|
182
|
+
MIT
|
|
183
|
+
|
|
184
|
+
## Author
|
|
185
|
+
|
|
186
|
+
**Hemanth Dev**
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React, { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
type DatePickerMode = "single" | "range";
|
|
4
|
+
interface DateRange {
|
|
5
|
+
start: Date | null;
|
|
6
|
+
end: Date | null;
|
|
7
|
+
}
|
|
8
|
+
interface PresetRange {
|
|
9
|
+
label: string;
|
|
10
|
+
getValue: () => DateRange;
|
|
11
|
+
}
|
|
12
|
+
interface DatePickerProps {
|
|
13
|
+
mode?: DatePickerMode;
|
|
14
|
+
value?: Date | DateRange;
|
|
15
|
+
onChange?: (value: Date | DateRange) => void;
|
|
16
|
+
onApply?: (value: Date | DateRange | undefined) => void;
|
|
17
|
+
onReset?: () => void;
|
|
18
|
+
minDate?: Date;
|
|
19
|
+
maxDate?: Date;
|
|
20
|
+
disabledDates?: Date[];
|
|
21
|
+
weekStart?: 0 | 1;
|
|
22
|
+
numberOfMonths?: number;
|
|
23
|
+
showFooter?: boolean;
|
|
24
|
+
className?: string;
|
|
25
|
+
renderDay?: (date: Date) => ReactNode;
|
|
26
|
+
presets?: PresetRange[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
declare const DatePicker: React.ForwardRefExoticComponent<DatePickerProps & React.RefAttributes<HTMLDivElement>>;
|
|
30
|
+
|
|
31
|
+
export { DatePicker, type DatePickerProps };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React, { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
type DatePickerMode = "single" | "range";
|
|
4
|
+
interface DateRange {
|
|
5
|
+
start: Date | null;
|
|
6
|
+
end: Date | null;
|
|
7
|
+
}
|
|
8
|
+
interface PresetRange {
|
|
9
|
+
label: string;
|
|
10
|
+
getValue: () => DateRange;
|
|
11
|
+
}
|
|
12
|
+
interface DatePickerProps {
|
|
13
|
+
mode?: DatePickerMode;
|
|
14
|
+
value?: Date | DateRange;
|
|
15
|
+
onChange?: (value: Date | DateRange) => void;
|
|
16
|
+
onApply?: (value: Date | DateRange | undefined) => void;
|
|
17
|
+
onReset?: () => void;
|
|
18
|
+
minDate?: Date;
|
|
19
|
+
maxDate?: Date;
|
|
20
|
+
disabledDates?: Date[];
|
|
21
|
+
weekStart?: 0 | 1;
|
|
22
|
+
numberOfMonths?: number;
|
|
23
|
+
showFooter?: boolean;
|
|
24
|
+
className?: string;
|
|
25
|
+
renderDay?: (date: Date) => ReactNode;
|
|
26
|
+
presets?: PresetRange[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
declare const DatePicker: React.ForwardRefExoticComponent<DatePickerProps & React.RefAttributes<HTMLDivElement>>;
|
|
30
|
+
|
|
31
|
+
export { DatePicker, type DatePickerProps };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var React = require('react');
|
|
4
|
+
var dateFns = require('date-fns');
|
|
5
|
+
var lucideReact = require('lucide-react');
|
|
6
|
+
var framerMotion = require('framer-motion');
|
|
7
|
+
var clsx = require('clsx');
|
|
8
|
+
var tailwindMerge = require('tailwind-merge');
|
|
9
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
10
|
+
|
|
11
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
|
+
|
|
13
|
+
var React__default = /*#__PURE__*/_interopDefault(React);
|
|
14
|
+
|
|
15
|
+
// src/components/date-picker/DatePicker.tsx
|
|
16
|
+
function cn(...inputs) {
|
|
17
|
+
return tailwindMerge.twMerge(clsx.clsx(inputs));
|
|
18
|
+
}
|
|
19
|
+
var WEEK_DAYS_SUN = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
|
20
|
+
var WEEK_DAYS_MON = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];
|
|
21
|
+
var MonthGrid = ({
|
|
22
|
+
month,
|
|
23
|
+
mode,
|
|
24
|
+
selectedDate,
|
|
25
|
+
selectedRange,
|
|
26
|
+
hoverDate,
|
|
27
|
+
onDateClick,
|
|
28
|
+
onDateHover,
|
|
29
|
+
minDate,
|
|
30
|
+
maxDate,
|
|
31
|
+
disabledDates = [],
|
|
32
|
+
weekStart = 0,
|
|
33
|
+
renderDay,
|
|
34
|
+
showLabel = true
|
|
35
|
+
}) => {
|
|
36
|
+
const days = React.useMemo(() => {
|
|
37
|
+
return dateFns.eachDayOfInterval({
|
|
38
|
+
start: dateFns.startOfMonth(month),
|
|
39
|
+
end: dateFns.endOfMonth(month)
|
|
40
|
+
});
|
|
41
|
+
}, [month]);
|
|
42
|
+
const weekDays = weekStart === 1 ? WEEK_DAYS_MON : WEEK_DAYS_SUN;
|
|
43
|
+
const startPad = React.useMemo(() => {
|
|
44
|
+
const dayOfWeek = days[0].getDay();
|
|
45
|
+
return weekStart === 1 ? dayOfWeek === 0 ? 6 : dayOfWeek - 1 : dayOfWeek;
|
|
46
|
+
}, [days, weekStart]);
|
|
47
|
+
const isDisabled = (date) => {
|
|
48
|
+
if (minDate && dateFns.isBefore(date, minDate)) return true;
|
|
49
|
+
if (maxDate && dateFns.isAfter(date, maxDate)) return true;
|
|
50
|
+
return disabledDates.some((d) => dateFns.isSameDay(d, date));
|
|
51
|
+
};
|
|
52
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-[252px]", children: [
|
|
53
|
+
showLabel && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center mb-3", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium text-dp-text", children: dateFns.format(month, "MMMM yyyy") }) }),
|
|
54
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-7 mb-1.5", children: weekDays.map((d) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
55
|
+
"span",
|
|
56
|
+
{
|
|
57
|
+
className: "text-[11px] font-semibold text-dp-text-muted text-center uppercase tracking-wider py-1",
|
|
58
|
+
children: d
|
|
59
|
+
},
|
|
60
|
+
d
|
|
61
|
+
)) }),
|
|
62
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-7", children: [
|
|
63
|
+
[...Array(startPad)].map((_, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-9 w-9" }, `pad-${i}`)),
|
|
64
|
+
days.map((day) => {
|
|
65
|
+
const disabled = isDisabled(day);
|
|
66
|
+
const isSelected = mode === "single" ? selectedDate && dateFns.isSameDay(day, selectedDate) : selectedRange && (selectedRange.start && dateFns.isSameDay(day, selectedRange.start) || selectedRange.end && dateFns.isSameDay(day, selectedRange.end));
|
|
67
|
+
const isStart = mode === "range" && (selectedRange == null ? void 0 : selectedRange.start) && dateFns.isSameDay(day, selectedRange.start);
|
|
68
|
+
const isEnd = mode === "range" && (selectedRange == null ? void 0 : selectedRange.end) && dateFns.isSameDay(day, selectedRange.end);
|
|
69
|
+
let inRange = false;
|
|
70
|
+
if (mode === "range" && (selectedRange == null ? void 0 : selectedRange.start)) {
|
|
71
|
+
if (selectedRange.end) {
|
|
72
|
+
inRange = dateFns.isWithinInterval(day, {
|
|
73
|
+
start: selectedRange.start,
|
|
74
|
+
end: selectedRange.end
|
|
75
|
+
});
|
|
76
|
+
} else if (hoverDate) {
|
|
77
|
+
const rangeStart = dateFns.isBefore(hoverDate, selectedRange.start) ? hoverDate : selectedRange.start;
|
|
78
|
+
const rangeEnd = dateFns.isBefore(hoverDate, selectedRange.start) ? selectedRange.start : hoverDate;
|
|
79
|
+
inRange = dateFns.isWithinInterval(day, {
|
|
80
|
+
start: rangeStart,
|
|
81
|
+
end: rangeEnd
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
86
|
+
"button",
|
|
87
|
+
{
|
|
88
|
+
type: "button",
|
|
89
|
+
disabled,
|
|
90
|
+
onClick: () => !disabled && onDateClick(day),
|
|
91
|
+
onMouseEnter: () => !disabled && onDateHover(day),
|
|
92
|
+
onMouseLeave: () => onDateHover(null),
|
|
93
|
+
"aria-label": dateFns.format(day, "PPPP"),
|
|
94
|
+
"aria-selected": !!isSelected,
|
|
95
|
+
className: cn(
|
|
96
|
+
"relative h-9 w-9 text-sm flex items-center justify-center transition-all tabular-nums",
|
|
97
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
|
|
98
|
+
"active:scale-[0.96]",
|
|
99
|
+
// Default
|
|
100
|
+
!isSelected && !inRange && "text-dp-text hover:bg-dp-surface-hover rounded-lg",
|
|
101
|
+
// Selected (start/end/single)
|
|
102
|
+
isSelected && "bg-dp-surface-active text-dp-text-selected z-10 shadow-sm font-semibold",
|
|
103
|
+
// Single mode selected
|
|
104
|
+
mode === "single" && isSelected && "rounded-lg",
|
|
105
|
+
// Range endpoints
|
|
106
|
+
isStart && !isEnd && "rounded-l-lg rounded-r-none",
|
|
107
|
+
isEnd && !isStart && "rounded-r-lg rounded-l-none",
|
|
108
|
+
isStart && isEnd && "rounded-lg",
|
|
109
|
+
// In range but not selected
|
|
110
|
+
inRange && !isSelected && "bg-[hsl(var(--dp-range-tint)/0.08)] text-primary rounded-none",
|
|
111
|
+
// Disabled
|
|
112
|
+
disabled && "text-dp-text-muted/40 cursor-not-allowed hover:bg-transparent"
|
|
113
|
+
),
|
|
114
|
+
children: renderDay ? renderDay(day) : dateFns.format(day, "d")
|
|
115
|
+
},
|
|
116
|
+
day.toISOString()
|
|
117
|
+
);
|
|
118
|
+
})
|
|
119
|
+
] })
|
|
120
|
+
] });
|
|
121
|
+
};
|
|
122
|
+
var MonthGrid_default = React__default.default.memo(MonthGrid);
|
|
123
|
+
var MonthSelector = ({
|
|
124
|
+
currentMonth,
|
|
125
|
+
onSelect
|
|
126
|
+
}) => {
|
|
127
|
+
const current = dateFns.getMonth(currentMonth);
|
|
128
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
129
|
+
framerMotion.motion.div,
|
|
130
|
+
{
|
|
131
|
+
initial: { opacity: 0, y: 4 },
|
|
132
|
+
animate: { opacity: 1, y: 0 },
|
|
133
|
+
exit: { opacity: 0, y: -4 },
|
|
134
|
+
transition: { duration: 0.15, ease: [0.25, 0.1, 0.25, 1] },
|
|
135
|
+
className: "grid grid-cols-3 gap-2 w-full max-w-[320px] mx-auto",
|
|
136
|
+
children: Array.from({ length: 12 }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
137
|
+
"button",
|
|
138
|
+
{
|
|
139
|
+
type: "button",
|
|
140
|
+
onClick: () => onSelect(i),
|
|
141
|
+
className: cn(
|
|
142
|
+
"py-3 text-sm font-medium rounded-lg transition-all active:scale-[0.97]",
|
|
143
|
+
current === i ? "bg-dp-surface-active text-dp-text-selected shadow-sm" : "hover:bg-dp-surface-hover text-dp-text"
|
|
144
|
+
),
|
|
145
|
+
children: dateFns.format(new Date(2024, i, 1), "MMM")
|
|
146
|
+
},
|
|
147
|
+
i
|
|
148
|
+
))
|
|
149
|
+
},
|
|
150
|
+
"month-selector"
|
|
151
|
+
);
|
|
152
|
+
};
|
|
153
|
+
var MonthSelector_default = MonthSelector;
|
|
154
|
+
var YearSelector = ({
|
|
155
|
+
currentYear,
|
|
156
|
+
onSelect,
|
|
157
|
+
minDate,
|
|
158
|
+
maxDate
|
|
159
|
+
}) => {
|
|
160
|
+
const years = Array.from({ length: 12 }, (_, i) => currentYear - 5 + i);
|
|
161
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
162
|
+
framerMotion.motion.div,
|
|
163
|
+
{
|
|
164
|
+
initial: { opacity: 0, y: 4 },
|
|
165
|
+
animate: { opacity: 1, y: 0 },
|
|
166
|
+
exit: { opacity: 0, y: -4 },
|
|
167
|
+
transition: { duration: 0.15, ease: [0.25, 0.1, 0.25, 1] },
|
|
168
|
+
className: "grid grid-cols-3 gap-2 w-full max-w-[320px] mx-auto",
|
|
169
|
+
children: years.map((y) => {
|
|
170
|
+
const disabled = minDate && y < minDate.getFullYear() || maxDate && y > maxDate.getFullYear();
|
|
171
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
172
|
+
"button",
|
|
173
|
+
{
|
|
174
|
+
type: "button",
|
|
175
|
+
disabled: !!disabled,
|
|
176
|
+
onClick: () => !disabled && onSelect(y),
|
|
177
|
+
className: cn(
|
|
178
|
+
"py-3 text-sm font-medium rounded-lg transition-all active:scale-[0.97]",
|
|
179
|
+
currentYear === y ? "bg-dp-surface-active text-dp-text-selected shadow-sm" : "hover:bg-dp-surface-hover text-dp-text",
|
|
180
|
+
disabled && "opacity-30 cursor-not-allowed"
|
|
181
|
+
),
|
|
182
|
+
children: y
|
|
183
|
+
},
|
|
184
|
+
y
|
|
185
|
+
);
|
|
186
|
+
})
|
|
187
|
+
},
|
|
188
|
+
"year-selector"
|
|
189
|
+
);
|
|
190
|
+
};
|
|
191
|
+
var YearSelector_default = YearSelector;
|
|
192
|
+
var DatePicker = React__default.default.forwardRef(
|
|
193
|
+
({
|
|
194
|
+
mode = "single",
|
|
195
|
+
value,
|
|
196
|
+
onChange,
|
|
197
|
+
onApply,
|
|
198
|
+
onReset,
|
|
199
|
+
minDate,
|
|
200
|
+
maxDate,
|
|
201
|
+
disabledDates,
|
|
202
|
+
weekStart = 0,
|
|
203
|
+
numberOfMonths = 2,
|
|
204
|
+
showFooter = true,
|
|
205
|
+
className,
|
|
206
|
+
renderDay,
|
|
207
|
+
presets
|
|
208
|
+
}, ref) => {
|
|
209
|
+
const [viewDate, setViewDate] = React.useState(() => {
|
|
210
|
+
if (mode === "single" && value instanceof Date) return value;
|
|
211
|
+
if (mode === "range" && value && "start" in value && value.start)
|
|
212
|
+
return value.start;
|
|
213
|
+
return /* @__PURE__ */ new Date();
|
|
214
|
+
});
|
|
215
|
+
const [viewMode, setViewMode] = React.useState("calendar");
|
|
216
|
+
const [hoverDate, setHoverDate] = React.useState(null);
|
|
217
|
+
const selectedRange = mode === "range" ? value != null ? value : null : null;
|
|
218
|
+
const selectedDate = mode === "single" ? value != null ? value : null : null;
|
|
219
|
+
const handleDateClick = React.useCallback(
|
|
220
|
+
(date) => {
|
|
221
|
+
if (mode === "single") {
|
|
222
|
+
onChange == null ? void 0 : onChange(date);
|
|
223
|
+
} else {
|
|
224
|
+
const range = selectedRange || { start: null, end: null };
|
|
225
|
+
if (!range.start || range.start && range.end) {
|
|
226
|
+
onChange == null ? void 0 : onChange({ start: date, end: null });
|
|
227
|
+
} else {
|
|
228
|
+
if (dateFns.isBefore(date, range.start)) {
|
|
229
|
+
onChange == null ? void 0 : onChange({ start: date, end: range.start });
|
|
230
|
+
} else if (dateFns.isSameDay(date, range.start)) {
|
|
231
|
+
onChange == null ? void 0 : onChange({ start: date, end: date });
|
|
232
|
+
} else {
|
|
233
|
+
onChange == null ? void 0 : onChange({ start: range.start, end: date });
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
[mode, selectedRange, onChange]
|
|
239
|
+
);
|
|
240
|
+
const handleKeyDown = React.useCallback((e) => {
|
|
241
|
+
if (e.key === "Escape") {
|
|
242
|
+
setViewMode("calendar");
|
|
243
|
+
}
|
|
244
|
+
}, []);
|
|
245
|
+
const handlePresetClick = React.useCallback(
|
|
246
|
+
(preset) => {
|
|
247
|
+
const range = preset.getValue();
|
|
248
|
+
onChange == null ? void 0 : onChange(range);
|
|
249
|
+
},
|
|
250
|
+
[onChange]
|
|
251
|
+
);
|
|
252
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
253
|
+
"div",
|
|
254
|
+
{
|
|
255
|
+
ref,
|
|
256
|
+
className: cn(
|
|
257
|
+
"inline-flex flex-col rounded-xl border border-dp-border bg-dp-surface select-none antialiased",
|
|
258
|
+
"shadow-[var(--dp-shadow)]",
|
|
259
|
+
className
|
|
260
|
+
),
|
|
261
|
+
onKeyDown: handleKeyDown,
|
|
262
|
+
role: "application",
|
|
263
|
+
"aria-label": "Date picker",
|
|
264
|
+
children: [
|
|
265
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-b border-dp-border", children: [
|
|
266
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
267
|
+
"button",
|
|
268
|
+
{
|
|
269
|
+
type: "button",
|
|
270
|
+
onClick: () => setViewDate(dateFns.subMonths(viewDate, 1)),
|
|
271
|
+
className: "p-1.5 rounded-md hover:bg-dp-surface-hover text-dp-text-muted hover:text-dp-text transition-colors",
|
|
272
|
+
"aria-label": "Previous month",
|
|
273
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { size: 16 })
|
|
274
|
+
}
|
|
275
|
+
),
|
|
276
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
277
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
278
|
+
"button",
|
|
279
|
+
{
|
|
280
|
+
type: "button",
|
|
281
|
+
onClick: () => setViewMode(viewMode === "month" ? "calendar" : "month"),
|
|
282
|
+
className: cn(
|
|
283
|
+
"text-sm font-semibold px-2 py-1 rounded-md transition-colors",
|
|
284
|
+
viewMode === "month" ? "bg-dp-surface-hover text-dp-text" : "hover:bg-dp-surface-hover text-dp-text"
|
|
285
|
+
),
|
|
286
|
+
children: dateFns.format(viewDate, "MMMM")
|
|
287
|
+
}
|
|
288
|
+
),
|
|
289
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
290
|
+
"button",
|
|
291
|
+
{
|
|
292
|
+
type: "button",
|
|
293
|
+
onClick: () => setViewMode(viewMode === "year" ? "calendar" : "year"),
|
|
294
|
+
className: cn(
|
|
295
|
+
"text-sm font-semibold px-2 py-1 rounded-md transition-colors",
|
|
296
|
+
viewMode === "year" ? "bg-dp-surface-hover text-dp-text" : "hover:bg-dp-surface-hover text-dp-text"
|
|
297
|
+
),
|
|
298
|
+
children: dateFns.format(viewDate, "yyyy")
|
|
299
|
+
}
|
|
300
|
+
)
|
|
301
|
+
] }),
|
|
302
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
303
|
+
"button",
|
|
304
|
+
{
|
|
305
|
+
type: "button",
|
|
306
|
+
onClick: () => setViewDate(dateFns.addMonths(viewDate, 1)),
|
|
307
|
+
className: "p-1.5 rounded-md hover:bg-dp-surface-hover text-dp-text-muted hover:text-dp-text transition-colors",
|
|
308
|
+
"aria-label": "Next month",
|
|
309
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { size: 16 })
|
|
310
|
+
}
|
|
311
|
+
)
|
|
312
|
+
] }),
|
|
313
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex", children: [
|
|
314
|
+
presets && presets.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-r border-dp-border p-3 min-w-[140px] flex flex-col gap-0.5", children: presets.map((preset) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
315
|
+
"button",
|
|
316
|
+
{
|
|
317
|
+
type: "button",
|
|
318
|
+
onClick: () => handlePresetClick(preset),
|
|
319
|
+
className: "text-left text-xs font-medium px-2.5 py-1.5 rounded-md text-dp-text-muted hover:text-dp-text hover:bg-dp-surface-hover transition-colors",
|
|
320
|
+
children: preset.label
|
|
321
|
+
},
|
|
322
|
+
preset.label
|
|
323
|
+
)) }),
|
|
324
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 flex-1", children: /* @__PURE__ */ jsxRuntime.jsxs(framerMotion.AnimatePresence, { mode: "wait", children: [
|
|
325
|
+
viewMode === "calendar" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
326
|
+
framerMotion.motion.div,
|
|
327
|
+
{
|
|
328
|
+
initial: { opacity: 0, y: 4 },
|
|
329
|
+
animate: { opacity: 1, y: 0 },
|
|
330
|
+
exit: { opacity: 0, y: -4 },
|
|
331
|
+
transition: { duration: 0.15, ease: [0.25, 0.1, 0.25, 1] },
|
|
332
|
+
className: "flex flex-col md:flex-row gap-6",
|
|
333
|
+
children: [...Array(numberOfMonths)].map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
334
|
+
MonthGrid_default,
|
|
335
|
+
{
|
|
336
|
+
month: dateFns.addMonths(viewDate, i),
|
|
337
|
+
mode,
|
|
338
|
+
selectedDate,
|
|
339
|
+
selectedRange,
|
|
340
|
+
hoverDate,
|
|
341
|
+
onDateClick: handleDateClick,
|
|
342
|
+
onDateHover: setHoverDate,
|
|
343
|
+
minDate,
|
|
344
|
+
maxDate,
|
|
345
|
+
disabledDates,
|
|
346
|
+
weekStart,
|
|
347
|
+
renderDay,
|
|
348
|
+
showLabel: numberOfMonths > 1
|
|
349
|
+
},
|
|
350
|
+
i
|
|
351
|
+
))
|
|
352
|
+
},
|
|
353
|
+
"calendar"
|
|
354
|
+
),
|
|
355
|
+
viewMode === "month" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
356
|
+
MonthSelector_default,
|
|
357
|
+
{
|
|
358
|
+
currentMonth: viewDate,
|
|
359
|
+
onSelect: (m) => {
|
|
360
|
+
setViewDate(dateFns.setMonth(viewDate, m));
|
|
361
|
+
setViewMode("calendar");
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
),
|
|
365
|
+
viewMode === "year" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
366
|
+
YearSelector_default,
|
|
367
|
+
{
|
|
368
|
+
currentYear: dateFns.getYear(viewDate),
|
|
369
|
+
onSelect: (y) => {
|
|
370
|
+
setViewDate(dateFns.setYear(viewDate, y));
|
|
371
|
+
setViewMode("calendar");
|
|
372
|
+
},
|
|
373
|
+
minDate,
|
|
374
|
+
maxDate
|
|
375
|
+
}
|
|
376
|
+
)
|
|
377
|
+
] }) })
|
|
378
|
+
] }),
|
|
379
|
+
showFooter && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-4 py-3 bg-dp-footer border-t border-dp-border rounded-b-xl", children: [
|
|
380
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
381
|
+
"button",
|
|
382
|
+
{
|
|
383
|
+
type: "button",
|
|
384
|
+
onClick: onReset,
|
|
385
|
+
className: "flex items-center gap-1.5 text-xs font-medium text-dp-text-muted hover:text-dp-text transition-colors",
|
|
386
|
+
children: [
|
|
387
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.RotateCcw, { size: 13 }),
|
|
388
|
+
"Reset"
|
|
389
|
+
]
|
|
390
|
+
}
|
|
391
|
+
),
|
|
392
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
393
|
+
"button",
|
|
394
|
+
{
|
|
395
|
+
type: "button",
|
|
396
|
+
onClick: () => onApply == null ? void 0 : onApply(value),
|
|
397
|
+
className: "px-5 py-1.5 bg-primary text-primary-foreground text-xs font-semibold rounded hover:opacity-90 shadow-sm transition-all active:scale-[0.98]",
|
|
398
|
+
children: "Apply"
|
|
399
|
+
}
|
|
400
|
+
)
|
|
401
|
+
] })
|
|
402
|
+
]
|
|
403
|
+
}
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
);
|
|
407
|
+
DatePicker.displayName = "DatePicker";
|
|
408
|
+
|
|
409
|
+
exports.DatePicker = DatePicker;
|
|
410
|
+
//# sourceMappingURL=index.js.map
|
|
411
|
+
//# sourceMappingURL=index.js.map
|