@tunjiadeyemi/ui 1.0.1 → 1.2.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.
package/README.md CHANGED
@@ -19,24 +19,33 @@ yarn add @tunjiadeyemi/ui framer-motion lucide-react
19
19
  First, import the styles in your app's entry point (e.g., `App.tsx` or `main.tsx`):
20
20
 
21
21
  ```tsx
22
- import '@tunjiadeyemi/ui/styles.css';
22
+ import "@tunjiadeyemi/ui/styles.css";
23
23
  ```
24
24
 
25
25
  Then use the components:
26
26
 
27
27
  ```tsx
28
- import { Modal } from '@tunjiadeyemi/ui';
29
- import { useState } from 'react';
28
+ import { Modal, Input, Skeleton } from "@tunjiadeyemi/ui";
29
+ import { useState } from "react";
30
30
 
31
31
  function App() {
32
32
  const [showModal, setShowModal] = useState(false);
33
+ const [email, setEmail] = useState("");
33
34
 
34
35
  return (
35
36
  <>
37
+ <Input
38
+ type="email"
39
+ value={email}
40
+ onChange={(e) => setEmail(e.target.value)}
41
+ placeholder="Enter your email"
42
+ validate={true}
43
+ />
44
+
36
45
  <button onClick={() => setShowModal(true)}>Open Modal</button>
37
-
38
- <Modal
39
- showModal={showModal}
46
+
47
+ <Modal
48
+ showModal={showModal}
40
49
  onClose={() => setShowModal(false)}
41
50
  revealMode="fade"
42
51
  className="bg-white p-8 rounded-lg max-w-md"
@@ -44,24 +53,114 @@ function App() {
44
53
  <h2 className="text-2xl font-bold mb-4">Modal Title</h2>
45
54
  <p>Modal content goes here</p>
46
55
  </Modal>
56
+
57
+ <Skeleton width="200px" height="20px" animation="pulse" />
47
58
  </>
48
59
  );
49
60
  }
50
61
  ```
51
62
 
52
- ### Available Components
63
+ ## Available Components
53
64
 
54
- #### Modal
65
+ ### Modal
55
66
 
56
- A flexible modal component with multiple reveal animations.
67
+ A flexible modal component with multiple reveal animations and optional drag-to-dismiss.
57
68
 
58
69
  **Props:**
70
+
59
71
  - `showModal` (boolean): Controls modal visibility
60
72
  - `onClose` (function): Callback when modal is closed
61
- - `revealMode` ('fade' | 'slide-right' | 'slide-bottom'): Animation style
73
+ - `revealMode` ('fade' | 'slide-right' | 'slide-bottom'): Animation style - Default: `'fade'`
74
+ - `isDrag` (boolean): Enable drag to dismiss (for slide-bottom mode) - Default: `false`
62
75
  - `className` (string): Custom classes for the modal content
63
76
  - `children` (ReactNode): Modal content
64
77
 
78
+ **Example:**
79
+
80
+ ```tsx
81
+ <Modal
82
+ showModal={true}
83
+ onClose={() => console.log("Modal closed")}
84
+ revealMode="slide-bottom"
85
+ isDrag={true}
86
+ className="bg-white p-6 rounded-lg"
87
+ >
88
+ <h2>Your Content</h2>
89
+ </Modal>
90
+ ```
91
+
92
+ ### Input
93
+
94
+ A customizable input component with built-in validation, password visibility toggle, and OTP support.
95
+
96
+ **Props:**
97
+
98
+ - `type` ('text' | 'email' | 'password' | 'otp' | 'number'): Input type - Default: `'text'`
99
+ - `value` (string): Input value
100
+ - `onChange` (function): Change handler
101
+ - `placeholder` (string): Placeholder text
102
+ - `validate` (boolean): Enable validation - Default: `false`
103
+ - `minLength` (number): Minimum character length
104
+ - `maxLength` (number): Maximum character length
105
+ - `errorMessage` (string): Custom error message
106
+ - `onOtpClick` (function): Callback for OTP button click (when type is 'otp')
107
+ - `className` (string): Additional CSS classes
108
+ - `width` (string): Input width - Default: `'100%'`
109
+ - `height` (string): Input height - Default: `'40px'`
110
+ - `color` (string): Accent color - Default: `'#6B2CE9'`
111
+ - `textColor` (string): Text color - Default: `'white'`
112
+ - `backgroundColor` (string): Background color - Default: `'#1F1F23'`
113
+ - `borderRadius` (string): Border radius - Default: `'10px'`
114
+
115
+ **Example:**
116
+
117
+ ```tsx
118
+ <Input
119
+ type="email"
120
+ value={email}
121
+ onChange={(e) => setEmail(e.target.value)}
122
+ placeholder="Enter your email"
123
+ validate={true}
124
+ errorMessage="Please enter a valid email"
125
+ />
126
+
127
+ <Input
128
+ type="password"
129
+ value={password}
130
+ onChange={(e) => setPassword(e.target.value)}
131
+ minLength={8}
132
+ validate={true}
133
+ />
134
+
135
+ <Input
136
+ type="otp"
137
+ value={otp}
138
+ onChange={(e) => setOtp(e.target.value)}
139
+ onOtpClick={() => console.log('Send OTP')}
140
+ placeholder="Enter OTP"
141
+ />
142
+ ```
143
+
144
+ ### Skeleton
145
+
146
+ A loading placeholder component with multiple variants and animations.
147
+
148
+ **Props:**
149
+
150
+ - `variant` ('text' | 'circular' | 'rectangular'): Skeleton shape - Default: `'rectangular'`
151
+ - `width` (string | number): Width of skeleton - Default: `'100%'`
152
+ - `height` (string | number): Height of skeleton - Default: `'100%'`
153
+ - `animation` ('pulse' | 'wave' | 'none'): Animation type - Default: `'pulse'`
154
+ - `className` (string): Additional CSS classes
155
+
156
+ **Example:**
157
+
158
+ ```tsx
159
+ <Skeleton variant="text" width="200px" height="20px" />
160
+ <Skeleton variant="circular" width="50px" height="50px" />
161
+ <Skeleton variant="rectangular" width="100%" height="200px" animation="wave" />
162
+ ```
163
+
65
164
  ## Development
66
165
 
67
166
  ```bash
package/dist/index.d.mts CHANGED
@@ -1,34 +1,46 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import React$1 from 'react';
3
2
 
4
3
  type RevealMode = 'fade' | 'slide-right' | 'slide-bottom';
5
4
  interface ModalProps {
6
- children: React$1.ReactNode;
5
+ children: React.ReactNode;
7
6
  className?: string;
8
7
  revealMode?: RevealMode;
9
8
  showModal?: boolean;
10
9
  onClose?: () => void;
11
10
  isDrag?: boolean;
12
11
  }
13
- declare const Modal: ({ children, className, revealMode, showModal, onClose, isDrag }: ModalProps) => react_jsx_runtime.JSX.Element;
14
12
 
15
- declare const TextInput: ({ type, value, onChange, placeholder, className, backgroundColor, onOtpClick }: {
16
- type?: string;
13
+ declare const Modal: ({ isDrag, showModal, onClose, children, className, revealMode, }: ModalProps) => react_jsx_runtime.JSX.Element;
14
+
15
+ interface TextInputProps {
16
+ type?: "password" | "otp" | "text" | "email" | "number";
17
17
  value?: string;
18
18
  onOtpClick?: () => void;
19
19
  onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
20
20
  placeholder?: string;
21
21
  className?: string;
22
22
  backgroundColor?: string;
23
- }) => react_jsx_runtime.JSX.Element;
23
+ validate?: boolean;
24
+ minLength?: number;
25
+ maxLength?: number;
26
+ errorMessage?: string;
27
+ color?: string;
28
+ textColor?: string;
29
+ borderRadius?: string;
30
+ height?: string;
31
+ width?: string;
32
+ }
33
+
34
+ declare const Input: ({ onChange, onOtpClick, validate, type, value, minLength, maxLength, className, placeholder, errorMessage, width, height, color, textColor, borderRadius, backgroundColor, }: TextInputProps) => react_jsx_runtime.JSX.Element;
24
35
 
25
36
  interface SkeletonProps {
26
37
  className?: string;
27
- variant?: 'text' | 'circular' | 'rectangular';
38
+ variant?: "text" | "circular" | "rectangular";
28
39
  width?: string | number;
29
40
  height?: string | number;
30
- animation?: 'pulse' | 'wave' | 'none';
41
+ animation?: "pulse" | "wave" | "none";
31
42
  }
32
- declare const Skeleton: ({ className, variant, width, height, animation }: SkeletonProps) => react_jsx_runtime.JSX.Element;
33
43
 
34
- export { Modal, Skeleton, TextInput };
44
+ declare const Skeleton: ({ className, variant, width, height, animation, }: SkeletonProps) => react_jsx_runtime.JSX.Element;
45
+
46
+ export { Input, Modal, Skeleton };
package/dist/index.d.ts CHANGED
@@ -1,34 +1,46 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import React$1 from 'react';
3
2
 
4
3
  type RevealMode = 'fade' | 'slide-right' | 'slide-bottom';
5
4
  interface ModalProps {
6
- children: React$1.ReactNode;
5
+ children: React.ReactNode;
7
6
  className?: string;
8
7
  revealMode?: RevealMode;
9
8
  showModal?: boolean;
10
9
  onClose?: () => void;
11
10
  isDrag?: boolean;
12
11
  }
13
- declare const Modal: ({ children, className, revealMode, showModal, onClose, isDrag }: ModalProps) => react_jsx_runtime.JSX.Element;
14
12
 
15
- declare const TextInput: ({ type, value, onChange, placeholder, className, backgroundColor, onOtpClick }: {
16
- type?: string;
13
+ declare const Modal: ({ isDrag, showModal, onClose, children, className, revealMode, }: ModalProps) => react_jsx_runtime.JSX.Element;
14
+
15
+ interface TextInputProps {
16
+ type?: "password" | "otp" | "text" | "email" | "number";
17
17
  value?: string;
18
18
  onOtpClick?: () => void;
19
19
  onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
20
20
  placeholder?: string;
21
21
  className?: string;
22
22
  backgroundColor?: string;
23
- }) => react_jsx_runtime.JSX.Element;
23
+ validate?: boolean;
24
+ minLength?: number;
25
+ maxLength?: number;
26
+ errorMessage?: string;
27
+ color?: string;
28
+ textColor?: string;
29
+ borderRadius?: string;
30
+ height?: string;
31
+ width?: string;
32
+ }
33
+
34
+ declare const Input: ({ onChange, onOtpClick, validate, type, value, minLength, maxLength, className, placeholder, errorMessage, width, height, color, textColor, borderRadius, backgroundColor, }: TextInputProps) => react_jsx_runtime.JSX.Element;
24
35
 
25
36
  interface SkeletonProps {
26
37
  className?: string;
27
- variant?: 'text' | 'circular' | 'rectangular';
38
+ variant?: "text" | "circular" | "rectangular";
28
39
  width?: string | number;
29
40
  height?: string | number;
30
- animation?: 'pulse' | 'wave' | 'none';
41
+ animation?: "pulse" | "wave" | "none";
31
42
  }
32
- declare const Skeleton: ({ className, variant, width, height, animation }: SkeletonProps) => react_jsx_runtime.JSX.Element;
33
43
 
34
- export { Modal, Skeleton, TextInput };
44
+ declare const Skeleton: ({ className, variant, width, height, animation, }: SkeletonProps) => react_jsx_runtime.JSX.Element;
45
+
46
+ export { Input, Modal, Skeleton };
package/dist/index.js CHANGED
@@ -20,15 +20,16 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ Input: () => Input_default,
23
24
  Modal: () => Modal_default,
24
- Skeleton: () => Skeleton_default,
25
- TextInput: () => TextInput_default
25
+ Skeleton: () => Skeleton_default
26
26
  });
27
27
  module.exports = __toCommonJS(index_exports);
28
28
 
29
- // src/components/Modal.tsx
29
+ // src/components/Modal/Modal.tsx
30
30
  var import_framer_motion = require("framer-motion");
31
- var import_jsx_runtime = require("react/jsx-runtime");
31
+
32
+ // src/components/Modal/constant.ts
32
33
  var revealVariants = {
33
34
  fade: {
34
35
  initial: { opacity: 0 },
@@ -46,13 +47,16 @@ var revealVariants = {
46
47
  exit: { y: "100%", opacity: 0 }
47
48
  }
48
49
  };
50
+
51
+ // src/components/Modal/Modal.tsx
52
+ var import_jsx_runtime = require("react/jsx-runtime");
49
53
  var Modal = ({
50
- children,
51
- className,
52
- revealMode = "fade",
54
+ isDrag = false,
53
55
  showModal = true,
54
56
  onClose,
55
- isDrag = false
57
+ children,
58
+ className,
59
+ revealMode = "fade"
56
60
  }) => {
57
61
  const variants = revealVariants[revealMode];
58
62
  const handleOverlayClick = (e) => {
@@ -114,88 +118,190 @@ var Modal = ({
114
118
  };
115
119
  var Modal_default = Modal;
116
120
 
117
- // src/components/TextInput.tsx
121
+ // src/components/Input/Input.tsx
118
122
  var import_react = require("react");
119
123
  var import_lucide_react = require("lucide-react");
120
124
  var import_jsx_runtime2 = require("react/jsx-runtime");
121
- var TextInput = ({
125
+ var Input = ({
126
+ onChange,
127
+ onOtpClick,
128
+ validate = false,
122
129
  type,
123
130
  value,
124
- onChange,
125
- placeholder,
131
+ minLength,
132
+ maxLength,
126
133
  className,
127
- backgroundColor = "#1F1F23",
128
- onOtpClick
134
+ placeholder,
135
+ errorMessage,
136
+ width = "100%",
137
+ height = "40px",
138
+ color = "#6B2CE9",
139
+ textColor = "white",
140
+ borderRadius = "10px",
141
+ backgroundColor = "#1F1F23"
129
142
  }) => {
130
143
  const [showPassword, setShowPassword] = (0, import_react.useState)(false);
144
+ const [error, setError] = (0, import_react.useState)(null);
131
145
  const togglePasswordVisibility = () => {
132
146
  setShowPassword(!showPassword);
133
147
  };
148
+ const validateInput = (inputValue = "") => {
149
+ if (!validate) {
150
+ setError(null);
151
+ return true;
152
+ }
153
+ if (minLength && inputValue.length < minLength) {
154
+ setError(errorMessage || `Minimum ${minLength} characters required`);
155
+ return false;
156
+ }
157
+ if (maxLength && inputValue.length > maxLength) {
158
+ setError(errorMessage || `Maximum ${maxLength} characters allowed`);
159
+ return false;
160
+ }
161
+ switch (type) {
162
+ case "email":
163
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
164
+ if (inputValue && !emailRegex.test(inputValue)) {
165
+ setError(errorMessage || "Invalid email address");
166
+ return false;
167
+ }
168
+ break;
169
+ case "password":
170
+ if (!minLength && inputValue.length < 8) {
171
+ setError(errorMessage || "Password must be at least 8 characters");
172
+ return false;
173
+ }
174
+ break;
175
+ case "otp":
176
+ if (inputValue && !/^\d*$/.test(inputValue)) {
177
+ setError(errorMessage || "OTP must contain only numbers");
178
+ return false;
179
+ }
180
+ if (!minLength && !maxLength && inputValue.length !== 6) {
181
+ setError(errorMessage || "OTP must be 6 digits");
182
+ return false;
183
+ }
184
+ break;
185
+ case "number":
186
+ if (inputValue && isNaN(Number(inputValue))) {
187
+ setError(errorMessage || "Must be a valid number");
188
+ return false;
189
+ }
190
+ break;
191
+ }
192
+ setError(null);
193
+ return true;
194
+ };
195
+ (0, import_react.useEffect)(() => {
196
+ if (validate && value !== void 0) {
197
+ validateInput(value);
198
+ }
199
+ }, [value, validate, minLength, maxLength, type]);
134
200
  if (type === "password") {
135
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "relative w-full", children: [
136
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
137
- "input",
201
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { width }, children: [
202
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "relative", style: { width: "100%" }, children: [
203
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
204
+ "input",
205
+ {
206
+ type: showPassword ? "text" : "password",
207
+ value,
208
+ onChange,
209
+ placeholder,
210
+ style: {
211
+ backgroundColor,
212
+ color: textColor,
213
+ borderRadius,
214
+ height,
215
+ width: "100%"
216
+ },
217
+ className: `border ${error ? "border-red-500" : "border-transparent"} placeholder:text-sm text-sm px-4 pr-12 placeholder:opacity-30 focus:outline-none transition ${className}`,
218
+ onFocus: (e) => !error && (e.target.style.borderColor = color),
219
+ onBlur: (e) => e.target.style.borderColor = error ? "#ef4444" : "transparent"
220
+ }
221
+ ),
222
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
223
+ "button",
224
+ {
225
+ type: "button",
226
+ onClick: togglePasswordVisibility,
227
+ className: "absolute right-4 top-1/2 -translate-y-1/2 text-gray-400 hover:text-white transition-colors",
228
+ "aria-label": showPassword ? "Hide password" : "Show password",
229
+ children: showPassword ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.Eye, { className: "cursor-pointer", size: 18 }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.EyeClosed, { className: "cursor-pointer", size: 18 })
230
+ }
231
+ )
232
+ ] }),
233
+ error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-red-500 text-xs mt-1 px-1", children: error })
234
+ ] });
235
+ } else if (type === "otp") {
236
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { width }, children: [
237
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
238
+ "div",
138
239
  {
139
- type: showPassword ? "text" : "password",
140
- value,
141
- onChange,
142
- placeholder,
143
- style: { backgroundColor },
144
- className: `w-full border rounded-[10px] border-transparent placeholder:text-sm text-sm px-4 pr-12 h-[40px] text-white placeholder:opacity-30 focus:outline-none focus:border-[#6B2CE9] transition ${className}`
240
+ className: "relative flex items-center gap-4",
241
+ style: { width: "100%" },
242
+ children: [
243
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
244
+ "input",
245
+ {
246
+ type: "number",
247
+ value,
248
+ onChange,
249
+ placeholder,
250
+ style: {
251
+ backgroundColor,
252
+ color: textColor,
253
+ borderRadius,
254
+ height,
255
+ width: "100%"
256
+ },
257
+ className: `border ${error ? "border-red-500" : "border-transparent"} placeholder:text-sm text-sm px-4 pr-28 placeholder:opacity-30 focus:outline-none transition ${className}`,
258
+ onFocus: (e) => !error && (e.target.style.borderColor = color),
259
+ onBlur: (e) => e.target.style.borderColor = error ? "#ef4444" : "transparent"
260
+ }
261
+ ),
262
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
263
+ "button",
264
+ {
265
+ type: "button",
266
+ onClick: onOtpClick,
267
+ className: "text-[#A77BFF] cursor-pointer font-medium text-sm w-fit absolute right-4 top-1/2 -translate-y-1/2",
268
+ "aria-label": "Resend code",
269
+ children: "Resend code"
270
+ }
271
+ )
272
+ ]
145
273
  }
146
274
  ),
147
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
148
- "button",
149
- {
150
- type: "button",
151
- onClick: togglePasswordVisibility,
152
- className: "absolute right-4 top-1/2 -translate-y-1/2 text-gray-400 hover:text-white transition-colors",
153
- "aria-label": showPassword ? "Hide password" : "Show password",
154
- children: showPassword ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.Eye, { className: "cursor-pointer", size: 18 }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.EyeClosed, { className: "cursor-pointer", size: 18 })
155
- }
156
- )
275
+ error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-red-500 text-xs mt-1 px-1", children: error })
157
276
  ] });
158
- } else if (type === "otp") {
159
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "relative w-full flex items-center gap-4", children: [
277
+ } else {
278
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { width }, children: [
160
279
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
161
280
  "input",
162
281
  {
163
- type: "number",
282
+ type,
164
283
  value,
165
284
  onChange,
166
285
  placeholder,
167
- style: { backgroundColor },
168
- className: `w-full border rounded-[10px] border-transparent placeholder:text-sm text-sm px-4 pr-28 h-[40px] text-white placeholder:opacity-30 focus:outline-none focus:border-[#6B2CE9] transition ${className}`
286
+ style: {
287
+ backgroundColor,
288
+ color: textColor,
289
+ borderRadius,
290
+ height,
291
+ width: "100%"
292
+ },
293
+ className: `border ${error ? "border-red-500" : "border-transparent"} placeholder:text-sm text-sm px-4 placeholder:opacity-30 focus:outline-none transition ${className}`,
294
+ onFocus: (e) => !error && (e.target.style.borderColor = color),
295
+ onBlur: (e) => e.target.style.borderColor = error ? "#ef4444" : "transparent"
169
296
  }
170
297
  ),
171
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
172
- "button",
173
- {
174
- type: "button",
175
- onClick: onOtpClick,
176
- className: "text-[#A77BFF] cursor-pointer font-medium text-sm w-fit absolute right-4 top-1/2 -translate-y-1/2",
177
- "aria-label": showPassword ? "Hide password" : "Show password",
178
- children: "Resend code"
179
- }
180
- )
298
+ error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-red-500 text-xs mt-1 px-1", children: error })
181
299
  ] });
182
- } else {
183
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
184
- "input",
185
- {
186
- type,
187
- value,
188
- onChange,
189
- placeholder,
190
- style: { backgroundColor },
191
- className: `w-full border rounded-[10px] border-transparent placeholder:text-sm text-sm px-4 h-[40px] text-white placeholder:opacity-30 focus:outline-none focus:border-[#6B2CE9] transition ${className}`
192
- }
193
- );
194
300
  }
195
301
  };
196
- var TextInput_default = TextInput;
302
+ var Input_default = Input;
197
303
 
198
- // src/components/Skeleton.tsx
304
+ // src/components/Skeleton/Skeleton.tsx
199
305
  var import_jsx_runtime3 = require("react/jsx-runtime");
200
306
  var Skeleton = ({
201
307
  className = "",
@@ -241,7 +347,7 @@ var Skeleton = ({
241
347
  var Skeleton_default = Skeleton;
242
348
  // Annotate the CommonJS export names for ESM import in node:
243
349
  0 && (module.exports = {
350
+ Input,
244
351
  Modal,
245
- Skeleton,
246
- TextInput
352
+ Skeleton
247
353
  });
package/dist/index.mjs CHANGED
@@ -1,6 +1,7 @@
1
- // src/components/Modal.tsx
1
+ // src/components/Modal/Modal.tsx
2
2
  import { motion, AnimatePresence } from "framer-motion";
3
- import { jsx, jsxs } from "react/jsx-runtime";
3
+
4
+ // src/components/Modal/constant.ts
4
5
  var revealVariants = {
5
6
  fade: {
6
7
  initial: { opacity: 0 },
@@ -18,13 +19,16 @@ var revealVariants = {
18
19
  exit: { y: "100%", opacity: 0 }
19
20
  }
20
21
  };
22
+
23
+ // src/components/Modal/Modal.tsx
24
+ import { jsx, jsxs } from "react/jsx-runtime";
21
25
  var Modal = ({
22
- children,
23
- className,
24
- revealMode = "fade",
26
+ isDrag = false,
25
27
  showModal = true,
26
28
  onClose,
27
- isDrag = false
29
+ children,
30
+ className,
31
+ revealMode = "fade"
28
32
  }) => {
29
33
  const variants = revealVariants[revealMode];
30
34
  const handleOverlayClick = (e) => {
@@ -86,88 +90,190 @@ var Modal = ({
86
90
  };
87
91
  var Modal_default = Modal;
88
92
 
89
- // src/components/TextInput.tsx
90
- import { useState } from "react";
93
+ // src/components/Input/Input.tsx
94
+ import { useState, useEffect } from "react";
91
95
  import { Eye, EyeClosed } from "lucide-react";
92
96
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
93
- var TextInput = ({
97
+ var Input = ({
98
+ onChange,
99
+ onOtpClick,
100
+ validate = false,
94
101
  type,
95
102
  value,
96
- onChange,
97
- placeholder,
103
+ minLength,
104
+ maxLength,
98
105
  className,
99
- backgroundColor = "#1F1F23",
100
- onOtpClick
106
+ placeholder,
107
+ errorMessage,
108
+ width = "100%",
109
+ height = "40px",
110
+ color = "#6B2CE9",
111
+ textColor = "white",
112
+ borderRadius = "10px",
113
+ backgroundColor = "#1F1F23"
101
114
  }) => {
102
115
  const [showPassword, setShowPassword] = useState(false);
116
+ const [error, setError] = useState(null);
103
117
  const togglePasswordVisibility = () => {
104
118
  setShowPassword(!showPassword);
105
119
  };
120
+ const validateInput = (inputValue = "") => {
121
+ if (!validate) {
122
+ setError(null);
123
+ return true;
124
+ }
125
+ if (minLength && inputValue.length < minLength) {
126
+ setError(errorMessage || `Minimum ${minLength} characters required`);
127
+ return false;
128
+ }
129
+ if (maxLength && inputValue.length > maxLength) {
130
+ setError(errorMessage || `Maximum ${maxLength} characters allowed`);
131
+ return false;
132
+ }
133
+ switch (type) {
134
+ case "email":
135
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
136
+ if (inputValue && !emailRegex.test(inputValue)) {
137
+ setError(errorMessage || "Invalid email address");
138
+ return false;
139
+ }
140
+ break;
141
+ case "password":
142
+ if (!minLength && inputValue.length < 8) {
143
+ setError(errorMessage || "Password must be at least 8 characters");
144
+ return false;
145
+ }
146
+ break;
147
+ case "otp":
148
+ if (inputValue && !/^\d*$/.test(inputValue)) {
149
+ setError(errorMessage || "OTP must contain only numbers");
150
+ return false;
151
+ }
152
+ if (!minLength && !maxLength && inputValue.length !== 6) {
153
+ setError(errorMessage || "OTP must be 6 digits");
154
+ return false;
155
+ }
156
+ break;
157
+ case "number":
158
+ if (inputValue && isNaN(Number(inputValue))) {
159
+ setError(errorMessage || "Must be a valid number");
160
+ return false;
161
+ }
162
+ break;
163
+ }
164
+ setError(null);
165
+ return true;
166
+ };
167
+ useEffect(() => {
168
+ if (validate && value !== void 0) {
169
+ validateInput(value);
170
+ }
171
+ }, [value, validate, minLength, maxLength, type]);
106
172
  if (type === "password") {
107
- return /* @__PURE__ */ jsxs2("div", { className: "relative w-full", children: [
108
- /* @__PURE__ */ jsx2(
109
- "input",
173
+ return /* @__PURE__ */ jsxs2("div", { style: { width }, children: [
174
+ /* @__PURE__ */ jsxs2("div", { className: "relative", style: { width: "100%" }, children: [
175
+ /* @__PURE__ */ jsx2(
176
+ "input",
177
+ {
178
+ type: showPassword ? "text" : "password",
179
+ value,
180
+ onChange,
181
+ placeholder,
182
+ style: {
183
+ backgroundColor,
184
+ color: textColor,
185
+ borderRadius,
186
+ height,
187
+ width: "100%"
188
+ },
189
+ className: `border ${error ? "border-red-500" : "border-transparent"} placeholder:text-sm text-sm px-4 pr-12 placeholder:opacity-30 focus:outline-none transition ${className}`,
190
+ onFocus: (e) => !error && (e.target.style.borderColor = color),
191
+ onBlur: (e) => e.target.style.borderColor = error ? "#ef4444" : "transparent"
192
+ }
193
+ ),
194
+ /* @__PURE__ */ jsx2(
195
+ "button",
196
+ {
197
+ type: "button",
198
+ onClick: togglePasswordVisibility,
199
+ className: "absolute right-4 top-1/2 -translate-y-1/2 text-gray-400 hover:text-white transition-colors",
200
+ "aria-label": showPassword ? "Hide password" : "Show password",
201
+ children: showPassword ? /* @__PURE__ */ jsx2(Eye, { className: "cursor-pointer", size: 18 }) : /* @__PURE__ */ jsx2(EyeClosed, { className: "cursor-pointer", size: 18 })
202
+ }
203
+ )
204
+ ] }),
205
+ error && /* @__PURE__ */ jsx2("p", { className: "text-red-500 text-xs mt-1 px-1", children: error })
206
+ ] });
207
+ } else if (type === "otp") {
208
+ return /* @__PURE__ */ jsxs2("div", { style: { width }, children: [
209
+ /* @__PURE__ */ jsxs2(
210
+ "div",
110
211
  {
111
- type: showPassword ? "text" : "password",
112
- value,
113
- onChange,
114
- placeholder,
115
- style: { backgroundColor },
116
- className: `w-full border rounded-[10px] border-transparent placeholder:text-sm text-sm px-4 pr-12 h-[40px] text-white placeholder:opacity-30 focus:outline-none focus:border-[#6B2CE9] transition ${className}`
212
+ className: "relative flex items-center gap-4",
213
+ style: { width: "100%" },
214
+ children: [
215
+ /* @__PURE__ */ jsx2(
216
+ "input",
217
+ {
218
+ type: "number",
219
+ value,
220
+ onChange,
221
+ placeholder,
222
+ style: {
223
+ backgroundColor,
224
+ color: textColor,
225
+ borderRadius,
226
+ height,
227
+ width: "100%"
228
+ },
229
+ className: `border ${error ? "border-red-500" : "border-transparent"} placeholder:text-sm text-sm px-4 pr-28 placeholder:opacity-30 focus:outline-none transition ${className}`,
230
+ onFocus: (e) => !error && (e.target.style.borderColor = color),
231
+ onBlur: (e) => e.target.style.borderColor = error ? "#ef4444" : "transparent"
232
+ }
233
+ ),
234
+ /* @__PURE__ */ jsx2(
235
+ "button",
236
+ {
237
+ type: "button",
238
+ onClick: onOtpClick,
239
+ className: "text-[#A77BFF] cursor-pointer font-medium text-sm w-fit absolute right-4 top-1/2 -translate-y-1/2",
240
+ "aria-label": "Resend code",
241
+ children: "Resend code"
242
+ }
243
+ )
244
+ ]
117
245
  }
118
246
  ),
119
- /* @__PURE__ */ jsx2(
120
- "button",
121
- {
122
- type: "button",
123
- onClick: togglePasswordVisibility,
124
- className: "absolute right-4 top-1/2 -translate-y-1/2 text-gray-400 hover:text-white transition-colors",
125
- "aria-label": showPassword ? "Hide password" : "Show password",
126
- children: showPassword ? /* @__PURE__ */ jsx2(Eye, { className: "cursor-pointer", size: 18 }) : /* @__PURE__ */ jsx2(EyeClosed, { className: "cursor-pointer", size: 18 })
127
- }
128
- )
247
+ error && /* @__PURE__ */ jsx2("p", { className: "text-red-500 text-xs mt-1 px-1", children: error })
129
248
  ] });
130
- } else if (type === "otp") {
131
- return /* @__PURE__ */ jsxs2("div", { className: "relative w-full flex items-center gap-4", children: [
249
+ } else {
250
+ return /* @__PURE__ */ jsxs2("div", { style: { width }, children: [
132
251
  /* @__PURE__ */ jsx2(
133
252
  "input",
134
253
  {
135
- type: "number",
254
+ type,
136
255
  value,
137
256
  onChange,
138
257
  placeholder,
139
- style: { backgroundColor },
140
- className: `w-full border rounded-[10px] border-transparent placeholder:text-sm text-sm px-4 pr-28 h-[40px] text-white placeholder:opacity-30 focus:outline-none focus:border-[#6B2CE9] transition ${className}`
258
+ style: {
259
+ backgroundColor,
260
+ color: textColor,
261
+ borderRadius,
262
+ height,
263
+ width: "100%"
264
+ },
265
+ className: `border ${error ? "border-red-500" : "border-transparent"} placeholder:text-sm text-sm px-4 placeholder:opacity-30 focus:outline-none transition ${className}`,
266
+ onFocus: (e) => !error && (e.target.style.borderColor = color),
267
+ onBlur: (e) => e.target.style.borderColor = error ? "#ef4444" : "transparent"
141
268
  }
142
269
  ),
143
- /* @__PURE__ */ jsx2(
144
- "button",
145
- {
146
- type: "button",
147
- onClick: onOtpClick,
148
- className: "text-[#A77BFF] cursor-pointer font-medium text-sm w-fit absolute right-4 top-1/2 -translate-y-1/2",
149
- "aria-label": showPassword ? "Hide password" : "Show password",
150
- children: "Resend code"
151
- }
152
- )
270
+ error && /* @__PURE__ */ jsx2("p", { className: "text-red-500 text-xs mt-1 px-1", children: error })
153
271
  ] });
154
- } else {
155
- return /* @__PURE__ */ jsx2(
156
- "input",
157
- {
158
- type,
159
- value,
160
- onChange,
161
- placeholder,
162
- style: { backgroundColor },
163
- className: `w-full border rounded-[10px] border-transparent placeholder:text-sm text-sm px-4 h-[40px] text-white placeholder:opacity-30 focus:outline-none focus:border-[#6B2CE9] transition ${className}`
164
- }
165
- );
166
272
  }
167
273
  };
168
- var TextInput_default = TextInput;
274
+ var Input_default = Input;
169
275
 
170
- // src/components/Skeleton.tsx
276
+ // src/components/Skeleton/Skeleton.tsx
171
277
  import { jsx as jsx3 } from "react/jsx-runtime";
172
278
  var Skeleton = ({
173
279
  className = "",
@@ -212,7 +318,7 @@ var Skeleton = ({
212
318
  };
213
319
  var Skeleton_default = Skeleton;
214
320
  export {
321
+ Input_default as Input,
215
322
  Modal_default as Modal,
216
- Skeleton_default as Skeleton,
217
- TextInput_default as TextInput
323
+ Skeleton_default as Skeleton
218
324
  };
package/dist/styles.css CHANGED
@@ -7,11 +7,14 @@
7
7
  "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
8
8
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
9
9
  "Liberation Mono", "Courier New", monospace;
10
+ --color-red-500: oklch(0.637 0.237 25.331);
10
11
  --color-gray-400: oklch(0.707 0.022 261.325);
11
12
  --color-black: #000;
12
13
  --color-white: #fff;
13
14
  --spacing: 0.25rem;
14
15
  --container-md: 28rem;
16
+ --text-xs: 0.75rem;
17
+ --text-xs--line-height: calc(1 / 0.75);
15
18
  --text-sm: 0.875rem;
16
19
  --text-sm--line-height: calc(1.25 / 0.875);
17
20
  --text-2xl: 1.5rem;
@@ -213,6 +216,9 @@
213
216
  .z-50 {
214
217
  z-index: 50;
215
218
  }
219
+ .mt-1 {
220
+ margin-top: calc(var(--spacing) * 1);
221
+ }
216
222
  .mb-4 {
217
223
  margin-bottom: calc(var(--spacing) * 4);
218
224
  }
@@ -222,9 +228,6 @@
222
228
  .h-4 {
223
229
  height: calc(var(--spacing) * 4);
224
230
  }
225
- .h-\[40px\] {
226
- height: 40px;
227
- }
228
231
  .w-fit {
229
232
  width: -moz-fit-content;
230
233
  width: fit-content;
@@ -260,9 +263,6 @@
260
263
  .rounded-\[4px\] {
261
264
  border-radius: 4px;
262
265
  }
263
- .rounded-\[10px\] {
264
- border-radius: 10px;
265
- }
266
266
  .rounded-\[12px\] {
267
267
  border-radius: 12px;
268
268
  }
@@ -276,6 +276,9 @@
276
276
  border-style: var(--tw-border-style);
277
277
  border-width: 1px;
278
278
  }
279
+ .border-red-500 {
280
+ border-color: var(--color-red-500);
281
+ }
279
282
  .border-transparent {
280
283
  border-color: transparent;
281
284
  }
@@ -311,9 +314,15 @@
311
314
  .bg-\[length\:200\%_100\%\] {
312
315
  background-size: 200% 100%;
313
316
  }
317
+ .p-6 {
318
+ padding: calc(var(--spacing) * 6);
319
+ }
314
320
  .p-8 {
315
321
  padding: calc(var(--spacing) * 8);
316
322
  }
323
+ .px-1 {
324
+ padding-inline: calc(var(--spacing) * 1);
325
+ }
317
326
  .px-4 {
318
327
  padding-inline: calc(var(--spacing) * 4);
319
328
  }
@@ -334,6 +343,10 @@
334
343
  font-size: var(--text-sm);
335
344
  line-height: var(--tw-leading, var(--text-sm--line-height));
336
345
  }
346
+ .text-xs {
347
+ font-size: var(--text-xs);
348
+ line-height: var(--tw-leading, var(--text-xs--line-height));
349
+ }
337
350
  .font-bold {
338
351
  --tw-font-weight: var(--font-weight-bold);
339
352
  font-weight: var(--font-weight-bold);
@@ -348,8 +361,8 @@
348
361
  .text-gray-400 {
349
362
  color: var(--color-gray-400);
350
363
  }
351
- .text-white {
352
- color: var(--color-white);
364
+ .text-red-500 {
365
+ color: var(--color-red-500);
353
366
  }
354
367
  .transition {
355
368
  transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, backdrop-filter, display, content-visibility, overlay, pointer-events;
@@ -386,11 +399,6 @@
386
399
  }
387
400
  }
388
401
  }
389
- .focus\:border-\[\#6B2CE9\] {
390
- &:focus {
391
- border-color: #6B2CE9;
392
- }
393
- }
394
402
  .focus\:outline-none {
395
403
  &:focus {
396
404
  --tw-outline-style: none;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tunjiadeyemi/ui",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
4
4
  "description": "A collection of reusable UI components",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",