daliry-mobile-date-picker 1.0.0 → 1.0.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.
package/.editorconfig CHANGED
@@ -1,10 +1,10 @@
1
- root = true
2
-
3
- [*]
4
- end_of_line = lf
5
- insert_final_newline = true
6
-
7
- [*.{js,json,yml}]
8
- charset = utf-8
9
- indent_style = space
10
- indent_size = 2
1
+ root = true
2
+
3
+ [*]
4
+ end_of_line = lf
5
+ insert_final_newline = true
6
+
7
+ [*.{js,json,yml}]
8
+ charset = utf-8
9
+ indent_style = space
10
+ indent_size = 2
package/.gitattributes CHANGED
@@ -1,4 +1,4 @@
1
- /.yarn/** linguist-vendored
2
- /.yarn/releases/* binary
3
- /.yarn/plugins/**/* binary
4
- /.pnp.* binary linguist-generated
1
+ /.yarn/** linguist-vendored
2
+ /.yarn/releases/* binary
3
+ /.yarn/plugins/**/* binary
4
+ /.pnp.* binary linguist-generated
package/.yarnrc.yml CHANGED
@@ -1 +1 @@
1
- nodeLinker: "node-modules"
1
+ nodeLinker: "node-modules"
package/README.md CHANGED
@@ -1,26 +1,138 @@
1
- # 📅 Mobile Date Picker
2
-
3
- A mobile-first, customizable **Jalali (Shamsi) + Gregorian** date picker for React.
4
- Easily select and receive dates in both Persian and Gregorian formats with detailed breakdowns.
5
-
6
- ---
7
-
8
- ## ✨ Features
9
-
10
- - 📆 **Dual calendar**: Jalali (Shamsi) + Gregorian
11
- - 🎯 Designed for **mobile usage**
12
- - ⚙️ Simple API with a reusable React component
13
- - 📤 Returns multiple formats for selected date:
14
- - Jalali year/month/day
15
- - Formatted Jalali string
16
- - Formatted Gregorian string
17
- - Moment.js-compatible UTC string
18
-
19
- ---
20
-
21
- ## 📦 Installation
22
-
23
- Install using npm or yarn:
24
-
25
- ```bash
26
- npm install mobile-date-picker
1
+ # 📅 Daliry Mobile Date Picker – React 19 Mobile Date Picker with Jalali & Gregorian Support
2
+
3
+ **Daliry Mobile Date Picker** is a mobile-friendly and fully customizable date picker component for **React 19** that supports both **Jalali (Shamsi)** and **Gregorian** calendars.
4
+ Perfect for **mobile apps**, **birthdate pickers**, and any React project that needs a clean, touch-optimized date selection experience.
5
+
6
+ ![Daliry Mobile Date Picker Screenshot](https://github.com/daliryapp/mobile-date-picker/blob/master/src/assets/images/daliry-mobile-date-picker102.PNG?raw=true)
7
+
8
+ ---
9
+
10
+ ## 💖 Support
11
+
12
+ If you were thinking about donating, the best way to support me for now is to **follow me on GitHub** and **star this project**.
13
+ It really helps me keep building and improving open-source components.
14
+
15
+ 👉 [Follow me on GitHub](https://github.com/daliryapp)
16
+
17
+ ---
18
+ ## 🧠 Why Daliry?
19
+
20
+ If you're searching for a **mobile date picker for React** with **Jalali (Shamsi)** support, **Daliry Mobile Date Picker** is built exactly for that:
21
+
22
+ - Supports **React 19**
23
+ - Dual calendar: **Jalali (Persian)** and **Gregorian**
24
+ - Ideal for **mobile UX**
25
+ - Easy to plug into any **React project**
26
+ - Returns both formatted and raw date values
27
+ - Compatible with **Moment.js** for easy integration
28
+ - Uses **debounced** `onDateChange` calls for smoother performance
29
+ - No need for an extra **Apply** button
30
+
31
+ ---
32
+
33
+ ## ✨ Key Features
34
+
35
+ - 📆 Dual calendar support: Jalali (Shamsi) and Gregorian
36
+ - ⚛️ Built for **React 19**
37
+ - 📱 Touch-optimized for mobile and tablet
38
+ - ♻️ Reusable, lightweight React component
39
+ - 🔧 Minimal setup, easy customization
40
+ - ⚡ Debounced date change callback after user stops scrolling
41
+ - 🎂 Smart birthdate mode with default value set to **18 years ago**
42
+ - 📤 Output includes full date breakdown in multiple formats
43
+
44
+ ---
45
+
46
+ ## 📦 Installation
47
+
48
+ Install the date picker via npm or yarn:
49
+ ```bash
50
+ npm install daliry-mobile-date-picker
51
+
52
+ or
53
+
54
+ bash
55
+ yarn add daliry-mobile-date-picker
56
+
57
+ ---
58
+
59
+ ## ✨ Usage
60
+
61
+ tsx
62
+ import MobileDatePicker from "daliry-mobile-date-picker";
63
+ import "daliry-mobile-date-picker/dist/index.css";
64
+
65
+ <MobileDatePicker
66
+ onDateChange={(value) => {
67
+ console.log(value);
68
+ }}
69
+ isBirthdate={true}
70
+ isGregorian={false}
71
+ />
72
+
73
+ ---
74
+
75
+ ## 🛠 Props
76
+
77
+ | Prop | Type | Required | Default | Description |
78
+ |------|------|----------|---------|-------------|
79
+ | `onDateChange` | `(date: IDate) => void` | Yes | - | Called after the user finishes changing the date |
80
+ | `isBirthdate` | `boolean` | No | `false` | If true, default date is set to 18 years ago |
81
+ | `isGregorian` | `boolean` | No | `false` | Switches calendar mode to Gregorian |
82
+ | `backgroundColor` | `string` | No | `#f5f5f5` | Background color of picker |
83
+ | `textColor` | `string` | No | `#bbb` | Color of non-selected items |
84
+ | `selectedColor` | `string` | No | `#333` | Color of selected item |
85
+
86
+ ---
87
+
88
+ ## ⚡ Debounced Date Change
89
+
90
+ This component no longer needs an "Apply" button.
91
+
92
+ When the user scrolls and changes the date, `onDateChange` is called automatically with a small **debounce delay** after scrolling stops. This helps prevent multiple unnecessary callback executions during fast scrolling and improves mobile performance.
93
+
94
+ ---
95
+
96
+ ## 📤 Output Format
97
+
98
+ ts
99
+ {
100
+ year: 1403,
101
+ month: 3,
102
+ day: 21,
103
+ formatted: "1403/03/21",
104
+ date: "2024-06-10",
105
+ gDate: "2024-06-10",
106
+ moment: Moment
107
+ }
108
+
109
+ ---
110
+
111
+ ## 📘 Output Fields
112
+
113
+ - `year`: selected year
114
+ - `month`: selected month
115
+ - `day`: selected day
116
+ - `formatted`: formatted date based on selected calendar
117
+ - `date`: Gregorian date in `YYYY-MM-DD`
118
+ - `gDate`: Gregorian date in `YYYY-MM-DD`
119
+ - `moment`: Moment object for advanced usage
120
+
121
+ ---
122
+
123
+ ## ✅ Notes
124
+
125
+ - Supports both **Jalali** and **Gregorian** calendars
126
+ - CSS file should also be included:
127
+
128
+ tsx
129
+ import "daliry-mobile-date-picker/dist/index.css";
130
+
131
+ - Designed for a smooth mobile scrolling experience
132
+ - Optimized selected-item animation behavior for better visual stability
133
+
134
+ ---
135
+
136
+ ## 📜 License
137
+
138
+ MIT
package/build.js CHANGED
@@ -1,15 +1,15 @@
1
- const esbuild = require("esbuild");
2
-
3
- esbuild
4
- .build({
5
- entryPoints: ["src/MobileDatePicker.tsx"],
6
- outfile: "dist/index.js",
7
- bundle: true,
8
- minify: false,
9
- sourcemap: false,
10
- format: "esm", // یا "cjs" برای CommonJS
11
- target: ["esnext"],
12
- // REMOVE this line
13
- external: ["react", "react-dom"], // این خط را حذف کن
14
- })
15
- .catch(() => process.exit(1));
1
+ const esbuild = require("esbuild");
2
+
3
+ esbuild
4
+ .build({
5
+ entryPoints: ["src/MobileDatePicker.tsx"],
6
+ outfile: "dist/index.js",
7
+ bundle: true,
8
+ minify: false,
9
+ sourcemap: false,
10
+ format: "esm", // یا "cjs" برای CommonJS
11
+ target: ["esnext"],
12
+ // REMOVE this line
13
+ external: ["react", "react-dom"], // این خط را حذف کن
14
+ })
15
+ .catch(() => process.exit(1));
@@ -1,10 +1,10 @@
1
- import { Moment } from "moment-jalaali";
1
+ import type { Moment } from "moment";
2
2
  import "./mobileDatePicker.css";
3
3
  export interface IDate {
4
- jYear: number;
5
- jMonth: number;
6
- jDay: number;
7
- jDate: string;
4
+ year: number;
5
+ month: number;
6
+ day: number;
7
+ formatted: string;
8
8
  date: string;
9
9
  gDate: string;
10
10
  moment: Moment;
@@ -12,6 +12,10 @@ export interface IDate {
12
12
  interface IMobileDatePicker {
13
13
  onDateChange: (date: IDate) => void;
14
14
  isBirthdate?: boolean;
15
+ isGregorian?: boolean;
16
+ backgroundColor?: string;
17
+ textColor?: string;
18
+ selectedColor?: string;
15
19
  }
16
- declare const MobileDatePicker: ({ onDateChange, isBirthdate }: IMobileDatePicker) => import("react/jsx-runtime").JSX.Element;
17
- export default MobileDatePicker;
20
+ declare const DaliryMobileDatePicker: ({ onDateChange, isBirthdate, isGregorian, backgroundColor, textColor, selectedColor, }: IMobileDatePicker) => import("react/jsx-runtime").JSX.Element;
21
+ export default DaliryMobileDatePicker;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MobileDatePicker.d.ts","sourceRoot":"","sources":["../src/MobileDatePicker.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,wBAAwB,CAAC;AAEhC,MAAM,WAAW,KAAK;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,iBAAiB;IACvB,YAAY,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,IAAI,CAAC;IACpC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B;AAOD,QAAA,MAAM,sBAAsB,GAAI,wFAOH,iBAAiB,4CAuI7C,CAAC;AAEF,eAAe,sBAAsB,CAAC"}
@@ -0,0 +1,105 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useRef, useState } from "react";
3
+ import moment from "moment-jalaali";
4
+ import "./mobileDatePicker.css";
5
+ const JALALI_MONTHS = ["فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند"];
6
+ const GREGORIAN_MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
7
+ const ITEM_HEIGHT = 45;
8
+ const DaliryMobileDatePicker = ({ onDateChange, isBirthdate = false, isGregorian = false, backgroundColor = "#f5f5f5", textColor = "#bbb", selectedColor = "#333", }) => {
9
+ const now = moment();
10
+ const initialYear = useMemo(() => {
11
+ const currentYear = isGregorian ? now.year() : now.jYear();
12
+ return isBirthdate ? currentYear - 18 : currentYear;
13
+ }, [isGregorian, isBirthdate]);
14
+ const initialMonth = isGregorian ? now.month() + 1 : now.jMonth() + 1;
15
+ const initialDay = isGregorian ? now.date() : now.jDate();
16
+ const [selectedYear, setSelectedYear] = useState(initialYear);
17
+ const [selectedMonth, setSelectedMonth] = useState(initialMonth);
18
+ const [selectedDay, setSelectedDay] = useState(initialDay);
19
+ const yearRef = useRef(null);
20
+ const monthRef = useRef(null);
21
+ const dayRef = useRef(null);
22
+ const months = isGregorian ? GREGORIAN_MONTHS : JALALI_MONTHS;
23
+ const years = useMemo(() => {
24
+ const start = isGregorian ? 1930 : 1300;
25
+ const end = (isGregorian ? now.year() : now.jYear()) + 20;
26
+ return Array.from({ length: end - start + 1 }, (_, i) => start + i);
27
+ }, [isGregorian, now]);
28
+ const daysInMonth = useMemo(() => {
29
+ if (isGregorian) {
30
+ return moment(`${selectedYear}-${selectedMonth}-01`, "YYYY-M-DD").daysInMonth();
31
+ }
32
+ return moment.jDaysInMonth(selectedYear, selectedMonth - 1);
33
+ }, [isGregorian, selectedYear, selectedMonth]);
34
+ const days = useMemo(() => {
35
+ return Array.from({ length: daysInMonth }, (_, i) => i + 1);
36
+ }, [daysInMonth]);
37
+ useEffect(() => {
38
+ if (selectedDay > daysInMonth) {
39
+ setSelectedDay(daysInMonth);
40
+ }
41
+ }, [selectedDay, daysInMonth]);
42
+ const buildDateObject = (year, month, day) => {
43
+ const m = isGregorian
44
+ ? moment(`${year}-${month}-${day}`, "YYYY-M-D")
45
+ : moment(`${year}-${month}-${day}`, "jYYYY-jM-jD");
46
+ return {
47
+ year,
48
+ month,
49
+ day,
50
+ formatted: isGregorian ? m.format("YYYY/MM/DD") : m.format("jYYYY/jMM/jDD"),
51
+ date: m.format("YYYY-MM-DD"),
52
+ gDate: m.format("YYYY-MM-DD"),
53
+ moment: m,
54
+ };
55
+ };
56
+ // پیاده‌سازی Debounce برای ارسال تاریخ
57
+ useEffect(() => {
58
+ const timer = setTimeout(() => {
59
+ onDateChange(buildDateObject(selectedYear, selectedMonth, selectedDay));
60
+ }, 500); // ۵۰۰ میلی‌ثانیه صبر بعد از آخرین تغییر
61
+ return () => clearTimeout(timer);
62
+ }, [selectedYear, selectedMonth, selectedDay, isGregorian]);
63
+ const scrollToIndex = (ref, index) => {
64
+ if (ref.current) {
65
+ ref.current.scrollTop = index * ITEM_HEIGHT;
66
+ }
67
+ };
68
+ useEffect(() => {
69
+ const monthIndex = Math.max(0, selectedMonth - 1);
70
+ const dayIndex = Math.max(0, selectedDay - 1);
71
+ const yearIndex = Math.max(0, years.indexOf(selectedYear));
72
+ const timer = window.setTimeout(() => {
73
+ scrollToIndex(dayRef, dayIndex);
74
+ scrollToIndex(monthRef, monthIndex);
75
+ scrollToIndex(yearRef, yearIndex);
76
+ }, 0);
77
+ return () => window.clearTimeout(timer);
78
+ }, [years]); // فقط وقتی لیست سال‌ها (تقویم) عوض شد ری‌اسکرول کن
79
+ const handleScroll = (e, type) => {
80
+ const index = Math.round(e.currentTarget.scrollTop / ITEM_HEIGHT);
81
+ if (type === "day") {
82
+ const nextDay = days[index];
83
+ if (nextDay && nextDay !== selectedDay)
84
+ setSelectedDay(nextDay);
85
+ }
86
+ else if (type === "month") {
87
+ const nextMonth = index + 1;
88
+ if (nextMonth >= 1 && nextMonth <= 12 && nextMonth !== selectedMonth)
89
+ setSelectedMonth(nextMonth);
90
+ }
91
+ else {
92
+ const nextYear = years[index];
93
+ if (nextYear && nextYear !== selectedYear)
94
+ setSelectedYear(nextYear);
95
+ }
96
+ };
97
+ const renderList = (items, selectedValue, ref, type) => {
98
+ return (_jsxs("div", { className: "scroll-list", onScroll: (e) => handleScroll(e, type), ref: ref, children: [_jsx("div", { style: { height: ITEM_HEIGHT } }), items.map((item, index) => {
99
+ const isSelected = type === "month" ? (index + 1) === selectedMonth : item === selectedValue;
100
+ return (_jsx("div", { className: `scroll-item ${isSelected ? "selected" : ""}`, style: { color: isSelected ? selectedColor : textColor }, children: item }, `${type}-${item}`));
101
+ }), _jsx("div", { style: { height: ITEM_HEIGHT } })] }));
102
+ };
103
+ return (_jsx("div", { className: "datepicker-container", children: _jsxs("div", { className: "scroll-lists", style: { backgroundColor }, children: [renderList(days, selectedDay, dayRef, "day"), renderList(months, months[selectedMonth - 1], monthRef, "month"), renderList(years, selectedYear, yearRef, "year"), _jsx("div", { className: "selection-highlight", style: { top: ITEM_HEIGHT, height: ITEM_HEIGHT } })] }) }));
104
+ };
105
+ export default DaliryMobileDatePicker;
package/dist/index.css CHANGED
@@ -3,24 +3,28 @@
3
3
  display: flex;
4
4
  flex-direction: column;
5
5
  align-items: center;
6
- gap: 16px;
7
6
  width: 100%;
7
+ min-width: 350px;
8
+ user-select: none;
9
+ padding: 10px;
8
10
  }
9
11
  .scroll-lists {
10
12
  display: flex;
11
13
  justify-content: space-between;
12
- background-color: #f5f5f5;
13
14
  border-radius: 12px;
14
- height: 152px;
15
+ height: 135px;
15
16
  width: 100%;
17
+ position: relative;
18
+ overflow: hidden;
16
19
  }
17
20
  .scroll-list {
18
- width: 110px;
19
- height: 152px;
21
+ flex: 1;
22
+ height: 135px;
20
23
  overflow-y: auto;
21
24
  scroll-snap-type: y mandatory;
22
25
  scrollbar-width: none;
23
26
  -ms-overflow-style: none;
27
+ z-index: 2;
24
28
  }
25
29
  .scroll-list::-webkit-scrollbar {
26
30
  display: none;
@@ -30,25 +34,40 @@
30
34
  line-height: 45px;
31
35
  text-align: center;
32
36
  scroll-snap-align: center;
33
- color: #bbb;
34
- font-size: 16px;
35
- opacity: 0.5;
36
- filter: blur(4px);
37
- font-weight: normal;
37
+ font-size: 19px;
38
+ opacity: 0.4;
39
+ filter: blur(2px);
40
+ transform: scale(0.8);
38
41
  }
39
42
  .scroll-item.selected {
40
- color: #333;
41
- font-size: 20px;
42
43
  font-weight: bold;
43
44
  opacity: 1;
44
45
  filter: none;
46
+ transform: scale(1);
47
+ transition: all 0.1s ease;
48
+ }
49
+ .selection-highlight {
50
+ position: absolute;
51
+ top: 45px;
52
+ left: 10px;
53
+ right: 10px;
54
+ height: 45px;
55
+ border-top: 1px solid rgba(0, 0, 0, 0.05);
56
+ border-bottom: 1px solid rgba(0, 0, 0, 0.05);
57
+ pointer-events: none;
58
+ z-index: 1;
45
59
  }
46
60
  .apply-button {
47
- padding: 10px 20px;
48
- background-color: #1976d2;
49
- color: white;
61
+ width: 100%;
62
+ margin-top: 16px;
63
+ padding: 12px;
50
64
  border: none;
51
65
  border-radius: 8px;
52
66
  font-size: 16px;
67
+ font-weight: bold;
53
68
  cursor: pointer;
69
+ transition: opacity 0.2s;
70
+ }
71
+ .apply-button:active {
72
+ opacity: 0.8;
54
73
  }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import * as React from "react";
2
- declare const NegarAuth: React.FC;
3
- export default NegarAuth;
1
+ import * as React from "react";
2
+ declare const NegarAuth: React.FC;
3
+ export default NegarAuth;