ml-ui-lib 1.0.2 → 1.0.5

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/dist/index.d.ts CHANGED
@@ -5,8 +5,6 @@ export { Modal } from "./components/Modal/Modal";
5
5
  export type { ModalProps } from "./components/Modal/Modal";
6
6
  export { Input } from "./components/Input/Input";
7
7
  export type { InputProps } from "./components/Input/Input";
8
- export { Alert } from "./components/Alert/Alert";
9
- export type { AlertProps, AlertVariant } from "./components/Alert/Alert";
10
8
  export { PhoneInput } from "./components/PhoneInput/PhoneInput";
11
9
  export type { PhoneInputProps } from "./components/PhoneInput/PhoneInput";
12
10
  export { Dropdown } from "./components/Dropdown/Dropdown";
package/dist/index.js CHANGED
@@ -2,7 +2,6 @@ import "./common.css";
2
2
  export { Button } from "./components/Button/Button";
3
3
  export { Modal } from "./components/Modal/Modal";
4
4
  export { Input } from "./components/Input/Input";
5
- export { Alert } from "./components/Alert/Alert";
6
5
  export { PhoneInput } from "./components/PhoneInput/PhoneInput";
7
6
  export { Dropdown } from "./components/Dropdown/Dropdown";
8
7
  export { DatePicker2 } from "./components/DatePicker2/DatePicker2";
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "ml-ui-lib",
3
- "version": "1.0.2",
3
+ "version": "1.0.5",
4
4
  "main": "dist/index.js",
5
5
  "author": "Kenneth James B. Simbulan",
6
6
  "module": "dist/index.esm.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "files": [
9
- "dist"
9
+ "dist",
10
+ "src",
11
+ "common.css",
12
+ "components/**/*.css"
10
13
  ],
11
14
  "scripts": {
12
15
  "build": "tsc",
package/src/common.css ADDED
@@ -0,0 +1,27 @@
1
+ @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600&display=swap');
2
+
3
+ /* Common global styles for all ml-ui-lib components */
4
+ :root {
5
+ --ml-font-family: 'Poppins', sans-serif;
6
+ --ml-color-primary: #0070f3;
7
+ --ml-color-secondary: #eaeaea;
8
+ --ml-radius: 6px;
9
+ }
10
+
11
+ * {
12
+ box-sizing: border-box;
13
+ }
14
+
15
+ body {
16
+ font-family: var(--ml-font-family);
17
+ margin: 0;
18
+ padding: 0;
19
+ background-color: #fff;
20
+ color: #111;
21
+ }
22
+
23
+ button {
24
+ font-family: inherit;
25
+ border-radius: var(--ml-radius);
26
+ transition: all 0.2s ease;
27
+ }
@@ -0,0 +1,24 @@
1
+ .btn {
2
+ border: none;
3
+ border-radius: 6px;
4
+ padding: 10px 16px;
5
+ cursor: pointer;
6
+ font-size: 16px;
7
+ /* margin: 10px; */
8
+ }
9
+
10
+ .btn-default {
11
+ background: #ffffff;
12
+ color: #5d5d5d;
13
+ border: solid 0.5px #5d5d5d;
14
+ }
15
+
16
+ .btn-red {
17
+ background: #ff0000;
18
+ color: #ffffff;
19
+ }
20
+
21
+ .btn-black {
22
+ background: #333333;
23
+ color: #ffffff;
24
+ }
@@ -0,0 +1,20 @@
1
+ import React from "react";
2
+ import "./Button.css";
3
+
4
+ export interface ButtonProps {
5
+ label: string;
6
+ variant?: "default" | "red" | "black";
7
+ onClick?: () => void;
8
+ }
9
+
10
+ export const Button: React.FC<ButtonProps> = ({
11
+ label,
12
+ variant = "default",
13
+ onClick,
14
+ }) => {
15
+ return (
16
+ <button className={`btn btn-${variant}`} onClick={onClick}>
17
+ {label}
18
+ </button>
19
+ );
20
+ };
@@ -0,0 +1,103 @@
1
+ .datepicker-wrapper {
2
+ position: relative;
3
+ font-family: "Poppins", sans-serif;
4
+ width: 250px;
5
+ }
6
+
7
+ .datepicker-input-wrapper {
8
+ display: flex;
9
+ align-items: center;
10
+ position: relative;
11
+ cursor: pointer;
12
+ }
13
+
14
+ .datepicker-input {
15
+ width: 100%;
16
+ padding: 10px 36px 10px 12px;
17
+ border: 1px solid #ddd;
18
+ border-radius: 6px;
19
+ font-size: 15px;
20
+ cursor: pointer;
21
+ }
22
+
23
+ .datepicker-icon {
24
+ position: absolute;
25
+ right: 10px;
26
+ pointer-events: none;
27
+ color: #666;
28
+ }
29
+
30
+ .datepicker-calendar {
31
+ position: absolute;
32
+ top: 45px;
33
+ width: 100%;
34
+ background: #fff;
35
+ border: 1px solid #ddd;
36
+ border-radius: 6px;
37
+ box-shadow: 0 4px 12px rgba(0,0,0,0.1);
38
+ z-index: 10;
39
+ padding: 8px;
40
+ }
41
+
42
+ .datepicker-header {
43
+ display: flex;
44
+ justify-content: space-between;
45
+ align-items: center;
46
+ margin-bottom: 8px;
47
+ font-weight: 500;
48
+ }
49
+
50
+ .datepicker-weekdays {
51
+ display: grid;
52
+ grid-template-columns: repeat(7, 1fr);
53
+ text-align: center;
54
+ font-weight: 500;
55
+ margin-bottom: 4px;
56
+ }
57
+
58
+ .datepicker-weekday {
59
+ font-size: 12px;
60
+ }
61
+
62
+ .datepicker-days-grid {
63
+ display: grid;
64
+ grid-template-columns: repeat(7, 1fr);
65
+ gap: 4px;
66
+ }
67
+
68
+ .datepicker-day {
69
+ text-align: center;
70
+ padding: 6px 0;
71
+ cursor: pointer;
72
+ border-radius: 4px;
73
+ }
74
+
75
+ .datepicker-day:hover {
76
+ background: #f0f0f0;
77
+ }
78
+
79
+ .datepicker-day.selected {
80
+ background: #ff4d4d;
81
+ color: #fff;
82
+ font-weight: 500;
83
+ }
84
+
85
+ .datepicker-day.today {
86
+ border: 1px solid #ff4d4d;
87
+ border-radius: 50%;
88
+ }
89
+
90
+ .datepicker-day.empty {
91
+ pointer-events: none;
92
+ }
93
+
94
+ .has-error .datepicker-input {
95
+ border-color: #ff4d4d;
96
+ }
97
+
98
+ .datepicker-error {
99
+ color: #ff4d4d;
100
+ font-size: 13px;
101
+ margin-top: 4px;
102
+ display: block;
103
+ }
@@ -0,0 +1,158 @@
1
+ "use client";
2
+ import React, { useState, useRef, useEffect } from "react";
3
+ import "./DatePicker.css";
4
+
5
+ export interface DatePickerValue {
6
+ day: number;
7
+ month: number;
8
+ year: number;
9
+ }
10
+
11
+ export interface DatePickerProps {
12
+ value?: DatePickerValue;
13
+ onChange?: (value: DatePickerValue) => void;
14
+ placeholder?: string;
15
+ className?: string;
16
+ error?: string;
17
+ }
18
+
19
+ export const DatePicker: React.FC<DatePickerProps> = ({
20
+ value,
21
+ onChange,
22
+ placeholder = "Select date",
23
+ className = "",
24
+ error,
25
+ }) => {
26
+ const [open, setOpen] = useState(false);
27
+ const [selectedDate, setSelectedDate] = useState<DatePickerValue | null>(value || null);
28
+
29
+ const today = new Date();
30
+ const [currentMonth, setCurrentMonth] = useState<number>(
31
+ value?.month ? value.month - 1 : today.getMonth()
32
+ );
33
+ const [currentYear, setCurrentYear] = useState<number>(value?.year || today.getFullYear());
34
+
35
+ const wrapperRef = useRef<HTMLDivElement>(null);
36
+
37
+ useEffect(() => {
38
+ const handleClickOutside = (e: MouseEvent) => {
39
+ if (wrapperRef.current && !wrapperRef.current.contains(e.target as Node)) {
40
+ setOpen(false);
41
+ }
42
+ };
43
+ document.addEventListener("mousedown", handleClickOutside);
44
+ return () => document.removeEventListener("mousedown", handleClickOutside);
45
+ }, []);
46
+
47
+ const daysInMonth = new Date(currentYear, currentMonth + 1, 0).getDate();
48
+ const firstDayOfMonth = new Date(currentYear, currentMonth, 1).getDay();
49
+ const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
50
+ const weekDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
51
+
52
+ const handleDayClick = (day: number) => {
53
+ const newDate = { day, month: currentMonth + 1, year: currentYear };
54
+ setSelectedDate(newDate);
55
+ onChange?.(newDate);
56
+ setOpen(false);
57
+ };
58
+
59
+ return (
60
+ <div className={`datepicker-wrapper ${className} ${error ? "has-error" : ""}`} ref={wrapperRef}>
61
+ <div className="datepicker-input-wrapper" onClick={() => setOpen(!open)}>
62
+ <input
63
+ readOnly
64
+ value={
65
+ selectedDate
66
+ ? `${String(selectedDate.day).padStart(2, "0")}/${String(selectedDate.month).padStart(2, "0")}/${selectedDate.year}`
67
+ : ""
68
+ }
69
+ placeholder={placeholder}
70
+ className="datepicker-input"
71
+ />
72
+ {/* Inline line-style calendar icon */}
73
+ <svg
74
+ className="datepicker-icon"
75
+ xmlns="http://www.w3.org/2000/svg"
76
+ viewBox="0 0 24 24"
77
+ fill="none"
78
+ stroke="currentColor"
79
+ strokeWidth="2"
80
+ strokeLinecap="round"
81
+ strokeLinejoin="round"
82
+ width="18"
83
+ height="18"
84
+ >
85
+ <rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
86
+ <line x1="16" y1="2" x2="16" y2="6"></line>
87
+ <line x1="8" y1="2" x2="8" y2="6"></line>
88
+ <line x1="3" y1="10" x2="21" y2="10"></line>
89
+ </svg>
90
+ </div>
91
+
92
+ {open && (
93
+ <div className="datepicker-calendar">
94
+ {/* Header */}
95
+ <div className="datepicker-header">
96
+ <button
97
+ onClick={() =>
98
+ setCurrentMonth((prev) => (prev === 0 ? 11 : prev - 1))
99
+ }
100
+ >
101
+
102
+ </button>
103
+ <span>
104
+ {monthNames[currentMonth]} {currentYear}
105
+ </span>
106
+ <button
107
+ onClick={() =>
108
+ setCurrentMonth((prev) => (prev === 11 ? 0 : prev + 1))
109
+ }
110
+ >
111
+
112
+ </button>
113
+ </div>
114
+
115
+ {/* Weekday Names */}
116
+ <div className="datepicker-weekdays">
117
+ {weekDays.map((d) => (
118
+ <div key={d} className="datepicker-weekday">
119
+ {d}
120
+ </div>
121
+ ))}
122
+ </div>
123
+
124
+ {/* Days */}
125
+ <div className="datepicker-days-grid">
126
+ {Array.from({ length: firstDayOfMonth }).map((_, i) => (
127
+ <div key={`empty-${i}`} className="datepicker-day empty" />
128
+ ))}
129
+ {Array.from({ length: daysInMonth }).map((_, i) => {
130
+ const day = i + 1;
131
+ const isSelected =
132
+ selectedDate?.day === day &&
133
+ selectedDate?.month === currentMonth + 1 &&
134
+ selectedDate?.year === currentYear;
135
+ const isToday =
136
+ today.getDate() === day &&
137
+ today.getMonth() === currentMonth &&
138
+ today.getFullYear() === currentYear;
139
+ return (
140
+ <div
141
+ key={day}
142
+ className={`datepicker-day ${isSelected ? "selected" : ""} ${isToday ? "today" : ""}`}
143
+ onClick={() => handleDayClick(day)}
144
+ >
145
+ {day}
146
+ </div>
147
+ );
148
+ })}
149
+ </div>
150
+ </div>
151
+ )}
152
+
153
+ {error && <span className="datepicker-error">{error}</span>}
154
+ </div>
155
+ );
156
+ };
157
+
158
+ export default DatePicker;
@@ -0,0 +1,113 @@
1
+ .date-picker-wrapper {
2
+ display: flex;
3
+ gap: 8px;
4
+ font-family: "Poppins", sans-serif;
5
+ flex-wrap: wrap;
6
+ }
7
+
8
+ .full-width {
9
+ flex: 1 1 100px;
10
+ /* position: relative; */
11
+ }
12
+
13
+ .date-picker-dropdown {
14
+ cursor: pointer;
15
+ background: #fff;
16
+ border: 1px solid #ddd;
17
+ border-radius: 6px;
18
+ align-items: center;
19
+ padding: 0.4rem 0.6rem;
20
+ display: flex;
21
+ justify-content: space-between;
22
+ width: 100%;
23
+ }
24
+
25
+ .date-picker-dropdown-carret {
26
+ font-size: 12px;
27
+ color: #666;
28
+ }
29
+
30
+ .date-picker-grid-wrapper {
31
+ width: 30%;
32
+ position: absolute;
33
+ /* left: 0px; */
34
+ /* top: 40px;
35
+ background: #fff;
36
+ border: 1px solid #ddd;
37
+ border-radius: 6px;
38
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
39
+ z-index: 10; */
40
+ }
41
+
42
+ .date-picker-header {
43
+ display: flex;
44
+ justify-content: space-between;
45
+ flex-direction: row;
46
+ position: absolute;
47
+ z-index: 100;
48
+ left: 0px;
49
+ padding: 10px;
50
+ width: 100%;
51
+ background-color: #f5f5f5;
52
+ margin-top: 5px;
53
+ border-top-left-radius: 8px;
54
+ border-top-right-radius: 8px;
55
+ }
56
+
57
+ .date-picker-header button {
58
+ background-color: #ffffff;
59
+ color: #5f5f5f;
60
+ border-radius: 15px;
61
+ border: 0.5px solid #5f5f5f;
62
+ }
63
+
64
+ .date-picker-grid {
65
+ display: grid;
66
+ grid-template-columns: repeat(3, 1fr);
67
+ gap: 8px;
68
+ padding: 10px;
69
+ background-color: white;
70
+ border-radius: 8px;
71
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08);
72
+ max-height: 250px;
73
+ overflow-y: auto;
74
+ position: absolute;
75
+ z-index: 30;
76
+ width: 100%;
77
+ margin-top: 45px;
78
+ left: 0px;
79
+ }
80
+
81
+ .date-picker-dropdown-option {
82
+ text-align: center;
83
+ padding: 10px;
84
+ border-radius: 6px;
85
+ cursor: pointer;
86
+ /* font-size: 14px;
87
+ font-weight: 500; */
88
+ transition: background-color 0.2s ease, color 0.2s ease;
89
+ user-select: none;
90
+ }
91
+
92
+ .date-picker-dropdown-option:hover {
93
+ background-color: #f0f0f0;
94
+ }
95
+
96
+ .date-picker-dropdown-option.selected {
97
+ background-color: #333;
98
+ color: #fff;
99
+ }
100
+
101
+ .date-picker-empty {
102
+ gap: 8px;
103
+ padding: 10px;
104
+ background-color: white;
105
+ border-radius: 8px;
106
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08);
107
+ overflow-y: auto;
108
+ position: absolute;
109
+ z-index: 30;
110
+ /* width: 100%; */
111
+ margin-top: 10px;
112
+ left: 0px;
113
+ }
@@ -0,0 +1,179 @@
1
+ import React, { useState, useEffect, useRef } from "react";
2
+ import "./DatePicker2.css";
3
+
4
+ export interface DateValue {
5
+ month: number;
6
+ day: number;
7
+ year: number;
8
+ }
9
+
10
+ export interface DatePicker2Props {
11
+ value?: DateValue;
12
+ onChange?: (date: DateValue) => void;
13
+ }
14
+
15
+ export const DatePicker2: React.FC<DatePicker2Props> = ({ value, onChange }) => {
16
+ const currentYear = new Date().getFullYear();
17
+ const months = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
18
+
19
+ const [year, setYear] = useState<number | null>(null);
20
+ const [month, setMonth] = useState<number | null>(null);
21
+ const [monthLabel, setMonthLabel] = useState<string | null>(null);
22
+ const [day, setDay] = useState<number | null>(null);
23
+ const [daysInMonth, setDaysInMonth] = useState<number[]>([]);
24
+
25
+
26
+ useEffect(() => {
27
+ if (month && day && year && onChange) {
28
+ const newDate = { month, day, year };
29
+ // Only call onChange if different
30
+ if (
31
+ !value ||
32
+ value.month !== month ||
33
+ value.day !== day ||
34
+ value.year !== year
35
+ ) {
36
+ onChange(newDate);
37
+ }
38
+ }
39
+ }, [month, day, year]);
40
+
41
+
42
+ useEffect(() => {
43
+ if (value) {
44
+ setYear(value.year);
45
+ setMonth(value.month);
46
+ setDay(value.day);
47
+ setMonthLabel(months[value.month - 1]);
48
+ }
49
+ }, [value]);
50
+
51
+ const [yearOpen, setYearOpen] = useState(false);
52
+ const [monthOpen, setMonthOpen] = useState(false);
53
+ const [dayOpen, setDayOpen] = useState(false);
54
+ const wrapperRef = useRef<HTMLDivElement>(null);
55
+
56
+ useEffect(() => {
57
+ const handleClickOutside = (e: MouseEvent) => {
58
+ if (wrapperRef.current && !wrapperRef.current.contains(e.target as Node)) {
59
+ setYearOpen(false);
60
+ setMonthOpen(false);
61
+ setDayOpen(false);
62
+ }
63
+ };
64
+ document.addEventListener("mousedown", handleClickOutside);
65
+ return () => document.removeEventListener("mousedown", handleClickOutside);
66
+ }, []);
67
+
68
+ // Update days in month
69
+ useEffect(() => {
70
+ if (month && year) {
71
+ const totalDays = new Date(year, month, 0).getDate();
72
+ setDaysInMonth([...Array(totalDays)].map((_, i) => i + 1));
73
+ if (day && day > totalDays) setDay(totalDays);
74
+ } else {
75
+ setDaysInMonth([]);
76
+ setDay(null);
77
+ }
78
+ }, [month, year]);
79
+
80
+ // Trigger onChange
81
+ useEffect(() => {
82
+ if (year && month && day && onChange) {
83
+ onChange({ year, month, day });
84
+ }
85
+ }, [year, month, day, onChange]);
86
+
87
+ return (
88
+ <div className="date-picker-wrapper" ref={wrapperRef}>
89
+ {/* Year */}
90
+ <div className="full-width">
91
+ <div className="date-picker-dropdown" onClick={() => { setYearOpen(!yearOpen); setMonthOpen(false); setDayOpen(false); }}>
92
+ <span className="readble">{year ?? "Year"}</span>
93
+ <span className="date-picker-dropdown-carret">▼</span>
94
+ </div>
95
+ {yearOpen && (
96
+ <div className="date-picker-grid-wrapper">
97
+ <div className="date-picker-header">
98
+ <span>Select Year</span>
99
+ <button onClick={() => setYearOpen(false)}>✕</button>
100
+ </div>
101
+ <div className="date-picker-grid scrollbar">
102
+ {[...Array(100)].map((_, i) => {
103
+ const y = currentYear - i;
104
+ return (
105
+ <div
106
+ key={y}
107
+ className={`date-picker-dropdown-option ${y === year ? "selected" : ""}`}
108
+ onClick={() => { setYear(y); setYearOpen(false); setMonthOpen(true); }}
109
+ >
110
+ {y}
111
+ </div>
112
+ );
113
+ })}
114
+ </div>
115
+ </div>
116
+ )}
117
+ </div>
118
+
119
+ {/* Month */}
120
+ <div className="full-width">
121
+ <div className="date-picker-dropdown" onClick={() => { setMonthOpen(!monthOpen); setYearOpen(false); setDayOpen(false); }}>
122
+ <span className="readble">{monthLabel ?? "Month"}</span>
123
+ <span className="date-picker-dropdown-carret">▼</span>
124
+ </div>
125
+ {monthOpen && (
126
+ <div className="date-picker-grid-wrapper">
127
+ <div className="date-picker-header">
128
+ <span>Select Month</span>
129
+ <button onClick={() => setMonthOpen(false)}>✕</button>
130
+ </div>
131
+ <div className="date-picker-grid scrollbar">
132
+ {months.map((m, idx) => (
133
+ <div
134
+ key={idx}
135
+ className={`date-picker-dropdown-option ${month === idx + 1 ? "selected" : ""}`}
136
+ onClick={() => { setMonth(idx + 1); setMonthLabel(m); setMonthOpen(false); setDayOpen(true); }}
137
+ >
138
+ {m}
139
+ </div>
140
+ ))}
141
+ </div>
142
+ </div>
143
+ )}
144
+ </div>
145
+
146
+ {/* Day */}
147
+ <div className="full-width">
148
+ <div className="date-picker-dropdown" onClick={() => { setDayOpen(!dayOpen); setMonthOpen(false); setYearOpen(false); }}>
149
+ <span className="readble">{day ?? "Day"}</span>
150
+ <span className="date-picker-dropdown-carret">▼</span>
151
+ </div>
152
+ {dayOpen && daysInMonth.length > 0 && (
153
+ <div className="date-picker-grid-wrapper">
154
+ <div className="date-picker-header">
155
+ <span>Select Day</span>
156
+ <button onClick={() => setDayOpen(false)}>✕</button>
157
+ </div>
158
+ <div className="date-picker-grid scrollbar">
159
+ {daysInMonth.map((d) => (
160
+ <div
161
+ key={d}
162
+ className={`date-picker-dropdown-option ${day === d ? "selected" : ""}`}
163
+ onClick={() => {setDay(d); setDayOpen(false);}}
164
+ >
165
+ {d}
166
+ </div>
167
+ ))}
168
+ </div>
169
+ </div>
170
+ )}
171
+ {dayOpen && daysInMonth.length === 0 && (
172
+ <div className="date-picker-empty">Select Year and Month first.</div>
173
+ )}
174
+ </div>
175
+ </div>
176
+ );
177
+ };
178
+
179
+ export default DatePicker2;
@@ -0,0 +1,84 @@
1
+ .dropdown-wrapper {
2
+ position: relative;
3
+ display: flex;
4
+ flex-direction: column;
5
+ font-family: "Poppins", sans-serif;
6
+ gap: 4px;
7
+ }
8
+
9
+ .dropdown-label {
10
+ font-size: 14px;
11
+ font-weight: 500;
12
+ color: #333;
13
+ }
14
+
15
+ .dropdown-trigger {
16
+ display: flex;
17
+ justify-content: space-between;
18
+ align-items: center;
19
+ padding: 10px 12px;
20
+ border: 1px solid #ddd;
21
+ border-radius: 6px;
22
+ background: #fff;
23
+ cursor: pointer;
24
+ transition: border-color 0.2s;
25
+ }
26
+
27
+ .dropdown-trigger:hover {
28
+ border-color: #888;
29
+ }
30
+
31
+ .dropdown-value {
32
+ font-size: 15px;
33
+ color: #333;
34
+ }
35
+
36
+ .dropdown-caret {
37
+ font-size: 12px;
38
+ color: #666;
39
+ }
40
+
41
+ .dropdown-list {
42
+ position: absolute;
43
+ top: 100%;
44
+ left: 0;
45
+ width: 100%;
46
+ background: #fff;
47
+ border: 1px solid #ddd;
48
+ border-radius: 6px;
49
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
50
+ z-index: 10;
51
+ max-height: 200px;
52
+ overflow-y: auto;
53
+ margin-top: 4px;
54
+ }
55
+
56
+ .dropdown-option {
57
+ padding: 8px 12px;
58
+ cursor: pointer;
59
+ transition: background 0.2s;
60
+ }
61
+
62
+ .dropdown-option:hover {
63
+ background: #f5f5f5;
64
+ }
65
+
66
+ .dropdown-option.selected {
67
+ background: #ff4d4d10;
68
+ font-weight: 500;
69
+ }
70
+
71
+ .dropdown-empty {
72
+ padding: 8px 12px;
73
+ color: #999;
74
+ }
75
+
76
+ .dropdown-error {
77
+ margin-top: 4px;
78
+ color: #ff4d4d;
79
+ font-size: 13px;
80
+ }
81
+
82
+ .has-error .dropdown-trigger {
83
+ border-color: #ff4d4d;
84
+ }
@@ -0,0 +1,76 @@
1
+ import React, { useState, useRef, useEffect } from "react";
2
+ import "./Dropdown.css";
3
+
4
+ export interface DropdownProps {
5
+ label?: string;
6
+ options: string[];
7
+ value: string;
8
+ onChange: (value: string) => void;
9
+ placeholder?: string;
10
+ className?: string;
11
+ error?: string;
12
+ }
13
+
14
+ export const Dropdown: React.FC<DropdownProps> = ({
15
+ label,
16
+ value,
17
+ options,
18
+ onChange,
19
+ placeholder = "Select",
20
+ className = "",
21
+ error,
22
+ }) => {
23
+ const [dropdownOpen, setDropdownOpen] = useState(false);
24
+ const dropdownRef = useRef<HTMLDivElement>(null);
25
+
26
+ useEffect(() => {
27
+ const handleClickOutside = (e: MouseEvent) => {
28
+ if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
29
+ setDropdownOpen(false);
30
+ }
31
+ };
32
+ document.addEventListener("mousedown", handleClickOutside);
33
+ return () => document.removeEventListener("mousedown", handleClickOutside);
34
+ }, []);
35
+
36
+ return (
37
+ <div className={`dropdown-wrapper ${className} ${error ? "has-error" : ""}`} ref={dropdownRef}>
38
+ {label && <label className="dropdown-label">{label}</label>}
39
+
40
+ <div
41
+ className="dropdown-trigger"
42
+ onClick={() => setDropdownOpen(!dropdownOpen)}
43
+ >
44
+ <span className="dropdown-value">
45
+ {value === "" ? placeholder : value}
46
+ </span>
47
+ <span className="dropdown-caret">▼</span>
48
+ </div>
49
+
50
+ {dropdownOpen && (
51
+ <div className="dropdown-list scrollbar">
52
+ {options.length > 0 ? (
53
+ options.map((option, i) => (
54
+ <div
55
+ key={i}
56
+ className={`dropdown-option ${value === option ? "selected" : ""}`}
57
+ onClick={() => {
58
+ onChange(option);
59
+ setDropdownOpen(false);
60
+ }}
61
+ >
62
+ {option}
63
+ </div>
64
+ ))
65
+ ) : (
66
+ <div className="dropdown-empty">No options</div>
67
+ )}
68
+ </div>
69
+ )}
70
+
71
+ {error && <span className="dropdown-error">{error}</span>}
72
+ </div>
73
+ );
74
+ };
75
+
76
+ export default Dropdown;
@@ -0,0 +1,36 @@
1
+ .input-wrapper {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: 6px;
5
+ width: 100%;
6
+ }
7
+
8
+ .input-label {
9
+ font-size: 14px;
10
+ font-weight: 500;
11
+ color: #333;
12
+ }
13
+
14
+ .input {
15
+ font-family: 'Poppins', sans-serif;
16
+ font-size: 15px;
17
+ padding: 10px 12px;
18
+ border: 1px solid #ccc;
19
+ border-radius: 6px;
20
+ outline: none;
21
+ transition: all 0.2s ease;
22
+ }
23
+
24
+ .input:focus {
25
+ border-color: #0070f3;
26
+ box-shadow: 0 0 0 2px rgba(0, 112, 243, 0.2);
27
+ }
28
+
29
+ .input-error {
30
+ border-color: #ff4d4f;
31
+ }
32
+
33
+ .input-error-text {
34
+ color: #ff4d4f;
35
+ font-size: 12px;
36
+ }
@@ -0,0 +1,24 @@
1
+ import React from "react";
2
+ import "./Input.css";
3
+
4
+ export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
5
+ label?: string;
6
+ error?: string;
7
+ }
8
+
9
+ export const Input: React.FC<InputProps> = ({ label, error, ...props }) => {
10
+ return (
11
+ <div className="input-wrapper">
12
+ {label && <label className="input-label">{label}</label>}
13
+ <input
14
+ className={`input ${error ? "input-error" : ""}`}
15
+ {...props}
16
+ autoComplete="off"
17
+ autoCorrect="off"
18
+ autoCapitalize="off"
19
+ spellCheck={false}
20
+ />
21
+ {error && <span className="input-error-text">{error}</span>}
22
+ </div>
23
+ );
24
+ };
@@ -0,0 +1,81 @@
1
+ .modal-overlay {
2
+ position: fixed;
3
+ inset: 0;
4
+ background: rgba(0, 0, 0, 0.4);
5
+ display: flex;
6
+ justify-content: center;
7
+ align-items: center;
8
+ backdrop-filter: blur(4px);
9
+ animation: fadeIn 0.25s ease;
10
+ }
11
+
12
+ .modal {
13
+ background: #fff;
14
+ border-radius: 10px;
15
+ padding: 24px;
16
+ max-width: 420px;
17
+ width: 90%;
18
+ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
19
+ font-family: "Poppins", sans-serif;
20
+ animation: modalFadeIn 0.25s ease;
21
+ border-top: 5px solid transparent;
22
+ }
23
+
24
+ .modal-header {
25
+ display: flex;
26
+ align-items: center;
27
+ gap: 10px;
28
+ margin-bottom: 16px;
29
+ }
30
+
31
+ .modal-icon {
32
+ width: 26px;
33
+ height: 26px;
34
+ object-fit: contain;
35
+ }
36
+
37
+ .modal-title {
38
+ font-size: 20px;
39
+ font-weight: 600;
40
+ }
41
+
42
+ /* Variants */
43
+ .modal-default {
44
+ border-color: #ccc;
45
+ }
46
+
47
+ .modal-info {
48
+ border-color: #3b82f6;
49
+ }
50
+
51
+ .modal-success {
52
+ border-color: #22c55e;
53
+ }
54
+
55
+ .modal-warning {
56
+ border-color: #f59e0b;
57
+ }
58
+
59
+ .modal-error {
60
+ border-color: #ef4444;
61
+ }
62
+
63
+ @keyframes modalFadeIn {
64
+ from {
65
+ opacity: 0;
66
+ transform: translateY(-10px);
67
+ }
68
+ to {
69
+ opacity: 1;
70
+ transform: translateY(0);
71
+ }
72
+ }
73
+
74
+ @keyframes fadeIn {
75
+ from {
76
+ opacity: 0;
77
+ }
78
+ to {
79
+ opacity: 1;
80
+ }
81
+ }
@@ -0,0 +1,63 @@
1
+ import React, { useEffect } from "react";
2
+ import "./Modal.css";
3
+
4
+ // import DefaultIcon from "./icons/default.png";
5
+ // import InfoIcon from "./icons/info.png";
6
+ // import SuccessIcon from "./icons/success.png";
7
+ // import WarningIcon from "./icons/error.png";
8
+ import ErrorIcon from "./icons/error.png";
9
+
10
+ export interface ModalProps {
11
+ open: boolean;
12
+ onClose: () => void;
13
+ title?: string;
14
+ children: React.ReactNode;
15
+ zIndex?: number;
16
+ variant?: "default" | "info" | "success" | "warning" | "error";
17
+ }
18
+
19
+ export const Modal: React.FC<ModalProps> = ({
20
+ open,
21
+ onClose,
22
+ title,
23
+ children,
24
+ zIndex = 1000,
25
+ variant = "default",
26
+ }) => {
27
+ useEffect(() => {
28
+ document.body.style.overflow = open ? "hidden" : "";
29
+ }, [open]);
30
+
31
+ if (!open) return null;
32
+
33
+ const variantIcon = {
34
+ // default: DefaultIcon,
35
+ // info: InfoIcon,
36
+ // success: SuccessIcon,
37
+ // warning: WarningIcon,
38
+ error: ErrorIcon,
39
+ }[variant];
40
+
41
+
42
+ return (
43
+ <div
44
+ className="modal-overlay"
45
+ onClick={onClose}
46
+ style={{ zIndex }}
47
+ >
48
+ <div
49
+ className={`modal modal-${variant}`}
50
+ onClick={(e) => e.stopPropagation()}
51
+ style={{ zIndex: zIndex + 1 }}
52
+ >
53
+ {title && (
54
+ <div className="modal-header">
55
+ {/* <img src={variantIcon} alt={variant} className="modal-icon" /> */}
56
+ <h2 className="modal-title">{title}</h2>
57
+ </div>
58
+ )}
59
+ <div className="modal-content">{children}</div>
60
+ </div>
61
+ </div>
62
+ );
63
+ };
@@ -0,0 +1,78 @@
1
+ .phone-input-wrapper {
2
+ position: relative;
3
+ display: flex;
4
+ align-items: center;
5
+ gap: 8px;
6
+ width: 100%;
7
+ font-family: "Poppins", sans-serif;
8
+ }
9
+
10
+ .country-dropdown {
11
+ display: flex;
12
+ align-items: center;
13
+ gap: 6px;
14
+ padding: 10.5px 10px;
15
+ border: 1px solid #ddd;
16
+ border-radius: 6px;
17
+ background: #fff;
18
+ cursor: pointer;
19
+ }
20
+
21
+ .country-flag {
22
+ width: 20px;
23
+ height: 14px;
24
+ border-radius: 2px;
25
+ }
26
+
27
+ .country-code {
28
+ font-size: 15px;
29
+ color: #333;
30
+ }
31
+
32
+ .country-dropdown-menu {
33
+ position: absolute;
34
+ top: 50px;
35
+ left: 0;
36
+ width: 180px;
37
+ background: #fff;
38
+ border: 1px solid #ddd;
39
+ border-radius: 8px;
40
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
41
+ z-index: 10;
42
+ max-height: 200px;
43
+ overflow-y: auto;
44
+ }
45
+
46
+ .country-dropdown-item {
47
+ display: flex;
48
+ align-items: center;
49
+ justify-content: space-between;
50
+ padding: 8px 10px;
51
+ cursor: pointer;
52
+ transition: background 0.2s;
53
+ }
54
+
55
+ .country-dropdown-item:hover {
56
+ background: #f5f5f5;
57
+ }
58
+
59
+ .country-code-text {
60
+ font-size: 13px;
61
+ color: #777;
62
+ }
63
+
64
+ .phone-input {
65
+ flex: 1;
66
+ padding: 10px 14px;
67
+ border: 1px solid #ddd;
68
+ border-radius: 6px;
69
+ font-size: 15px;
70
+ font-family: inherit;
71
+ outline: none;
72
+ transition: border-color 0.2s, box-shadow 0.2s;
73
+ }
74
+
75
+ .phone-input:focus {
76
+ border-color: #ff4d4d;
77
+ box-shadow: 0 0 0 2px rgba(255, 0, 0, 0.1);
78
+ }
@@ -0,0 +1,127 @@
1
+ "use client";
2
+ import React, { useEffect, useRef, useState } from "react";
3
+ import "./PhoneInput.css";
4
+
5
+ interface CountryOption {
6
+ code: string;
7
+ country: string;
8
+ flag: string; // CDN URL or emoji
9
+ placeholder: string;
10
+ }
11
+
12
+ export interface PhoneInputProps {
13
+ value: string;
14
+ onChange: (formatted: string) => void;
15
+ country?: string;
16
+ onCountryChange?: (countryCode: string) => void;
17
+ }
18
+
19
+ const countryOptions: Record<string, CountryOption> = {
20
+ PH: {
21
+ code: "+63",
22
+ country: "Philippines",
23
+ flag: "https://flagcdn.com/w20/ph.png",
24
+ placeholder: "988 888 8888",
25
+ },
26
+ US: {
27
+ code: "+1",
28
+ country: "United States",
29
+ flag: "https://flagcdn.com/w20/us.png",
30
+ placeholder: "(000) 888 8888",
31
+ },
32
+ CA: {
33
+ code: "+1",
34
+ country: "Canada",
35
+ flag: "https://flagcdn.com/w20/ca.png",
36
+ placeholder: "000 888 8888",
37
+ },
38
+ };
39
+
40
+ export const PhoneInput: React.FC<PhoneInputProps> = ({
41
+ value,
42
+ onChange,
43
+ country = "PH",
44
+ onCountryChange,
45
+ }) => {
46
+ const [selectedCountry, setSelectedCountry] = useState<string>(country);
47
+ const [dropdownOpen, setDropdownOpen] = useState(false);
48
+ const dropdownRef = useRef<HTMLDivElement>(null);
49
+
50
+ useEffect(() => {
51
+ const handleClickOutside = (e: MouseEvent) => {
52
+ if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
53
+ setDropdownOpen(false);
54
+ }
55
+ };
56
+ document.addEventListener("mousedown", handleClickOutside);
57
+ return () => document.removeEventListener("mousedown", handleClickOutside);
58
+ }, []);
59
+
60
+ const handleCountrySelect = (countryKey: string) => {
61
+ setSelectedCountry(countryKey);
62
+ setDropdownOpen(false);
63
+ onCountryChange?.(countryKey);
64
+ };
65
+
66
+ const formatPhone = (raw: string) => {
67
+ const digits = raw.replace(/\D/g, "");
68
+ if (selectedCountry === "PH") {
69
+ return digits.replace(/^(\d{3})(\d{3})(\d{0,4}).*/, "$1 $2 $3").trim();
70
+ }
71
+ if (selectedCountry === "US" || selectedCountry === "CA") {
72
+ return digits.replace(/^(\d{3})(\d{3})(\d{0,4}).*/, "($1) $2 $3").trim();
73
+ }
74
+ return digits;
75
+ };
76
+
77
+ const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
78
+ const formatted = formatPhone(e.target.value);
79
+ onChange(formatted);
80
+ };
81
+
82
+ return (
83
+ <div className="phone-input-wrapper" ref={dropdownRef}>
84
+ {/* Country Selector */}
85
+ <div
86
+ className="country-dropdown"
87
+ onClick={() => setDropdownOpen(!dropdownOpen)}
88
+ >
89
+ <img
90
+ src={countryOptions[selectedCountry].flag}
91
+ alt={countryOptions[selectedCountry].country}
92
+ className="country-flag"
93
+ />
94
+ <span className="country-code">{countryOptions[selectedCountry].code}</span>
95
+ </div>
96
+
97
+ {/* Dropdown Menu */}
98
+ {dropdownOpen && (
99
+ <div className="country-dropdown-menu">
100
+ {Object.entries(countryOptions).map(([key, { code, country, flag }]) => (
101
+ <div
102
+ key={key}
103
+ className="country-dropdown-item"
104
+ onClick={() => handleCountrySelect(key)}
105
+ >
106
+ <img src={flag} alt={country} className="country-flag" />
107
+ <span>{country}</span>
108
+ <span className="country-code-text">{code}</span>
109
+ </div>
110
+ ))}
111
+ </div>
112
+ )}
113
+
114
+ {/* Input */}
115
+ <input
116
+ type="text"
117
+ value={value}
118
+ onChange={handleInputChange}
119
+ placeholder={countryOptions[selectedCountry].placeholder}
120
+ className="phone-input"
121
+ autoComplete="off"
122
+ />
123
+ </div>
124
+ );
125
+ };
126
+
127
+ export default PhoneInput;
@@ -0,0 +1,14 @@
1
+ declare module "*.png" {
2
+ const value: string;
3
+ export default value;
4
+ }
5
+
6
+ declare module "*.jpg" {
7
+ const value: string;
8
+ export default value;
9
+ }
10
+
11
+ declare module "*.svg" {
12
+ const value: string;
13
+ export default value;
14
+ }
package/src/index.ts ADDED
@@ -0,0 +1,22 @@
1
+ import "./common.css";
2
+
3
+ export { Button } from "./components/Button/Button";
4
+ export type { ButtonProps } from "./components/Button/Button";
5
+
6
+ export { Modal } from "./components/Modal/Modal";
7
+ export type { ModalProps } from "./components/Modal/Modal";
8
+
9
+ export { Input } from "./components/Input/Input";
10
+ export type { InputProps } from "./components/Input/Input";
11
+
12
+ export { PhoneInput } from "./components/PhoneInput/PhoneInput";
13
+ export type { PhoneInputProps } from "./components/PhoneInput/PhoneInput";
14
+
15
+ export { Dropdown } from "./components/Dropdown/Dropdown";
16
+ export type { DropdownProps } from "./components/Dropdown/Dropdown";
17
+
18
+ export { DatePicker2 } from "./components/DatePicker2/DatePicker2";
19
+ export type { DatePicker2Props, DateValue } from "./components/DatePicker2/DatePicker2";
20
+
21
+ export { DatePicker } from "./components/DatePicker/DatePicker";
22
+ export type { DatePickerProps, DatePickerValue } from "./components/DatePicker/DatePicker";