irismail 0.1.0 → 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.
- package/README.md +214 -106
- package/dist/chunk-64MVNASE.mjs +42 -0
- package/dist/chunk-GFYL2QKK.mjs +208 -0
- package/dist/index.d.mts +3 -10
- package/dist/index.d.ts +3 -10
- package/dist/index.js +187 -345
- package/dist/index.mjs +8 -18
- package/dist/react/index.d.mts +129 -51
- package/dist/react/index.d.ts +129 -51
- package/dist/react/index.js +160 -174
- package/dist/react/index.mjs +3 -3
- package/dist/server/index.d.mts +38 -98
- package/dist/server/index.d.ts +38 -98
- package/dist/server/index.js +25 -167
- package/dist/server/index.mjs +3 -11
- package/package.json +15 -6
- package/dist/chunk-FTNSOYOW.mjs +0 -223
- package/dist/chunk-XGASTZZ6.mjs +0 -180
package/README.md
CHANGED
|
@@ -1,157 +1,265 @@
|
|
|
1
1
|
# IrisMail
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/irismail)
|
|
4
|
+
[](https://www.npmjs.com/package/irismail)
|
|
5
|
+
[](https://opensource.org/licenses/ISC)
|
|
6
|
+
[](https://www.typescriptlang.org/)
|
|
7
|
+
|
|
8
|
+
A lightweight npm package for sending emails via Gmail and beautiful OTP input components for React.
|
|
4
9
|
|
|
5
10
|
## Features
|
|
6
11
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
+
- **Email Service**: Simple Gmail-based email sending with minimal configuration
|
|
13
|
+
- **OTP Input**: Beautiful, accessible OTP input components with copy-paste support
|
|
14
|
+
- **Themeable**: Dark and light themes built-in
|
|
15
|
+
- **Flexible**: Multiple sizes (sm/md/lg) and customizable styles via classNames API
|
|
16
|
+
- **Composable**: Build custom layouts with InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator
|
|
17
|
+
- **Easy to Use**: Minimal API surface - just a few props to get started
|
|
18
|
+
- **Type-Safe**: Built with TypeScript
|
|
19
|
+
- **Zero Config**: Just add your Gmail credentials - no SMTP configuration needed
|
|
12
20
|
|
|
13
21
|
## Installation
|
|
14
22
|
|
|
23
|
+
IrisMail works with all major package managers. No additional configuration is required.
|
|
24
|
+
|
|
15
25
|
```bash
|
|
16
26
|
npm install irismail
|
|
17
|
-
# or
|
|
18
|
-
yarn add irismail
|
|
19
|
-
# or
|
|
20
27
|
pnpm add irismail
|
|
28
|
+
yarn add irismail
|
|
29
|
+
bun add irismail
|
|
21
30
|
```
|
|
22
31
|
|
|
23
|
-
##
|
|
32
|
+
## Package Exports
|
|
24
33
|
|
|
25
|
-
|
|
34
|
+
| Import | Description |
|
|
35
|
+
|--------|-------------|
|
|
36
|
+
| `irismail/server` | Email service (IrisMail class and types) |
|
|
37
|
+
| `irismail/react` | OTP components (OTP, InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator) |
|
|
38
|
+
| `irismail` | All exports (server + react + utils) |
|
|
26
39
|
|
|
27
|
-
|
|
40
|
+
## Email Service
|
|
41
|
+
|
|
42
|
+
Send emails with just your Gmail credentials:
|
|
28
43
|
|
|
29
44
|
```typescript
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
transport: {
|
|
37
|
-
host: 'smtp.gmail.com',
|
|
38
|
-
port: 587,
|
|
39
|
-
auth: {
|
|
40
|
-
user: process.env.GMAIL_USER,
|
|
41
|
-
pass: process.env.GMAIL_PASS,
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
defaults: {
|
|
45
|
-
from: {
|
|
46
|
-
name: 'My App',
|
|
47
|
-
address: process.env.GMAIL_USER,
|
|
48
|
-
},
|
|
49
|
-
},
|
|
45
|
+
import { IrisMail } from 'irismail/server';
|
|
46
|
+
|
|
47
|
+
const mail = new IrisMail({
|
|
48
|
+
auth: {
|
|
49
|
+
user: process.env.GMAIL_USER!,
|
|
50
|
+
pass: process.env.GMAIL_APP_PASSWORD!,
|
|
50
51
|
},
|
|
51
|
-
secretKey: process.env.OTP_SECRET_KEY, // Must be 32 chars
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
// Send email
|
|
62
|
-
await irismail.email.sendEmail({
|
|
63
|
-
to: email,
|
|
64
|
-
subject: 'Your Verification Code',
|
|
65
|
-
html: `<p>Your code is: <b>${otp}</b></p>`,
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
return NextResponse.json({ encryptedOtp });
|
|
69
|
-
}
|
|
54
|
+
await mail.sendMail({
|
|
55
|
+
from: process.env.GMAIL_USER!,
|
|
56
|
+
to: 'user@example.com',
|
|
57
|
+
subject: 'Hello!',
|
|
58
|
+
html: '<h1>Welcome</h1><p>Thanks for signing up!</p>',
|
|
59
|
+
});
|
|
70
60
|
```
|
|
71
61
|
|
|
72
|
-
|
|
62
|
+
> **Note:** You'll need to generate a [Gmail App Password](https://support.google.com/accounts/answer/185833) for authentication.
|
|
73
63
|
|
|
74
|
-
|
|
64
|
+
## OTP Input Component
|
|
65
|
+
|
|
66
|
+
### Basic Usage
|
|
75
67
|
|
|
76
68
|
```tsx
|
|
77
|
-
// components/verify-form.tsx
|
|
78
69
|
'use client';
|
|
79
70
|
|
|
80
71
|
import { useState } from 'react';
|
|
81
|
-
import {
|
|
72
|
+
import { OTP } from 'irismail/react';
|
|
82
73
|
|
|
83
|
-
export function VerifyForm(
|
|
84
|
-
const [
|
|
85
|
-
const { otpTimeLeft, resendTimeLeft, formatTime } = useOTP({ email });
|
|
74
|
+
export function VerifyForm() {
|
|
75
|
+
const [code, setCode] = useState('');
|
|
86
76
|
|
|
87
77
|
return (
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
>
|
|
94
|
-
<InputOTPGroup>
|
|
95
|
-
<InputOTPSlot index={0} />
|
|
96
|
-
<InputOTPSlot index={1} />
|
|
97
|
-
<InputOTPSlot index={2} />
|
|
98
|
-
<InputOTPSlot index={3} />
|
|
99
|
-
<InputOTPSlot index={4} />
|
|
100
|
-
<InputOTPSlot index={5} />
|
|
101
|
-
</InputOTPGroup>
|
|
102
|
-
</InputOTP>
|
|
103
|
-
|
|
104
|
-
<div className="text-sm">
|
|
105
|
-
Time remaining: {formatTime(otpTimeLeft)}
|
|
106
|
-
</div>
|
|
107
|
-
</div>
|
|
78
|
+
<OTP
|
|
79
|
+
value={code}
|
|
80
|
+
onChange={setCode}
|
|
81
|
+
onComplete={(value) => verify(value)}
|
|
82
|
+
/>
|
|
108
83
|
);
|
|
109
84
|
}
|
|
110
85
|
```
|
|
111
86
|
|
|
112
|
-
|
|
87
|
+
### With Error State
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
<OTP
|
|
91
|
+
value={code}
|
|
92
|
+
onChange={setCode}
|
|
93
|
+
error={isInvalid}
|
|
94
|
+
/>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Custom Length
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
<OTP length={4} value={code} onChange={setCode} />
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Themes
|
|
104
|
+
|
|
105
|
+
Use the `theme` prop to switch between dark and light color schemes:
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
// Dark theme (default)
|
|
109
|
+
<OTP theme="dark" value={code} onChange={setCode} />
|
|
110
|
+
|
|
111
|
+
// Light theme - for light backgrounds
|
|
112
|
+
<OTP theme="light" value={code} onChange={setCode} />
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Sizes
|
|
116
|
+
|
|
117
|
+
Use the `slotSize` prop to change the slot dimensions:
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
<OTP slotSize="sm" value={code} onChange={setCode} />
|
|
121
|
+
<OTP slotSize="md" value={code} onChange={setCode} /> // default
|
|
122
|
+
<OTP slotSize="lg" value={code} onChange={setCode} />
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Separator & Grouping
|
|
126
|
+
|
|
127
|
+
Add visual separators and control grouping:
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
// With separator (auto groups: 3-3 for 6 digits)
|
|
131
|
+
<OTP separator value={code} onChange={setCode} />
|
|
132
|
+
|
|
133
|
+
// Custom group size (2-2-2 for 6 digits)
|
|
134
|
+
<OTP separator groupSize={2} value={code} onChange={setCode} />
|
|
135
|
+
|
|
136
|
+
// Stripe-style (3-3 for 6 digits)
|
|
137
|
+
<OTP length={6} separator groupSize={3} value={code} onChange={setCode} />
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Custom Styling (classNames API)
|
|
141
|
+
|
|
142
|
+
Use the `classNames` prop to customize individual parts:
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
<OTP
|
|
146
|
+
value={code}
|
|
147
|
+
onChange={setCode}
|
|
148
|
+
separator
|
|
149
|
+
groupSize={3}
|
|
150
|
+
classNames={{
|
|
151
|
+
slot: "rounded-none first:rounded-l-md last:rounded-r-md",
|
|
152
|
+
slotActive: "ring-2 ring-indigo-500 ring-offset-1 ring-offset-zinc-900",
|
|
153
|
+
separator: "mx-4",
|
|
154
|
+
separatorLine: "bg-indigo-500 w-4 h-0.5",
|
|
155
|
+
caret: "bg-indigo-400",
|
|
156
|
+
}}
|
|
157
|
+
/>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Composition Pattern
|
|
161
|
+
|
|
162
|
+
For custom layouts, use the composition components:
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
import {
|
|
166
|
+
InputOTP,
|
|
167
|
+
InputOTPGroup,
|
|
168
|
+
InputOTPSlot,
|
|
169
|
+
InputOTPSeparator,
|
|
170
|
+
} from 'irismail/react';
|
|
171
|
+
|
|
172
|
+
<InputOTP maxLength={6} value={code} onChange={setCode}>
|
|
173
|
+
<InputOTPGroup>
|
|
174
|
+
<InputOTPSlot index={0} />
|
|
175
|
+
<InputOTPSlot index={1} />
|
|
176
|
+
<InputOTPSlot index={2} />
|
|
177
|
+
</InputOTPGroup>
|
|
178
|
+
<InputOTPSeparator />
|
|
179
|
+
<InputOTPGroup>
|
|
180
|
+
<InputOTPSlot index={3} />
|
|
181
|
+
<InputOTPSlot index={4} />
|
|
182
|
+
<InputOTPSlot index={5} />
|
|
183
|
+
</InputOTPGroup>
|
|
184
|
+
</InputOTP>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
InputOTP and composition components support the same styling props: `theme`, `slotSize`, `error`, and `classNames`.
|
|
188
|
+
|
|
189
|
+
## OTP Props
|
|
190
|
+
|
|
191
|
+
| Prop | Type | Default | Description |
|
|
192
|
+
|------|------|---------|-------------|
|
|
193
|
+
| `length` | number | 6 | Number of OTP digits |
|
|
194
|
+
| `value` | string | — | Controlled input value |
|
|
195
|
+
| `onChange` | (value: string) => void | — | Called when value changes |
|
|
196
|
+
| `onComplete` | (value: string) => void | — | Called when all digits entered |
|
|
197
|
+
| `theme` | "dark" \| "light" | "dark" | Color theme |
|
|
198
|
+
| `slotSize` | "sm" \| "md" \| "lg" | "md" | Size of the input slots |
|
|
199
|
+
| `separator` | boolean | false | Show separator between groups |
|
|
200
|
+
| `groupSize` | number | length / 2 | Number of slots per group when separator is enabled |
|
|
201
|
+
| `disabled` | boolean | false | Disable the input |
|
|
202
|
+
| `error` | boolean | false | Show error styling |
|
|
203
|
+
| `autoFocus` | boolean | false | Auto focus first slot |
|
|
204
|
+
| `name` | string | — | Name attribute for forms |
|
|
205
|
+
| `className` | string | — | Container className |
|
|
206
|
+
| `classNames` | OTPClassNames | — | Styles API for fine-grained customization |
|
|
207
|
+
| `pattern` | string | "^[0-9]*$" | Regex pattern to match input against |
|
|
208
|
+
|
|
209
|
+
## ClassNames API
|
|
210
|
+
|
|
211
|
+
The `classNames` prop accepts an object with these keys for custom styling:
|
|
212
|
+
|
|
213
|
+
| Key | Description |
|
|
214
|
+
|-----|-------------|
|
|
215
|
+
| `root` | Root container element |
|
|
216
|
+
| `group` | Group wrapper containing slots |
|
|
217
|
+
| `slot` | Individual digit slot (base state) |
|
|
218
|
+
| `slotFilled` | Slot when it contains a digit |
|
|
219
|
+
| `slotActive` | Slot when focused/active |
|
|
220
|
+
| `slotError` | Slot in error state |
|
|
221
|
+
| `separator` | Separator container |
|
|
222
|
+
| `separatorLine` | The separator dash/line element |
|
|
223
|
+
| `caret` | Blinking caret cursor |
|
|
224
|
+
|
|
225
|
+
Works with both the `OTP` component and the composition components (`InputOTP`, etc.). OTP components work best with Tailwind CSS; use these class names to override or extend the default styles.
|
|
226
|
+
|
|
227
|
+
## Documentation
|
|
228
|
+
|
|
229
|
+
For full documentation and interactive examples, run the docs site:
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
npm run site
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Contributing
|
|
113
236
|
|
|
114
|
-
|
|
237
|
+
Contributions are welcome.
|
|
115
238
|
|
|
116
|
-
1. **
|
|
239
|
+
1. **Clone and install**
|
|
117
240
|
```bash
|
|
118
|
-
|
|
241
|
+
git clone https://github.com/nitin-1926/irismail.git
|
|
242
|
+
cd irismail
|
|
243
|
+
npm install
|
|
119
244
|
```
|
|
120
245
|
|
|
121
|
-
2. **
|
|
246
|
+
2. **Build the package**
|
|
122
247
|
```bash
|
|
123
|
-
|
|
124
|
-
# Edit .env.local with your Gmail credentials and secret key
|
|
248
|
+
npm run build
|
|
125
249
|
```
|
|
126
250
|
|
|
127
|
-
3. **
|
|
251
|
+
3. **Develop with watch mode**
|
|
128
252
|
```bash
|
|
129
253
|
npm run dev
|
|
130
254
|
```
|
|
131
255
|
|
|
132
|
-
4. **
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
See `example/README.md` for more details.
|
|
138
|
-
|
|
139
|
-
## Publishing to NPM
|
|
140
|
-
|
|
141
|
-
1. **Login to NPM**:
|
|
142
|
-
```bash
|
|
143
|
-
npm login
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
2. **Build the package**:
|
|
147
|
-
```bash
|
|
148
|
-
npm run build
|
|
149
|
-
```
|
|
256
|
+
4. **Run the documentation site**
|
|
257
|
+
```bash
|
|
258
|
+
npm run site
|
|
259
|
+
```
|
|
260
|
+
Then open [http://localhost:3000](http://localhost:3000).
|
|
150
261
|
|
|
151
|
-
|
|
152
|
-
```bash
|
|
153
|
-
npm publish --access public
|
|
154
|
-
```
|
|
262
|
+
5. **Submit changes**: Open a pull request with a clear description of the change. Ensure the build passes (`npm run build`) and the package works with the docs site.
|
|
155
263
|
|
|
156
264
|
## License
|
|
157
265
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__publicField
|
|
3
|
+
} from "./chunk-QZ7TP4HQ.mjs";
|
|
4
|
+
|
|
5
|
+
// src/server/email.ts
|
|
6
|
+
import nodemailer from "nodemailer";
|
|
7
|
+
var IrisMail = class {
|
|
8
|
+
constructor(config) {
|
|
9
|
+
__publicField(this, "transporter");
|
|
10
|
+
this.transporter = nodemailer.createTransport({
|
|
11
|
+
host: "smtp.gmail.com",
|
|
12
|
+
port: 587,
|
|
13
|
+
secure: false,
|
|
14
|
+
// true for 465, false for other ports
|
|
15
|
+
auth: config.auth
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Send an email
|
|
20
|
+
* @param options - Email options (from, to, subject, html)
|
|
21
|
+
* @returns Promise with success status and messageId
|
|
22
|
+
*/
|
|
23
|
+
async sendMail(options) {
|
|
24
|
+
const { from, to, subject, html } = options;
|
|
25
|
+
const text = html.replace(/<[^>]*>/g, "");
|
|
26
|
+
const info = await this.transporter.sendMail({
|
|
27
|
+
from,
|
|
28
|
+
to,
|
|
29
|
+
subject,
|
|
30
|
+
html,
|
|
31
|
+
text
|
|
32
|
+
});
|
|
33
|
+
return {
|
|
34
|
+
success: true,
|
|
35
|
+
messageId: info.messageId
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export {
|
|
41
|
+
IrisMail
|
|
42
|
+
};
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
// src/react/components/input-otp.tsx
|
|
2
|
+
import { OTPInput, OTPInputContext } from "input-otp";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
// src/utils/constants.ts
|
|
6
|
+
import { clsx } from "clsx";
|
|
7
|
+
import { twMerge } from "tailwind-merge";
|
|
8
|
+
function cn(...inputs) {
|
|
9
|
+
return twMerge(clsx(inputs));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// src/react/components/input-otp.tsx
|
|
13
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
14
|
+
var slotSizeClasses = {
|
|
15
|
+
sm: "h-9 w-7 text-sm",
|
|
16
|
+
md: "h-11 w-9 text-base",
|
|
17
|
+
lg: "h-14 w-11 text-lg"
|
|
18
|
+
};
|
|
19
|
+
var caretSizeClasses = {
|
|
20
|
+
sm: "h-3 w-0.5",
|
|
21
|
+
md: "h-4 w-0.5",
|
|
22
|
+
lg: "h-5 w-0.5"
|
|
23
|
+
};
|
|
24
|
+
var themeClasses = {
|
|
25
|
+
dark: {
|
|
26
|
+
slot: {
|
|
27
|
+
base: "border-zinc-700 bg-zinc-800/50 text-white",
|
|
28
|
+
filled: "border-zinc-600 bg-zinc-800",
|
|
29
|
+
active: "border-zinc-500 bg-zinc-800",
|
|
30
|
+
error: "border-red-500/50 bg-red-500/5",
|
|
31
|
+
errorActive: "border-red-500/70"
|
|
32
|
+
},
|
|
33
|
+
caret: "bg-zinc-400",
|
|
34
|
+
separator: "bg-zinc-600"
|
|
35
|
+
},
|
|
36
|
+
light: {
|
|
37
|
+
slot: {
|
|
38
|
+
base: "border-zinc-300 bg-white text-zinc-900",
|
|
39
|
+
filled: "border-zinc-400 bg-zinc-50",
|
|
40
|
+
active: "border-zinc-500 bg-white",
|
|
41
|
+
error: "border-red-500/50 bg-red-50",
|
|
42
|
+
errorActive: "border-red-500/70"
|
|
43
|
+
},
|
|
44
|
+
caret: "bg-zinc-600",
|
|
45
|
+
separator: "bg-zinc-300"
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
var InputOTPStyleContext = React.createContext({
|
|
49
|
+
theme: "dark",
|
|
50
|
+
slotSize: "md"
|
|
51
|
+
});
|
|
52
|
+
var InputOTP = React.forwardRef(({ className, containerClassName, error, theme, slotSize, classNames, ...props }, ref) => {
|
|
53
|
+
const resolvedTheme = theme ?? "dark";
|
|
54
|
+
const resolvedSlotSize = slotSize ?? "md";
|
|
55
|
+
return /* @__PURE__ */ jsx(InputOTPStyleContext.Provider, { value: { error, theme: resolvedTheme, slotSize: resolvedSlotSize, classNames }, children: /* @__PURE__ */ jsx(
|
|
56
|
+
OTPInput,
|
|
57
|
+
{
|
|
58
|
+
ref,
|
|
59
|
+
containerClassName: cn(
|
|
60
|
+
"flex items-center gap-2 has-[:disabled]:opacity-50",
|
|
61
|
+
containerClassName,
|
|
62
|
+
classNames?.root
|
|
63
|
+
),
|
|
64
|
+
className: cn("disabled:cursor-not-allowed", className),
|
|
65
|
+
...props
|
|
66
|
+
}
|
|
67
|
+
) });
|
|
68
|
+
});
|
|
69
|
+
InputOTP.displayName = "InputOTP";
|
|
70
|
+
var InputOTPGroup = React.forwardRef(({ className, ...props }, ref) => {
|
|
71
|
+
const { classNames } = React.useContext(InputOTPStyleContext);
|
|
72
|
+
return /* @__PURE__ */ jsx(
|
|
73
|
+
"div",
|
|
74
|
+
{
|
|
75
|
+
ref,
|
|
76
|
+
className: cn("flex items-center gap-2", className, classNames?.group),
|
|
77
|
+
...props
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
InputOTPGroup.displayName = "InputOTPGroup";
|
|
82
|
+
var InputOTPSlot = React.forwardRef(({ index, className, ...props }, ref) => {
|
|
83
|
+
const inputOTPContext = React.useContext(OTPInputContext);
|
|
84
|
+
const { error, theme, slotSize, classNames } = React.useContext(InputOTPStyleContext);
|
|
85
|
+
const slot = inputOTPContext.slots[index];
|
|
86
|
+
const { char, hasFakeCaret, isActive } = slot;
|
|
87
|
+
const colors = themeClasses[theme];
|
|
88
|
+
return /* @__PURE__ */ jsxs(
|
|
89
|
+
"div",
|
|
90
|
+
{
|
|
91
|
+
ref,
|
|
92
|
+
className: cn(
|
|
93
|
+
// Base styles
|
|
94
|
+
"relative flex items-center justify-center",
|
|
95
|
+
"border rounded-md",
|
|
96
|
+
"font-mono font-medium",
|
|
97
|
+
"transition-colors duration-150",
|
|
98
|
+
// Size
|
|
99
|
+
slotSizeClasses[slotSize],
|
|
100
|
+
// Default state (theme-aware)
|
|
101
|
+
colors.slot.base,
|
|
102
|
+
// Custom base slot class
|
|
103
|
+
classNames?.slot,
|
|
104
|
+
// Filled state
|
|
105
|
+
char && colors.slot.filled,
|
|
106
|
+
char && classNames?.slotFilled,
|
|
107
|
+
// Active/focus state
|
|
108
|
+
isActive && colors.slot.active,
|
|
109
|
+
isActive && classNames?.slotActive,
|
|
110
|
+
// Error state
|
|
111
|
+
error && colors.slot.error,
|
|
112
|
+
error && isActive && colors.slot.errorActive,
|
|
113
|
+
error && classNames?.slotError,
|
|
114
|
+
className
|
|
115
|
+
),
|
|
116
|
+
"data-active": isActive,
|
|
117
|
+
"data-filled": Boolean(char),
|
|
118
|
+
"data-error": error,
|
|
119
|
+
...props,
|
|
120
|
+
children: [
|
|
121
|
+
char,
|
|
122
|
+
hasFakeCaret && /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsx("div", { className: cn(
|
|
123
|
+
"animate-caret-blink rounded-full",
|
|
124
|
+
caretSizeClasses[slotSize],
|
|
125
|
+
colors.caret,
|
|
126
|
+
classNames?.caret
|
|
127
|
+
) }) })
|
|
128
|
+
]
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
InputOTPSlot.displayName = "InputOTPSlot";
|
|
133
|
+
var InputOTPSeparator = React.forwardRef(({ className, ...props }, ref) => {
|
|
134
|
+
const { theme, classNames } = React.useContext(InputOTPStyleContext);
|
|
135
|
+
const colors = themeClasses[theme];
|
|
136
|
+
return /* @__PURE__ */ jsx(
|
|
137
|
+
"div",
|
|
138
|
+
{
|
|
139
|
+
ref,
|
|
140
|
+
role: "separator",
|
|
141
|
+
className: cn("flex items-center justify-center", className, classNames?.separator),
|
|
142
|
+
...props,
|
|
143
|
+
children: /* @__PURE__ */ jsx("span", { className: cn("w-3 h-0.5 rounded-full", colors.separator, classNames?.separatorLine) })
|
|
144
|
+
}
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
InputOTPSeparator.displayName = "InputOTPSeparator";
|
|
148
|
+
var OTP = React.forwardRef(
|
|
149
|
+
({
|
|
150
|
+
length = 6,
|
|
151
|
+
value,
|
|
152
|
+
onChange,
|
|
153
|
+
onComplete,
|
|
154
|
+
disabled,
|
|
155
|
+
error,
|
|
156
|
+
autoFocus,
|
|
157
|
+
name,
|
|
158
|
+
className,
|
|
159
|
+
pattern = "^[0-9]*$",
|
|
160
|
+
theme,
|
|
161
|
+
slotSize,
|
|
162
|
+
separator = false,
|
|
163
|
+
groupSize,
|
|
164
|
+
classNames
|
|
165
|
+
}, ref) => {
|
|
166
|
+
const resolvedTheme = theme ?? "dark";
|
|
167
|
+
const resolvedSlotSize = slotSize ?? "md";
|
|
168
|
+
const slotIndices = Array.from({ length }, (_, i) => i);
|
|
169
|
+
const effectiveGroupSize = groupSize ?? (separator ? Math.ceil(length / 2) : length);
|
|
170
|
+
const groups = [];
|
|
171
|
+
for (let i = 0; i < length; i += effectiveGroupSize) {
|
|
172
|
+
groups.push(slotIndices.slice(i, i + effectiveGroupSize));
|
|
173
|
+
}
|
|
174
|
+
return /* @__PURE__ */ jsx(
|
|
175
|
+
InputOTP,
|
|
176
|
+
{
|
|
177
|
+
ref,
|
|
178
|
+
maxLength: length,
|
|
179
|
+
value,
|
|
180
|
+
onChange,
|
|
181
|
+
onComplete,
|
|
182
|
+
disabled,
|
|
183
|
+
error,
|
|
184
|
+
autoFocus,
|
|
185
|
+
name,
|
|
186
|
+
pattern,
|
|
187
|
+
containerClassName: className,
|
|
188
|
+
theme: resolvedTheme,
|
|
189
|
+
slotSize: resolvedSlotSize,
|
|
190
|
+
classNames,
|
|
191
|
+
children: groups.map((group, groupIndex) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
|
|
192
|
+
groupIndex > 0 && separator && /* @__PURE__ */ jsx(InputOTPSeparator, {}),
|
|
193
|
+
/* @__PURE__ */ jsx(InputOTPGroup, { children: group.map((index) => /* @__PURE__ */ jsx(InputOTPSlot, { index }, index)) })
|
|
194
|
+
] }, groupIndex))
|
|
195
|
+
}
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
);
|
|
199
|
+
OTP.displayName = "OTP";
|
|
200
|
+
|
|
201
|
+
export {
|
|
202
|
+
cn,
|
|
203
|
+
InputOTP,
|
|
204
|
+
InputOTPGroup,
|
|
205
|
+
InputOTPSlot,
|
|
206
|
+
InputOTPSeparator,
|
|
207
|
+
OTP
|
|
208
|
+
};
|
package/dist/index.d.mts
CHANGED
|
@@ -1,16 +1,9 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot,
|
|
1
|
+
export { IrisMail, IrisMailConfig, SendMailOptions, SendMailResult } from './server/index.mjs';
|
|
2
|
+
export { InputOTP, InputOTPGroup, InputOTPProps, InputOTPSeparator, InputOTPSlot, OTP, OTPClassNames, OTPProps, OTPSize, OTPTheme } from './react/index.mjs';
|
|
3
3
|
import { ClassValue } from 'clsx';
|
|
4
4
|
import 'input-otp';
|
|
5
5
|
import 'react';
|
|
6
6
|
|
|
7
7
|
declare function cn(...inputs: ClassValue[]): string;
|
|
8
|
-
declare const OTP_DEFAULTS: {
|
|
9
|
-
LENGTH: number;
|
|
10
|
-
EXPIRY_MINUTES: number;
|
|
11
|
-
RESEND_COOLDOWN_SECONDS: number;
|
|
12
|
-
MAX_ATTEMPTS: number;
|
|
13
|
-
RATE_LIMIT_RESET_HOURS: number;
|
|
14
|
-
};
|
|
15
8
|
|
|
16
|
-
export {
|
|
9
|
+
export { cn };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,9 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot,
|
|
1
|
+
export { IrisMail, IrisMailConfig, SendMailOptions, SendMailResult } from './server/index.js';
|
|
2
|
+
export { InputOTP, InputOTPGroup, InputOTPProps, InputOTPSeparator, InputOTPSlot, OTP, OTPClassNames, OTPProps, OTPSize, OTPTheme } from './react/index.js';
|
|
3
3
|
import { ClassValue } from 'clsx';
|
|
4
4
|
import 'input-otp';
|
|
5
5
|
import 'react';
|
|
6
6
|
|
|
7
7
|
declare function cn(...inputs: ClassValue[]): string;
|
|
8
|
-
declare const OTP_DEFAULTS: {
|
|
9
|
-
LENGTH: number;
|
|
10
|
-
EXPIRY_MINUTES: number;
|
|
11
|
-
RESEND_COOLDOWN_SECONDS: number;
|
|
12
|
-
MAX_ATTEMPTS: number;
|
|
13
|
-
RATE_LIMIT_RESET_HOURS: number;
|
|
14
|
-
};
|
|
15
8
|
|
|
16
|
-
export {
|
|
9
|
+
export { cn };
|