next-helios-fe 1.5.3 → 1.6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-helios-fe",
3
- "version": "1.5.3",
3
+ "version": "1.6.1",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -8,6 +8,7 @@ import { Checkbox, type CheckboxProps } from "./input/checkbox";
8
8
  import { Radio, type RadioProps } from "./input/radio";
9
9
  import { Range, type RangeProps } from "./input/range";
10
10
  import { Time, type TimeProps } from "./input/time";
11
+ import { Date, type DateProps } from "./input/date";
11
12
  import { File, type FileProps } from "./input/file";
12
13
  import { Color, type ColorProps } from "./input/color";
13
14
  import { Password, type PasswordProps } from "./input/password";
@@ -36,6 +37,7 @@ interface FormComponent extends React.FC<FormProps> {
36
37
  Radio: React.FC<RadioProps>;
37
38
  Range: React.FC<RangeProps>;
38
39
  Time: React.FC<TimeProps>;
40
+ Date: React.FC<DateProps>;
39
41
  File: React.FC<FileProps>;
40
42
  Color: React.FC<ColorProps>;
41
43
  Password: React.FC<PasswordProps>;
@@ -71,6 +73,7 @@ Form.Checkbox = Checkbox;
71
73
  Form.Radio = Radio;
72
74
  Form.Range = Range;
73
75
  Form.Time = Time;
76
+ Form.Date = Date;
74
77
  Form.File = File;
75
78
  Form.Color = Color;
76
79
  Form.Password = Password;
@@ -0,0 +1,191 @@
1
+ "use client";
2
+ import React, { useState, useEffect, useRef } from "react";
3
+ import { Dropdown, Calendar } from "../../../components";
4
+ import { Icon } from "@iconify/react";
5
+ import dayjs from "dayjs";
6
+
7
+ export interface DateProps extends React.InputHTMLAttributes<HTMLInputElement> {
8
+ options?: {
9
+ width?: "full" | "fit";
10
+ height?: "short" | "medium" | "high";
11
+ };
12
+ label?: string;
13
+ }
14
+
15
+ export const Date: React.FC<DateProps> = ({ options, label, ...rest }) => {
16
+ const [tempValue, setTempValue] = useState<any>([dayjs(), dayjs()]);
17
+ const [selectedRange, setSelectedRange] = useState<string | any[]>("Today");
18
+ const [manualDate, setManualDate] = useState<any>("");
19
+ const dropdownRef = useRef<HTMLButtonElement>(null);
20
+ const width = options?.width === "fit" ? "w-fit" : "w-full";
21
+ const height =
22
+ options?.height === "short"
23
+ ? "py-1"
24
+ : options?.height === "high"
25
+ ? "py-2"
26
+ : "py-1.5";
27
+
28
+ useEffect(() => {
29
+ if (rest.value) {
30
+ setTempValue(rest.value as any);
31
+ return;
32
+ } else if (rest.defaultValue) {
33
+ setTempValue(rest.defaultValue as any);
34
+ return;
35
+ }
36
+ }, [rest.value, rest.defaultValue]);
37
+
38
+ useEffect(() => {
39
+ rest.onChange &&
40
+ rest.onChange({
41
+ target: {
42
+ value: tempValue,
43
+ } as HTMLInputElement,
44
+ } as React.ChangeEvent<HTMLInputElement>);
45
+ }, [tempValue]);
46
+
47
+ return (
48
+ <div className="flex items-end">
49
+ <label className={`grid gap-2 ${width}`}>
50
+ {label && (
51
+ <span
52
+ className={`text-sm select-none ${
53
+ rest.required &&
54
+ "after:content-['*'] after:ml-1 after:text-danger"
55
+ }`}
56
+ >
57
+ {label}
58
+ </span>
59
+ )}
60
+ <div className="relative flex items-center">
61
+ <input
62
+ type="text"
63
+ className={`accent-primary w-full px-4 border-default border rounded-md bg-secondary-bg placeholder:duration-300 placeholder:translate-x-0 focus:placeholder:translate-x-1 placeholder:text-slate-300 focus:outline-none focus:ring-1 focus:ring-primary focus:shadow focus:shadow-primary focus:border-primary-dark disabled:bg-secondary-light disabled:text-slate-400 ${height}`}
64
+ value={
65
+ selectedRange === "Manual Input"
66
+ ? manualDate
67
+ : selectedRange === "Custom"
68
+ ? `${
69
+ dayjs(tempValue[0]).format("MMM YYYY") ===
70
+ dayjs(tempValue[1]).format("MMM YYYY")
71
+ ? dayjs(tempValue[0]).format("DD")
72
+ : dayjs(tempValue[0]).format("DD MMM YYYY")
73
+ } - ${dayjs(tempValue[1]).format("DD MMM YYYY")}`
74
+ : selectedRange
75
+ }
76
+ onChange={(e) => {
77
+ setManualDate(e.target.value);
78
+ }}
79
+ onKeyDown={(e) => {
80
+ if (e.key === "Backspace" && selectedRange !== "Manual Input") {
81
+ setSelectedRange("Manual Input");
82
+ }
83
+ }}
84
+ {...rest}
85
+ />
86
+ <button
87
+ type="button"
88
+ className="absolute right-4 p-1 rounded-full text-xl text-slate-400 cursor-pointer hover:bg-secondary-light"
89
+ tabIndex={-1}
90
+ disabled={rest.disabled}
91
+ onClick={(e) => {
92
+ e.preventDefault();
93
+ dropdownRef.current?.click();
94
+ }}
95
+ >
96
+ <Icon icon="akar-icons:calendar" />
97
+ </button>
98
+ </div>
99
+ </label>
100
+ <div className="w-0 overflow-hidden">
101
+ <Dropdown
102
+ dismissOnClick={false}
103
+ trigger={
104
+ <button
105
+ type="button"
106
+ ref={dropdownRef}
107
+ className={`w-0 my-0.5 ${height}`}
108
+ >
109
+ 1
110
+ </button>
111
+ }
112
+ >
113
+ <div className="flex gap-2 w-80 md:w-full overflow-auto md:overflow-clip">
114
+ <div className="w-full">
115
+ {/* <button
116
+ className="min-w-40 w-full my-0.5 px-4 py-2 rounded-md text-sm text-left hover:bg-secondary-light disabled:bg-primary-transparent disabled:text-primary"
117
+ disabled={tempValue === "Manual Input"}
118
+ onClick={() => {
119
+ setTempValue("Manual Input");
120
+ }}
121
+ >
122
+ Manual Input
123
+ </button> */}
124
+ <button
125
+ className="min-w-40 w-full my-0.5 px-4 py-2 rounded-md text-sm text-left hover:bg-secondary-light disabled:bg-primary-transparent disabled:text-primary"
126
+ disabled={selectedRange === "Today"}
127
+ onClick={() => {
128
+ setSelectedRange("Today");
129
+ setTempValue([dayjs(), dayjs()]);
130
+ }}
131
+ >
132
+ Today
133
+ </button>
134
+ <button
135
+ className="min-w-40 w-full my-0.5 px-4 py-2 rounded-md text-sm text-left hover:bg-secondary-light disabled:bg-primary-transparent disabled:text-primary"
136
+ disabled={selectedRange === "Yesterday"}
137
+ onClick={() => {
138
+ setSelectedRange("Yesterday");
139
+ setTempValue([
140
+ dayjs().subtract(1, "days"),
141
+ dayjs().subtract(1, "days"),
142
+ ]);
143
+ }}
144
+ >
145
+ Yesterday
146
+ </button>
147
+ <button
148
+ className="min-w-40 w-full my-0.5 px-4 py-2 rounded-md text-sm text-left hover:bg-secondary-light disabled:bg-primary-transparent disabled:text-primary"
149
+ disabled={selectedRange === "Last 7 days"}
150
+ onClick={() => {
151
+ setSelectedRange("Last 7 days");
152
+ setTempValue([dayjs().subtract(7, "days"), dayjs()]);
153
+ }}
154
+ >
155
+ Last 7 days
156
+ </button>
157
+ <button
158
+ className="min-w-40 w-full my-0.5 px-4 py-2 rounded-md text-sm text-left hover:bg-secondary-light disabled:bg-primary-transparent disabled:text-primary"
159
+ disabled={selectedRange === "Last 30 days"}
160
+ onClick={() => {
161
+ setSelectedRange("Last 30 days");
162
+ setTempValue([dayjs().subtract(1, "months"), dayjs()]);
163
+ }}
164
+ >
165
+ Last 30 days
166
+ </button>
167
+ <button
168
+ className="min-w-40 w-full my-0.5 px-4 py-2 rounded-md text-sm text-left hover:bg-secondary-light disabled:bg-primary-transparent disabled:text-primary"
169
+ disabled={selectedRange === "Custom"}
170
+ onClick={() => {
171
+ setSelectedRange("Custom");
172
+ setTempValue([dayjs(), dayjs()]);
173
+ }}
174
+ >
175
+ Custom
176
+ </button>
177
+ </div>
178
+ <Calendar
179
+ options={{ enableSelectRange: true }}
180
+ value={tempValue}
181
+ onChange={(value) => {
182
+ setSelectedRange("Custom");
183
+ setTempValue(value);
184
+ }}
185
+ />
186
+ </div>
187
+ </Dropdown>
188
+ </div>
189
+ </div>
190
+ );
191
+ };
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import React, { useState, useEffect, useRef } from "react";
3
- import { createPortal } from "react-dom";
3
+ import { Dropdown } from "../../../components";
4
+ import { Icon } from "@iconify/react";
4
5
  import dayjs from "dayjs";
5
6
 
6
7
  export interface TimeProps extends React.InputHTMLAttributes<HTMLInputElement> {
@@ -13,14 +14,7 @@ export interface TimeProps extends React.InputHTMLAttributes<HTMLInputElement> {
13
14
 
14
15
  export const Time: React.FC<TimeProps> = ({ options, label, ...rest }) => {
15
16
  const [tempValue, setTempValue] = useState(dayjs().format("hh:mm a"));
16
- const [openDropdown, setOpenDropdown] = useState(false);
17
- const [position, setPosition] = useState<{
18
- top: number;
19
- left: number;
20
- } | null>(null);
21
- const [dropdownWidth, setDropdownWidth] = useState<number>(0);
22
- const triggerRef = useRef<HTMLDivElement>(null);
23
- const dropdownRef = useRef<HTMLDivElement>(null);
17
+ const dropdownRef = useRef<HTMLButtonElement>(null);
24
18
  const hoursRef = useRef<HTMLDivElement>(null);
25
19
  const minutesRef = useRef<HTMLDivElement>(null);
26
20
  const width = options?.width === "fit" ? "w-fit" : "w-full";
@@ -108,74 +102,26 @@ export const Time: React.FC<TimeProps> = ({ options, label, ...rest }) => {
108
102
  ];
109
103
  const ampm = ["am", "pm"];
110
104
 
111
- useEffect(() => {
112
- const handleClickOutside = (e: MouseEvent) => {
113
- if (
114
- dropdownRef.current &&
115
- !dropdownRef.current.contains(e.target as Node) &&
116
- !triggerRef.current?.contains(e.target as Node)
117
- ) {
118
- setOpenDropdown(false);
119
- }
120
- };
121
-
122
- document.addEventListener("mousedown", handleClickOutside);
123
- return () => {
124
- document.removeEventListener("mousedown", handleClickOutside);
125
- };
126
- }, []);
127
-
128
- useEffect(() => {
129
- if (triggerRef.current) {
130
- const rect = triggerRef.current.getBoundingClientRect();
131
- const dropdownHeight = dropdownRef.current?.offsetHeight || 0;
132
- const windowHeight = window.innerHeight;
133
-
134
- setPosition({
135
- top: rect.bottom + window.scrollY + 10,
136
- left: rect.left + window.scrollX,
137
- });
138
-
139
- setDropdownWidth(rect.width);
140
-
141
- if (rect.bottom + dropdownHeight > windowHeight) {
142
- setPosition((prev) =>
143
- prev
144
- ? { ...prev, top: rect.top + window.scrollY - dropdownHeight - 10 }
145
- : null
105
+ const handleAutoScroll = () => {
106
+ const scrollToSelected = (
107
+ ref: React.RefObject<HTMLDivElement>,
108
+ value: string,
109
+ delay: number
110
+ ) => {
111
+ const timeout = setTimeout(() => {
112
+ const selectedElement = ref.current?.querySelector(
113
+ `[data-value='${value}']`
146
114
  );
147
- }
148
- }
149
-
150
- if (openDropdown) {
151
- document.getElementById("body")!.style.overflow = "hidden";
152
- } else {
153
- document.getElementById("body")!.style.overflow = "auto";
154
- }
155
- }, [openDropdown]);
156
-
157
- useEffect(() => {
158
- if (openDropdown) {
159
- const scrollToSelected = (
160
- ref: React.RefObject<HTMLDivElement>,
161
- value: string,
162
- delay: number
163
- ) => {
164
- const timeout = setTimeout(() => {
165
- const selectedElement = ref.current?.querySelector(
166
- `[data-value='${value}']`
167
- );
168
- selectedElement?.scrollIntoView({
169
- block: "nearest",
170
- });
171
- }, delay);
172
- return () => clearTimeout(timeout);
173
- };
115
+ selectedElement?.scrollIntoView({
116
+ block: "center",
117
+ });
118
+ }, delay);
119
+ return () => clearTimeout(timeout);
120
+ };
174
121
 
175
- scrollToSelected(hoursRef, tempValue.split(":")[0], 0);
176
- scrollToSelected(minutesRef, tempValue.split(":")[1].split(" ")[0], 0);
177
- }
178
- }, [openDropdown, tempValue]);
122
+ scrollToSelected(hoursRef, tempValue.split(":")[0], 0);
123
+ scrollToSelected(minutesRef, tempValue.split(":")[1].split(" ")[0], 1);
124
+ };
179
125
 
180
126
  useEffect(() => {
181
127
  if (rest.value) {
@@ -197,119 +143,132 @@ export const Time: React.FC<TimeProps> = ({ options, label, ...rest }) => {
197
143
  }, [tempValue]);
198
144
 
199
145
  return (
200
- <label className={`grid gap-2 ${width}`}>
201
- {label && (
202
- <span
203
- className={`text-sm select-none ${
204
- rest.required && "after:content-['*'] after:ml-1 after:text-danger"
205
- }`}
206
- >
207
- {label}
208
- </span>
209
- )}
210
- <div className="relative" ref={triggerRef}>
211
- <div
212
- className="relative flex items-center cursor-pointer"
213
- onClick={() => setOpenDropdown(!openDropdown)}
214
- >
146
+ <div className="flex items-end">
147
+ <label className={`grid gap-2 ${width}`}>
148
+ {label && (
149
+ <span
150
+ className={`text-sm select-none ${
151
+ rest.required &&
152
+ "after:content-['*'] after:ml-1 after:text-danger"
153
+ }`}
154
+ >
155
+ {label}
156
+ </span>
157
+ )}
158
+ <div className="relative flex items-center">
215
159
  <input
216
160
  type="text"
217
161
  className={`accent-primary w-full px-4 border-default border rounded-md bg-secondary-bg placeholder:duration-300 placeholder:translate-x-0 focus:placeholder:translate-x-1 placeholder:text-slate-300 focus:outline-none focus:ring-1 focus:ring-primary focus:shadow focus:shadow-primary focus:border-primary-dark disabled:bg-secondary-light disabled:text-slate-400 ${height}`}
218
162
  value={tempValue}
219
163
  {...rest}
220
164
  />
165
+ <button
166
+ type="button"
167
+ className="absolute right-4 p-1 rounded-full text-xl text-slate-400 cursor-pointer hover:bg-secondary-light"
168
+ tabIndex={-1}
169
+ disabled={rest.disabled}
170
+ onClick={(e) => {
171
+ e.preventDefault();
172
+ dropdownRef.current?.click();
173
+ handleAutoScroll();
174
+ }}
175
+ >
176
+ <Icon icon="tabler:clock" />
177
+ </button>
221
178
  </div>
222
- {openDropdown &&
223
- position &&
224
- createPortal(
225
- <div
179
+ </label>
180
+ <div className="w-0 overflow-hidden">
181
+ <Dropdown
182
+ dismissOnClick={false}
183
+ trigger={
184
+ <button
185
+ type="button"
226
186
  ref={dropdownRef}
227
- className={`absolute grid grid-cols-3 gap-2 min-w-40 h-40 p-1 z-50 pointer-events-auto bg-secondary-bg shadow border rounded-md overflow-hidden`}
228
- style={{
229
- top: position.top,
230
- left: position.left,
231
- width: dropdownWidth,
232
- }}
187
+ className={`w-0 my-0.5 ${height}`}
188
+ >
189
+ 1
190
+ </button>
191
+ }
192
+ >
193
+ <div className="flex gap-2 w-80 md:w-full overflow-auto md:overflow-clip">
194
+ <div
195
+ ref={hoursRef}
196
+ className="w-20 h-80 overflow-auto [&::-webkit-scrollbar]:hidden"
197
+ >
198
+ {hours.map((item, index) => (
199
+ <button
200
+ key={index}
201
+ type="button"
202
+ className={`w-full my-0.5 px-4 py-2 rounded-md text-sm text-left text-default ${
203
+ tempValue.split(":")[0] === item
204
+ ? "bg-primary-transparent cursor-default"
205
+ : "hover:bg-secondary-light"
206
+ }`}
207
+ data-value={item}
208
+ onMouseDown={() => {
209
+ setTempValue(
210
+ `${item}:${tempValue.split(":")[1].split(" ")[0]} ${
211
+ tempValue.split(" ")[1]
212
+ }`
213
+ );
214
+ }}
215
+ >
216
+ {item}
217
+ </button>
218
+ ))}
219
+ </div>
220
+ <div
221
+ ref={minutesRef}
222
+ className="w-20 h-80 overflow-auto [&::-webkit-scrollbar]:hidden"
233
223
  >
234
- <div
235
- ref={hoursRef}
236
- className="h-full overflow-auto [&::-webkit-scrollbar]:hidden"
237
- >
238
- {hours.map((item, index) => (
239
- <button
240
- key={index}
241
- type="button"
242
- className={`w-full my-0.5 px-4 py-2 rounded-md text-sm text-left text-default ${
243
- tempValue.split(":")[0] === item
244
- ? "bg-primary-transparent cursor-default"
245
- : "hover:bg-secondary-light"
246
- }`}
247
- data-value={item}
248
- onMouseDown={() => {
249
- setTempValue(
250
- `${item}:${tempValue.split(":")[1].split(" ")[0]} ${
251
- tempValue.split(" ")[1]
252
- }`
253
- );
254
- }}
255
- >
256
- {item}
257
- </button>
258
- ))}
259
- </div>
260
- <div
261
- ref={minutesRef}
262
- className="h-full overflow-auto [&::-webkit-scrollbar]:hidden"
263
- >
264
- {minutes.map((item, index) => (
265
- <button
266
- key={index}
267
- type="button"
268
- className={`w-full my-0.5 px-4 py-2 rounded-md text-sm text-left text-default ${
269
- tempValue.split(":")[1].split(" ")[0] === item
270
- ? "bg-primary-transparent cursor-default"
271
- : "hover:bg-secondary-light "
272
- }`}
273
- data-value={item}
274
- onMouseDown={() => {
275
- setTempValue(
276
- `${tempValue.split(":")[0]}:${item} ${
277
- tempValue.split(" ")[1]
278
- }`
279
- );
280
- }}
281
- >
282
- {item}
283
- </button>
284
- ))}
285
- </div>
286
- <div className="h-full overflow-auto [&::-webkit-scrollbar]:hidden">
287
- {ampm.map((item, index) => (
288
- <button
289
- key={index}
290
- type="button"
291
- className={`w-full my-0.5 px-4 py-2 rounded-md text-sm text-left text-default ${
292
- tempValue.split(" ")[1] === item
293
- ? "bg-primary-transparent cursor-default"
294
- : "hover:bg-secondary-light "
295
- }`}
296
- data-value={item}
297
- onMouseDown={() => {
298
- setTempValue(
299
- `${tempValue.split(":")[0]}:${
300
- tempValue.split(":")[1].split(" ")[0]
301
- } ${item}`
302
- );
303
- }}
304
- >
305
- {item}
306
- </button>
307
- ))}
308
- </div>
309
- </div>,
310
- document.body
311
- )}
224
+ {minutes.map((item, index) => (
225
+ <button
226
+ key={index}
227
+ type="button"
228
+ className={`w-full my-0.5 px-4 py-2 rounded-md text-sm text-left text-default ${
229
+ tempValue.split(":")[1].split(" ")[0] === item
230
+ ? "bg-primary-transparent cursor-default"
231
+ : "hover:bg-secondary-light "
232
+ }`}
233
+ data-value={item}
234
+ onMouseDown={() => {
235
+ setTempValue(
236
+ `${tempValue.split(":")[0]}:${item} ${
237
+ tempValue.split(" ")[1]
238
+ }`
239
+ );
240
+ }}
241
+ >
242
+ {item}
243
+ </button>
244
+ ))}
245
+ </div>
246
+ <div className="w-20 h-80 overflow-auto [&::-webkit-scrollbar]:hidden">
247
+ {ampm.map((item, index) => (
248
+ <button
249
+ key={index}
250
+ type="button"
251
+ className={`w-full my-0.5 px-4 py-2 rounded-md text-sm text-left text-default ${
252
+ tempValue.split(" ")[1] === item
253
+ ? "bg-primary-transparent cursor-default"
254
+ : "hover:bg-secondary-light "
255
+ }`}
256
+ data-value={item}
257
+ onMouseDown={() => {
258
+ setTempValue(
259
+ `${tempValue.split(":")[0]}:${
260
+ tempValue.split(":")[1].split(" ")[0]
261
+ } ${item}`
262
+ );
263
+ }}
264
+ >
265
+ {item}
266
+ </button>
267
+ ))}
268
+ </div>
269
+ </div>
270
+ </Dropdown>
312
271
  </div>
313
- </label>
272
+ </div>
314
273
  );
315
274
  };