next-helios-fe 1.0.0

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 (112) hide show
  1. package/@types/index.d.ts +2 -0
  2. package/dist/components/button/index.d.ts +15 -0
  3. package/dist/components/calendar/big-calendar/event.d.ts +7 -0
  4. package/dist/components/calendar/big-calendar/index.d.ts +14 -0
  5. package/dist/components/calendar/big-calendar/toolbar.d.ts +8 -0
  6. package/dist/components/calendar/calendar/index.d.ts +11 -0
  7. package/dist/components/chart/index.d.ts +17 -0
  8. package/dist/components/chip/index.d.ts +12 -0
  9. package/dist/components/content-container/accordion/index.d.ts +10 -0
  10. package/dist/components/content-container/accordion/item.d.ts +6 -0
  11. package/dist/components/content-container/card.d.ts +10 -0
  12. package/dist/components/content-container/carousel.d.ts +10 -0
  13. package/dist/components/content-container/drawer.d.ts +8 -0
  14. package/dist/components/content-container/modal/index.d.ts +21 -0
  15. package/dist/components/content-container/sortable/index.d.ts +14 -0
  16. package/dist/components/content-container/sortable/item.d.ts +8 -0
  17. package/dist/components/content-container/sortable/knob.d.ts +5 -0
  18. package/dist/components/content-container/tab/index.d.ts +17 -0
  19. package/dist/components/content-container/tab/window.d.ts +5 -0
  20. package/dist/components/data-tree/index.d.ts +6 -0
  21. package/dist/components/dialog/index.d.ts +16 -0
  22. package/dist/components/dropdown/header.d.ts +5 -0
  23. package/dist/components/dropdown/index.d.ts +15 -0
  24. package/dist/components/dropdown/item.d.ts +7 -0
  25. package/dist/components/form/index.d.ts +44 -0
  26. package/dist/components/form/input/checkbox.d.ts +9 -0
  27. package/dist/components/form/input/color.d.ts +10 -0
  28. package/dist/components/form/input/email.d.ts +9 -0
  29. package/dist/components/form/input/file.d.ts +14 -0
  30. package/dist/components/form/input/number.d.ts +9 -0
  31. package/dist/components/form/input/password.d.ts +9 -0
  32. package/dist/components/form/input/radio.d.ts +9 -0
  33. package/dist/components/form/input/range.d.ts +9 -0
  34. package/dist/components/form/input/search.d.ts +8 -0
  35. package/dist/components/form/input/text.d.ts +9 -0
  36. package/dist/components/form/input/time.d.ts +9 -0
  37. package/dist/components/form/other/autocomplete.d.ts +15 -0
  38. package/dist/components/form/other/multipleSelect.d.ts +23 -0
  39. package/dist/components/form/other/phoneNumber.d.ts +10 -0
  40. package/dist/components/form/other/pin.d.ts +10 -0
  41. package/dist/components/form/other/secret.d.ts +11 -0
  42. package/dist/components/form/other/select.d.ts +15 -0
  43. package/dist/components/form/other/textarea.d.ts +9 -0
  44. package/dist/components/index.d.ts +19 -0
  45. package/dist/components/map/index.d.ts +13 -0
  46. package/dist/components/map/marker.d.ts +8 -0
  47. package/dist/components/syntax-highlighter/index.d.ts +9 -0
  48. package/dist/components/table/action.d.ts +5 -0
  49. package/dist/components/table/index.d.ts +24 -0
  50. package/dist/components/timeline/index.d.ts +10 -0
  51. package/dist/components/timeline/item.d.ts +7 -0
  52. package/dist/index.d.ts +2 -0
  53. package/dist/index.js +2 -0
  54. package/dist/index.js.LICENSE.txt +62 -0
  55. package/package.json +72 -0
  56. package/src/components/button/index.tsx +74 -0
  57. package/src/components/calendar/big-calendar/event.tsx +46 -0
  58. package/src/components/calendar/big-calendar/index.tsx +102 -0
  59. package/src/components/calendar/big-calendar/toolbar.tsx +98 -0
  60. package/src/components/calendar/calendar/index.tsx +26 -0
  61. package/src/components/chart/index.tsx +121 -0
  62. package/src/components/chip/index.tsx +47 -0
  63. package/src/components/content-container/accordion/index.tsx +28 -0
  64. package/src/components/content-container/accordion/item.tsx +40 -0
  65. package/src/components/content-container/card.tsx +21 -0
  66. package/src/components/content-container/carousel.tsx +35 -0
  67. package/src/components/content-container/drawer.tsx +78 -0
  68. package/src/components/content-container/modal/index.tsx +127 -0
  69. package/src/components/content-container/sortable/index.tsx +47 -0
  70. package/src/components/content-container/sortable/item.tsx +20 -0
  71. package/src/components/content-container/sortable/knob.tsx +11 -0
  72. package/src/components/content-container/tab/index.tsx +94 -0
  73. package/src/components/content-container/tab/window.tsx +10 -0
  74. package/src/components/data-tree/index.tsx +60 -0
  75. package/src/components/dialog/index.tsx +88 -0
  76. package/src/components/dropdown/header.tsx +11 -0
  77. package/src/components/dropdown/index.tsx +69 -0
  78. package/src/components/dropdown/item.tsx +22 -0
  79. package/src/components/form/index.tsx +81 -0
  80. package/src/components/form/input/checkbox.tsx +49 -0
  81. package/src/components/form/input/color.tsx +104 -0
  82. package/src/components/form/input/email.tsx +40 -0
  83. package/src/components/form/input/file.tsx +189 -0
  84. package/src/components/form/input/number.tsx +93 -0
  85. package/src/components/form/input/password.tsx +57 -0
  86. package/src/components/form/input/radio.tsx +49 -0
  87. package/src/components/form/input/range.tsx +67 -0
  88. package/src/components/form/input/search.tsx +50 -0
  89. package/src/components/form/input/text.tsx +39 -0
  90. package/src/components/form/input/time.tsx +315 -0
  91. package/src/components/form/other/autocomplete.tsx +199 -0
  92. package/src/components/form/other/multipleSelect.tsx +211 -0
  93. package/src/components/form/other/phoneNumber.tsx +1668 -0
  94. package/src/components/form/other/pin.tsx +56 -0
  95. package/src/components/form/other/secret.tsx +74 -0
  96. package/src/components/form/other/select.tsx +187 -0
  97. package/src/components/form/other/textarea.tsx +44 -0
  98. package/src/components/index.ts +29 -0
  99. package/src/components/map/index.tsx +72 -0
  100. package/src/components/map/marker.tsx +40 -0
  101. package/src/components/syntax-highlighter/index.tsx +45 -0
  102. package/src/components/table/action.tsx +22 -0
  103. package/src/components/table/index.tsx +431 -0
  104. package/src/components/timeline/index.tsx +28 -0
  105. package/src/components/timeline/item.tsx +25 -0
  106. package/src/index.css +1 -0
  107. package/src/index.ts +3 -0
  108. package/src/styles/big-calendar.scss +810 -0
  109. package/src/styles/calendar.scss +195 -0
  110. package/src/styles/index.css +2 -0
  111. package/tsconfig.json +17 -0
  112. package/webpack.config.js +35 -0
@@ -0,0 +1,69 @@
1
+ "use client";
2
+ import React from "react";
3
+ import { Dropdown as Dd } from "flowbite-react";
4
+ import { Header, type HeaderProps } from "./header";
5
+ import { Item, type ItemProps } from "./item";
6
+
7
+ interface DropdownProps {
8
+ children: React.ReactNode;
9
+ placement?:
10
+ | "top"
11
+ | "top-start"
12
+ | "top-end"
13
+ | "bottom"
14
+ | "bottom-start"
15
+ | "bottom-end";
16
+ dismissOnClick?: boolean;
17
+ trigger?: React.ReactNode;
18
+ }
19
+
20
+ interface DropdownComponent extends React.FC<DropdownProps> {
21
+ Header: React.FC<HeaderProps>;
22
+ Item: React.FC<ItemProps>;
23
+ }
24
+
25
+ export const Dropdown: DropdownComponent = ({
26
+ children,
27
+ placement,
28
+ dismissOnClick,
29
+ trigger,
30
+ }) => {
31
+ const childrenList = React.Children.toArray(children);
32
+ const header = childrenList.find((child) => {
33
+ return (child as React.ReactElement).type === Dropdown.Header;
34
+ });
35
+ const itemList = childrenList.filter((child) => {
36
+ return (child as React.ReactElement).type === Dropdown.Item;
37
+ });
38
+
39
+ return (
40
+ <Dd
41
+ label=""
42
+ className="px-1 rounded-md border-default bg-secondary-bg z-20"
43
+ placement={placement || "bottom-end"}
44
+ dismissOnClick={dismissOnClick || true}
45
+ theme={{
46
+ floating: {
47
+ header: "px-4 py-2 text-sm !text-default",
48
+ item: {
49
+ base: `min-w-40 w-full my-0.5 rounded-md text-sm text-left text-default ${
50
+ childrenList.length > 0 && !header && itemList.length === 0
51
+ ? ""
52
+ : "px-4 py-2 hover:bg-secondary-light"
53
+ }`,
54
+ },
55
+ },
56
+ }}
57
+ renderTrigger={() => <div>{trigger}</div>}
58
+ >
59
+ {header}
60
+ {itemList}
61
+ {childrenList.length > 0 && !header && itemList.length === 0 && (
62
+ <Dd.Item className="grid cursor-default">{children}</Dd.Item>
63
+ )}
64
+ </Dd>
65
+ );
66
+ };
67
+
68
+ Dropdown.Header = Header;
69
+ Dropdown.Item = Item;
@@ -0,0 +1,22 @@
1
+ "use client";
2
+ import React from "react";
3
+ import { Dropdown as Dd } from "flowbite-react";
4
+
5
+ export interface ItemProps {
6
+ children: React.ReactNode;
7
+ active?: boolean;
8
+ onClick?: () => void;
9
+ }
10
+
11
+ export const Item: React.FC<ItemProps> = ({ children, active, onClick }) => {
12
+ return (
13
+ <Dd.Item
14
+ className={`${
15
+ active && "bg-primary-transparent text-primary pointer-events-none"
16
+ }`}
17
+ onClick={onClick}
18
+ >
19
+ {children}
20
+ </Dd.Item>
21
+ );
22
+ };
@@ -0,0 +1,81 @@
1
+ "use client";
2
+ import React from "react";
3
+ import { Text, type TextProps } from "./input/text";
4
+ import { Number, type NumberProps } from "./input/number";
5
+ import { Email, type EmailProps } from "./input/email";
6
+ import { Search, type SearchProps } from "./input/search";
7
+ import { Checkbox, type CheckboxProps } from "./input/checkbox";
8
+ import { Radio, type RadioProps } from "./input/radio";
9
+ import { Range, type RangeProps } from "./input/range";
10
+ import { Time, type TimeProps } from "./input/time";
11
+ import { File, type FileProps } from "./input/file";
12
+ import { Color, type ColorProps } from "./input/color";
13
+ import { Password, type PasswordProps } from "./input/password";
14
+ import { PhoneNumber, type PhoneNumberProps } from "./other/phoneNumber";
15
+ import { Secret, type SecretProps } from "./other/secret";
16
+ import { Pin, type PinProps } from "./other/pin";
17
+ import { Textarea, type TextareaProps } from "./other/textarea";
18
+ import { Select, type SelectProps } from "./other/select";
19
+ import {
20
+ MultipleSelect,
21
+ type MultipleSelectProps,
22
+ } from "./other/multipleSelect";
23
+ import { Autocomplete, type AutocompleteProps } from "./other/autocomplete";
24
+
25
+ interface FormProps extends React.FormHTMLAttributes<HTMLFormElement> {
26
+ children: React.ReactNode;
27
+ }
28
+
29
+ interface FormComponent extends React.FC<FormProps> {
30
+ Text: React.FC<TextProps>;
31
+ Number: React.FC<NumberProps>;
32
+ Email: React.FC<EmailProps>;
33
+ Search: React.FC<SearchProps>;
34
+ Checkbox: React.FC<CheckboxProps>;
35
+ Radio: React.FC<RadioProps>;
36
+ Range: React.FC<RangeProps>;
37
+ Time: React.FC<TimeProps>;
38
+ File: React.FC<FileProps>;
39
+ Color: React.FC<ColorProps>;
40
+ Password: React.FC<PasswordProps>;
41
+ PhoneNumber: React.FC<PhoneNumberProps>;
42
+ Secret: React.FC<SecretProps>;
43
+ Pin: React.FC<PinProps>;
44
+ Textarea: React.FC<TextareaProps>;
45
+ Select: React.FC<SelectProps>;
46
+ MultipleSelect: React.FC<MultipleSelectProps>;
47
+ Autocomplete: React.FC<AutocompleteProps>;
48
+ }
49
+
50
+ export const Form: FormComponent = ({ children, onSubmit, ...rest }) => {
51
+ return (
52
+ <form
53
+ onSubmit={(e) => {
54
+ e.preventDefault();
55
+ onSubmit!(e);
56
+ }}
57
+ {...rest}
58
+ >
59
+ {children}
60
+ </form>
61
+ );
62
+ };
63
+
64
+ Form.Text = Text;
65
+ Form.Number = Number;
66
+ Form.Email = Email;
67
+ Form.Search = Search;
68
+ Form.Checkbox = Checkbox;
69
+ Form.Radio = Radio;
70
+ Form.Range = Range;
71
+ Form.Time = Time;
72
+ Form.File = File;
73
+ Form.Color = Color;
74
+ Form.Password = Password;
75
+ Form.PhoneNumber = PhoneNumber;
76
+ Form.Secret = Secret;
77
+ Form.Pin = Pin;
78
+ Form.Textarea = Textarea;
79
+ Form.Select = Select;
80
+ Form.MultipleSelect = MultipleSelect;
81
+ Form.Autocomplete = Autocomplete;
@@ -0,0 +1,49 @@
1
+ "use client";
2
+ import React from "react";
3
+
4
+ export interface CheckboxProps
5
+ extends React.InputHTMLAttributes<HTMLInputElement> {
6
+ options?: {
7
+ disableHover?: boolean;
8
+ };
9
+ label?: string;
10
+ theme?: string;
11
+ }
12
+
13
+ export const Checkbox: React.FC<CheckboxProps> = ({
14
+ options,
15
+ label,
16
+ theme,
17
+ ...rest
18
+ }) => {
19
+ return (
20
+ <label
21
+ className={`flex items-center w-fit ${
22
+ !rest.disabled && "cursor-pointer"
23
+ } ${!options?.disableHover ? "gap-2" : "gap-4"}`}
24
+ >
25
+ <div
26
+ className={`flex justify-center items-center ${
27
+ !options?.disableHover &&
28
+ "w-8 h-8 rounded-full hover:bg-secondary-light"
29
+ }`}
30
+ >
31
+ <input
32
+ type="checkbox"
33
+ className={`border-default border rounded bg-secondary-bg cursor-pointer checked:bg-primary focus:outline-none focus:ring-0 focus:ring-offset-0 disabled:bg-secondary-light disabled:checked:bg-secondary-dark disabled:cursor-default ${
34
+ theme && "border-0"
35
+ }`}
36
+ style={{ backgroundColor: theme }}
37
+ {...rest}
38
+ />
39
+ </div>
40
+ {label && (
41
+ <span
42
+ className={`text-sm select-none ${rest.disabled && "text-slate-400"}`}
43
+ >
44
+ {label}
45
+ </span>
46
+ )}
47
+ </label>
48
+ );
49
+ };
@@ -0,0 +1,104 @@
1
+ "use client";
2
+ import React, { useState, useEffect, useRef } from "react";
3
+ import { Icon } from "@iconify/react";
4
+
5
+ export interface ColorProps
6
+ extends React.InputHTMLAttributes<HTMLInputElement> {
7
+ options?: {
8
+ width?: "full" | "fit";
9
+ height?: "short" | "medium" | "high";
10
+ };
11
+ label?: string;
12
+ onClipboardClick?: (value: string) => void;
13
+ }
14
+
15
+ export const Color: React.FC<ColorProps> = ({
16
+ options,
17
+ label,
18
+ onClipboardClick,
19
+ ...rest
20
+ }) => {
21
+ const [tempValue, setTempValue] = useState("");
22
+ const inputRef = useRef<HTMLInputElement>(null);
23
+ const width = options?.width === "fit" ? "w-fit" : "w-full";
24
+ const height =
25
+ options?.height === "short"
26
+ ? "py-1"
27
+ : options?.height === "high"
28
+ ? "py-2"
29
+ : "py-1.5";
30
+
31
+ useEffect(() => {
32
+ if (rest.value) {
33
+ setTempValue(rest.value as string);
34
+ return;
35
+ } else if (rest.defaultValue) {
36
+ setTempValue(rest.defaultValue as string);
37
+ return;
38
+ }
39
+ }, [rest.value, rest.defaultValue]);
40
+
41
+ useEffect(() => {
42
+ rest.onChange &&
43
+ rest.onChange({
44
+ target: { value: tempValue } as HTMLInputElement,
45
+ } as React.ChangeEvent<HTMLInputElement>);
46
+ }, [tempValue]);
47
+
48
+ return (
49
+ <label className={`grid gap-2 ${width}`}>
50
+ {label && (
51
+ <span
52
+ className={`text-sm select-none ${
53
+ rest.required && "after:content-['*'] after:ml-1 after:text-danger"
54
+ }`}
55
+ >
56
+ {label}
57
+ </span>
58
+ )}
59
+ <div className="flex gap-4">
60
+ <div className="relative flex flex-col">
61
+ <button
62
+ type="button"
63
+ className={`h-full px-4 border-default border rounded-md text-slate-400 cursor-pointer 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
+ style={{ backgroundColor: tempValue }}
65
+ onClick={() => {
66
+ inputRef.current?.click();
67
+ }}
68
+ >
69
+ <Icon
70
+ icon="mingcute:color-picker-line"
71
+ className={`text-xl ${tempValue && "invisible"}`}
72
+ />
73
+ </button>
74
+ <input
75
+ ref={inputRef}
76
+ type="color"
77
+ className="absolute -bottom-2.5 -z-10"
78
+ value={tempValue}
79
+ onChange={(e) => setTempValue(e.target.value.toUpperCase())}
80
+ {...rest}
81
+ />
82
+ </div>
83
+ <div className="relative flex-1 flex items-center">
84
+ <input
85
+ type="text"
86
+ className={`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}`}
87
+ placeholder={rest.placeholder}
88
+ value={tempValue}
89
+ onChange={(e) => setTempValue(e.target.value.toUpperCase())}
90
+ />
91
+ <div
92
+ className="absolute right-4 p-1 rounded-full text-xl text-slate-400 cursor-pointer hover:bg-secondary-light"
93
+ onClick={(e) => {
94
+ e.preventDefault();
95
+ onClipboardClick && onClipboardClick(tempValue);
96
+ }}
97
+ >
98
+ <Icon icon="fluent:clipboard-text-ltr-32-regular" />
99
+ </div>
100
+ </div>
101
+ </div>
102
+ </label>
103
+ );
104
+ };
@@ -0,0 +1,40 @@
1
+ "use client";
2
+ import React from "react";
3
+
4
+ export interface EmailProps
5
+ extends React.InputHTMLAttributes<HTMLInputElement> {
6
+ options?: {
7
+ width?: "full" | "fit";
8
+ height?: "short" | "medium" | "high";
9
+ };
10
+ label?: string;
11
+ }
12
+
13
+ export const Email: React.FC<EmailProps> = ({ options, label, ...rest }) => {
14
+ const width = options?.width === "fit" ? "w-fit" : "w-full";
15
+ const height =
16
+ options?.height === "short"
17
+ ? "py-1"
18
+ : options?.height === "high"
19
+ ? "py-2"
20
+ : "py-1.5";
21
+
22
+ return (
23
+ <label className={`grid gap-2 ${width}`}>
24
+ {label && (
25
+ <span
26
+ className={`text-sm select-none ${
27
+ rest.required && "after:content-['*'] after:ml-1 after:text-danger"
28
+ }`}
29
+ >
30
+ {label}
31
+ </span>
32
+ )}
33
+ <input
34
+ type="email"
35
+ className={`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}`}
36
+ {...rest}
37
+ />
38
+ </label>
39
+ );
40
+ };
@@ -0,0 +1,189 @@
1
+ "use client";
2
+ import React, { useState, useEffect, useRef } from "react";
3
+ import { Icon } from "@iconify/react";
4
+
5
+ export interface FileProps extends React.InputHTMLAttributes<HTMLInputElement> {
6
+ options?: {
7
+ variant?: "simple" | "drag&drop";
8
+ buttonOnly?: boolean;
9
+ maxFileSize?: number;
10
+ width?: "full" | "fit";
11
+ height?: "short" | "medium" | "high";
12
+ };
13
+ label?: string;
14
+ files?: any;
15
+ onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
16
+ }
17
+
18
+ export const File: React.FC<FileProps> = ({
19
+ options,
20
+ label,
21
+ files,
22
+ onChange,
23
+ ...rest
24
+ }) => {
25
+ const inputRef = useRef<HTMLInputElement>(null);
26
+ const [tempValue, setTempValue] = useState<any>();
27
+ const width = options?.width === "fit" ? "w-fit" : "w-full";
28
+ const height =
29
+ options?.height === "short"
30
+ ? "py-1"
31
+ : options?.height === "high"
32
+ ? "py-2"
33
+ : "py-1.5";
34
+
35
+ useEffect(() => {
36
+ if (files) {
37
+ setTempValue(files);
38
+ return;
39
+ }
40
+ }, [files]);
41
+
42
+ useEffect(() => {
43
+ onChange &&
44
+ onChange({
45
+ target: { files: tempValue } as any,
46
+ } as React.ChangeEvent<HTMLInputElement>);
47
+ }, [tempValue]);
48
+
49
+ return (
50
+ <div
51
+ className={`grid gap-2 ${width}`}
52
+ onClick={() => {
53
+ inputRef.current?.click();
54
+ }}
55
+ >
56
+ {label && (
57
+ <span
58
+ className={`text-sm select-none ${
59
+ rest.required && "after:content-['*'] after:ml-1 after:text-danger"
60
+ }`}
61
+ >
62
+ {label}
63
+ </span>
64
+ )}
65
+ {options?.variant !== "drag&drop" ? (
66
+ <div className="w-full flex items-center gap-4">
67
+ <button
68
+ type="button"
69
+ className="p-2 rounded-full hover:bg-secondary-light"
70
+ disabled={rest.disabled}
71
+ >
72
+ <Icon icon="mi:attachment" className="text-xl" />
73
+ </button>
74
+ {!options?.buttonOnly && (
75
+ <input
76
+ type="text"
77
+ className={`flex-1 px-4 border-default border rounded-md bg-secondary-bg cursor-pointer caret-transparent 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 disabled:cursor-default ${height}`}
78
+ placeholder={rest.placeholder}
79
+ disabled={rest.disabled}
80
+ value={
81
+ tempValue
82
+ ? `${tempValue?.name} (${
83
+ tempValue?.size < 1000000
84
+ ? `${tempValue?.size / 1000} KB`
85
+ : `${tempValue?.size / 1000000} MB`
86
+ })`
87
+ : ""
88
+ }
89
+ />
90
+ )}
91
+ </div>
92
+ ) : (
93
+ <button
94
+ type="button"
95
+ className="relative flex flex-col justify-center items-center gap-8 w-full min-h-60 h-full px-4 py-1.5 border border-dashed rounded-md bg-secondary-bg disabled:bg-secondary-light"
96
+ onDragEnter={(e) => {
97
+ if (!rest.disabled) {
98
+ e.preventDefault();
99
+ e.stopPropagation();
100
+ e.currentTarget.classList.add(
101
+ "border-primary",
102
+ "bg-secondary-light"
103
+ );
104
+ }
105
+ }}
106
+ onDragLeave={(e) => {
107
+ if (!rest.disabled) {
108
+ e.preventDefault();
109
+ e.stopPropagation();
110
+ e.currentTarget.classList.remove(
111
+ "border-primary",
112
+ "bg-secondary-light"
113
+ );
114
+ }
115
+ }}
116
+ onDragOver={(e) => {
117
+ if (!rest.disabled) {
118
+ e.preventDefault();
119
+ e.stopPropagation();
120
+ }
121
+ }}
122
+ onDrop={(e) => {
123
+ if (!rest.disabled) {
124
+ e.preventDefault();
125
+ e.stopPropagation();
126
+ e.currentTarget.classList.remove(
127
+ "border-primary",
128
+ "bg-secondary-light"
129
+ );
130
+ setTempValue(e.dataTransfer.files[0]);
131
+ }
132
+ }}
133
+ disabled={rest.disabled}
134
+ >
135
+ <Icon
136
+ icon={tempValue ? "mdi:file-outline" : "mynaui:upload"}
137
+ className={`text-5xl pointer-events-none ${
138
+ tempValue ? "" : "text-slate-400"
139
+ }`}
140
+ />
141
+ <div className="flex flex-col gap-2 items-center pointer-events-none">
142
+ {tempValue ? (
143
+ <span>{tempValue?.name}</span>
144
+ ) : (
145
+ <div className="flex gap-2 text-slate-400">
146
+ <span className="underline underline-offset-2">
147
+ Click to upload
148
+ </span>
149
+ <span>or drag and drop</span>
150
+ </div>
151
+ )}
152
+ <span
153
+ className={`text-sm font-light ${
154
+ tempValue ? "" : "text-slate-400"
155
+ }`}
156
+ >
157
+ {tempValue
158
+ ? `${
159
+ tempValue?.size < 1000000
160
+ ? `${tempValue?.size / 1000} KB`
161
+ : `${tempValue?.size / 1000000} MB`
162
+ }`
163
+ : `Maximum file size ${options?.maxFileSize} MB`}
164
+ </span>
165
+ </div>
166
+ {tempValue && (
167
+ <div
168
+ className="absolute top-4 right-4 p-1 rounded-full hover:bg-secondary-light"
169
+ onClick={() => {
170
+ setTempValue(null);
171
+ }}
172
+ >
173
+ <Icon icon="pajamas:close" />
174
+ </div>
175
+ )}
176
+ </button>
177
+ )}
178
+ <input
179
+ ref={inputRef}
180
+ type="file"
181
+ className="hidden"
182
+ onChange={(e) => {
183
+ setTempValue(e.target.files![0]);
184
+ }}
185
+ {...rest}
186
+ />
187
+ </div>
188
+ );
189
+ };
@@ -0,0 +1,93 @@
1
+ "use client";
2
+ import React, { useState, useEffect } from "react";
3
+ import { Icon } from "@iconify/react";
4
+
5
+ export interface NumberProps
6
+ extends React.InputHTMLAttributes<HTMLInputElement> {
7
+ options?: {
8
+ width?: "full" | "fit";
9
+ height?: "short" | "medium" | "high";
10
+ };
11
+ label?: string;
12
+ }
13
+
14
+ export const Number: React.FC<NumberProps> = ({ options, label, ...rest }) => {
15
+ const [tempValue, setTempValue] = useState("");
16
+ const width = options?.width === "fit" ? "w-fit" : "w-full";
17
+ const height =
18
+ options?.height === "short"
19
+ ? "py-1"
20
+ : options?.height === "high"
21
+ ? "py-2"
22
+ : "py-1.5";
23
+
24
+ useEffect(() => {
25
+ if (rest.value) {
26
+ setTempValue(rest.value as string);
27
+ return;
28
+ } else if (rest.defaultValue) {
29
+ setTempValue(rest.defaultValue as string);
30
+ return;
31
+ }
32
+ }, [rest.value, rest.defaultValue]);
33
+
34
+ useEffect(() => {
35
+ rest.onChange &&
36
+ rest.onChange({
37
+ target: { value: tempValue } as HTMLInputElement,
38
+ } as React.ChangeEvent<HTMLInputElement>);
39
+ }, [tempValue]);
40
+
41
+ return (
42
+ <label className={`grid gap-2 ${width}`}>
43
+ {label && (
44
+ <span
45
+ className={`text-sm select-none ${
46
+ rest.required && "after:content-['*'] after:ml-1 after:text-danger"
47
+ }`}
48
+ >
49
+ {label}
50
+ </span>
51
+ )}
52
+ <div className="relative flex items-center">
53
+ <input
54
+ type="number"
55
+ className={`w-full px-4 border-default border rounded-md bg-secondary-bg [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none 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}`}
56
+ value={tempValue}
57
+ onChange={(e) => setTempValue(e.target.value)}
58
+ {...rest}
59
+ />
60
+ <div className="absolute right-4 flex gap-2">
61
+ <button
62
+ type="button"
63
+ className="p-0.5 rounded-md shadow bg-primary-transparent hover:bg-primary-light text-primary disabled:bg-secondary disabled:text-white"
64
+ disabled={rest.disabled}
65
+ onClick={() => {
66
+ setTempValue(
67
+ (
68
+ (parseInt(tempValue) || 0) - ((rest.step as number) || 1)
69
+ ).toString()
70
+ );
71
+ }}
72
+ >
73
+ <Icon icon="ic:round-minus" className="text-lg" />
74
+ </button>
75
+ <button
76
+ type="button"
77
+ className="p-0.5 rounded-md shadow bg-primary-transparent hover:bg-primary-light text-primary disabled:bg-secondary disabled:text-white"
78
+ disabled={rest.disabled}
79
+ onClick={() => {
80
+ setTempValue(
81
+ (
82
+ (parseInt(tempValue) || 0) + ((rest.step as number) || 1)
83
+ ).toString()
84
+ );
85
+ }}
86
+ >
87
+ <Icon icon="ic:round-plus" className="text-lg" />
88
+ </button>
89
+ </div>
90
+ </div>
91
+ </label>
92
+ );
93
+ };