jdc-react-mailer 0.1.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/dist/style.css ADDED
@@ -0,0 +1,257 @@
1
+ /* jdc-react-mailer — themeable via CSS custom properties */
2
+
3
+ .jdcm-root {
4
+ --jdcm-primary: #0070f3;
5
+ --jdcm-primary-hover: #005bb5;
6
+ --jdcm-bg: #ffffff;
7
+ --jdcm-surface: #f9fafb;
8
+ --jdcm-border: #e2e8f0;
9
+ --jdcm-text: #1a202c;
10
+ --jdcm-muted: #718096;
11
+ --jdcm-error: #e53e3e;
12
+ --jdcm-success: #38a169;
13
+ --jdcm-radius: 8px;
14
+ --jdcm-spacing: 1rem;
15
+ --jdcm-font: inherit;
16
+ --jdcm-transition: 0.2s ease;
17
+ --jdcm-focus-ring: 2px solid var(--jdcm-primary);
18
+ --jdcm-focus-offset: 2px;
19
+
20
+ font-family: var(--jdcm-font);
21
+ color: var(--jdcm-text);
22
+ background: var(--jdcm-bg);
23
+ padding: var(--jdcm-spacing);
24
+ border-radius: var(--jdcm-radius);
25
+ box-sizing: border-box;
26
+ }
27
+
28
+ .jdcm-root *,
29
+ .jdcm-root *::before,
30
+ .jdcm-root *::after {
31
+ box-sizing: border-box;
32
+ }
33
+
34
+ /* Form layout */
35
+ .jdcm-form {
36
+ display: flex;
37
+ flex-direction: column;
38
+ gap: calc(var(--jdcm-spacing) * 1.25);
39
+ }
40
+
41
+ /* Field group */
42
+ .jdcm-field {
43
+ display: flex;
44
+ flex-direction: column;
45
+ gap: 0.25rem;
46
+ }
47
+
48
+ .jdcm-field label {
49
+ font-size: 0.875rem;
50
+ font-weight: 500;
51
+ color: var(--jdcm-text);
52
+ }
53
+
54
+ .jdcm-field input,
55
+ .jdcm-field textarea {
56
+ width: 100%;
57
+ padding: 0.625rem 0.75rem;
58
+ font-family: var(--jdcm-font);
59
+ font-size: 1rem;
60
+ line-height: 1.5;
61
+ color: var(--jdcm-text);
62
+ background: var(--jdcm-surface);
63
+ border: 1px solid var(--jdcm-border);
64
+ border-radius: var(--jdcm-radius);
65
+ transition: border-color var(--jdcm-transition), box-shadow var(--jdcm-transition);
66
+ }
67
+
68
+ .jdcm-field input::placeholder,
69
+ .jdcm-field textarea::placeholder {
70
+ color: var(--jdcm-muted);
71
+ }
72
+
73
+ .jdcm-field input:hover,
74
+ .jdcm-field textarea:hover {
75
+ border-color: color-mix(in srgb, var(--jdcm-primary) 30%, var(--jdcm-border));
76
+ }
77
+
78
+ .jdcm-field input:focus,
79
+ .jdcm-field textarea:focus {
80
+ outline: none;
81
+ border-color: var(--jdcm-primary);
82
+ box-shadow: 0 0 0 var(--jdcm-focus-offset) var(--jdcm-primary);
83
+ }
84
+
85
+ .jdcm-field input:focus-visible,
86
+ .jdcm-field textarea:focus-visible {
87
+ outline: var(--jdcm-focus-ring);
88
+ outline-offset: var(--jdcm-focus-offset);
89
+ }
90
+
91
+ .jdcm-field input[aria-invalid="true"],
92
+ .jdcm-field textarea[aria-invalid="true"] {
93
+ border-color: var(--jdcm-error);
94
+ }
95
+
96
+ .jdcm-field input[aria-invalid="true"]:focus,
97
+ .jdcm-field textarea[aria-invalid="true"]:focus {
98
+ box-shadow: 0 0 0 var(--jdcm-focus-offset) var(--jdcm-error);
99
+ }
100
+
101
+ .jdcm-field textarea {
102
+ min-height: 120px;
103
+ resize: vertical;
104
+ }
105
+
106
+ /* Honeypot: visually hidden, not display:none for better spam behavior */
107
+ .jdcm-hp {
108
+ position: absolute;
109
+ width: 1px;
110
+ height: 1px;
111
+ padding: 0;
112
+ margin: -1px;
113
+ overflow: hidden;
114
+ clip: rect(0, 0, 0, 0);
115
+ white-space: nowrap;
116
+ border: 0;
117
+ }
118
+
119
+ /* Error message */
120
+ .jdcm-error-msg {
121
+ font-size: 0.8125rem;
122
+ color: var(--jdcm-error);
123
+ margin-top: 0.125rem;
124
+ }
125
+
126
+ /* Submit button */
127
+ .jdcm-submit {
128
+ align-self: flex-start;
129
+ padding: 0.625rem 1.25rem;
130
+ font-family: var(--jdcm-font);
131
+ font-size: 1rem;
132
+ font-weight: 500;
133
+ color: #fff;
134
+ background: var(--jdcm-primary);
135
+ border: none;
136
+ border-radius: var(--jdcm-radius);
137
+ cursor: pointer;
138
+ transition: background-color var(--jdcm-transition), transform var(--jdcm-transition);
139
+ }
140
+
141
+ .jdcm-submit:hover:not(:disabled) {
142
+ background: var(--jdcm-primary-hover);
143
+ }
144
+
145
+ .jdcm-submit:focus-visible {
146
+ outline: var(--jdcm-focus-ring);
147
+ outline-offset: var(--jdcm-focus-offset);
148
+ }
149
+
150
+ .jdcm-submit:disabled {
151
+ cursor: not-allowed;
152
+ opacity: 0.8;
153
+ }
154
+
155
+ /* Loading spinner */
156
+ .jdcm-spinner {
157
+ display: inline-block;
158
+ width: 1em;
159
+ height: 1em;
160
+ margin-right: 0.5em;
161
+ vertical-align: -0.15em;
162
+ border: 2px solid currentColor;
163
+ border-right-color: transparent;
164
+ border-radius: 50%;
165
+ animation: jdcm-spin 0.6s linear infinite;
166
+ }
167
+
168
+ @keyframes jdcm-spin {
169
+ to {
170
+ transform: rotate(360deg);
171
+ }
172
+ }
173
+
174
+ /* Success message */
175
+ .jdcm-success-msg {
176
+ padding: var(--jdcm-spacing);
177
+ font-size: 1rem;
178
+ color: var(--jdcm-success);
179
+ background: color-mix(in srgb, var(--jdcm-success) 12%, transparent);
180
+ border-radius: var(--jdcm-radius);
181
+ animation: jdcm-slide-in 0.3s ease;
182
+ }
183
+
184
+ @keyframes jdcm-slide-in {
185
+ from {
186
+ opacity: 0;
187
+ transform: translateY(-8px);
188
+ }
189
+ to {
190
+ opacity: 1;
191
+ transform: translateY(0);
192
+ }
193
+ }
194
+
195
+ /* Error banner */
196
+ .jdcm-error-banner {
197
+ display: flex;
198
+ align-items: center;
199
+ justify-content: space-between;
200
+ gap: var(--jdcm-spacing);
201
+ padding: var(--jdcm-spacing);
202
+ font-size: 0.9375rem;
203
+ color: var(--jdcm-error);
204
+ background: color-mix(in srgb, var(--jdcm-error) 12%, transparent);
205
+ border-radius: var(--jdcm-radius);
206
+ animation: jdcm-slide-in 0.3s ease;
207
+ }
208
+
209
+ .jdcm-error-banner button {
210
+ flex-shrink: 0;
211
+ padding: 0.25rem 0.5rem;
212
+ font-family: var(--jdcm-font);
213
+ font-size: 0.875rem;
214
+ color: inherit;
215
+ background: transparent;
216
+ border: 1px solid currentColor;
217
+ border-radius: var(--jdcm-radius);
218
+ cursor: pointer;
219
+ opacity: 0.9;
220
+ }
221
+
222
+ .jdcm-error-banner button:hover {
223
+ opacity: 1;
224
+ }
225
+
226
+ /* Reduced motion */
227
+ @media (prefers-reduced-motion: reduce) {
228
+ .jdcm-root {
229
+ --jdcm-transition: 0s;
230
+ }
231
+
232
+ .jdcm-field input,
233
+ .jdcm-field textarea,
234
+ .jdcm-submit {
235
+ transition: none;
236
+ }
237
+
238
+ .jdcm-spinner {
239
+ animation-duration: 1.2s;
240
+ }
241
+
242
+ .jdcm-success-msg,
243
+ .jdcm-error-banner {
244
+ animation: none;
245
+ }
246
+ }
247
+
248
+ /* Dark mode support (optional; consumer can override variables) */
249
+ @media (prefers-color-scheme: dark) {
250
+ .jdcm-root:not([data-theme-set]) {
251
+ --jdcm-bg: #1a202c;
252
+ --jdcm-surface: #2d3748;
253
+ --jdcm-border: #4a5568;
254
+ --jdcm-text: #f7fafc;
255
+ --jdcm-muted: #a0aec0;
256
+ }
257
+ }
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "jdc-react-mailer",
3
+ "version": "0.1.0",
4
+ "description": "Reusable, themeable React contact form with Next.js handler and Nodemailer",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ },
15
+ "./handler": {
16
+ "types": "./dist/handler/index.d.ts",
17
+ "import": "./dist/handler/index.js",
18
+ "require": "./dist/handler/index.cjs"
19
+ },
20
+ "./style.css": "./dist/style.css"
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "README.md",
25
+ "CHANGELOG.md"
26
+ ],
27
+ "peerDependencies": {
28
+ "next": ">=14",
29
+ "react": ">=18",
30
+ "react-dom": ">=18"
31
+ },
32
+ "peerDependenciesMeta": {
33
+ "next": {
34
+ "optional": true
35
+ }
36
+ },
37
+ "dependencies": {
38
+ "nodemailer": "^8.0.1"
39
+ },
40
+ "engines": {
41
+ "node": ">=18"
42
+ },
43
+ "devDependencies": {
44
+ "@eslint/js": "^10.0.1",
45
+ "@testing-library/dom": "^10.4.1",
46
+ "@testing-library/react": "^16.3.2",
47
+ "@types/node": "^25.2.3",
48
+ "@types/nodemailer": "^7.0.10",
49
+ "@types/react": "^19.2.14",
50
+ "@types/react-dom": "^19.2.3",
51
+ "@vitejs/plugin-react": "^5.1.4",
52
+ "eslint": "^10.0.0",
53
+ "happy-dom": "^20.6.2",
54
+ "next": "^16.1.6",
55
+ "prettier": "^3.8.1",
56
+ "tsup": "^8.5.1",
57
+ "typescript": "^5.9.3",
58
+ "typescript-eslint": "^8.56.0",
59
+ "vitest": "^4.0.18"
60
+ },
61
+ "scripts": {
62
+ "build": "tsup && cp src/components/ContactForm/ContactForm.css dist/style.css",
63
+ "dev": "tsup --watch",
64
+ "test": "vitest run",
65
+ "lint": "eslint src --ext .ts,.tsx",
66
+ "format": "prettier --write src"
67
+ }
68
+ }