@visns-studio/visns-components 5.14.16 → 5.14.17

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/package.json CHANGED
@@ -89,7 +89,7 @@
89
89
  "react-dom": "^17.0.0 || ^18.0.0"
90
90
  },
91
91
  "name": "@visns-studio/visns-components",
92
- "version": "5.14.16",
92
+ "version": "5.14.17",
93
93
  "description": "Various packages to assist in the development of our Custom Applications.",
94
94
  "main": "src/index.js",
95
95
  "files": [
@@ -25,6 +25,7 @@ import {
25
25
  X,
26
26
  } from 'lucide-react';
27
27
  import { toast } from 'react-toastify';
28
+ import validator from 'validator';
28
29
  import styles from './styles/Field.module.scss';
29
30
 
30
31
  import CustomFetch from './Fetch';
@@ -81,6 +82,9 @@ function Field({
81
82
  const [formItemStyle, setFormItemStyle] = useState({});
82
83
  const [formItemClass, setFormItemClass] = useState('');
83
84
  const [options, setOptions] = useState([]);
85
+ const [emailError, setEmailError] = useState('');
86
+ const [emailSuggestions, setEmailSuggestions] = useState([]);
87
+ const [showEmailSuggestions, setShowEmailSuggestions] = useState(false);
84
88
 
85
89
  /** Canvas States */
86
90
  const [canvasPopupOpen, setCanvasPopupOpen] = useState(false);
@@ -91,6 +95,91 @@ function Field({
91
95
  const [lineColor, setLineColor] = useState('rgba(0, 0, 0, 1)');
92
96
  const sketchRef = useRef(null);
93
97
 
98
+ /** Email Functions */
99
+ const validateEmail = (email) => {
100
+ if (!email) {
101
+ setEmailError('');
102
+ return true;
103
+ }
104
+
105
+ if (!validator.isEmail(email)) {
106
+ setEmailError('Please enter a valid email address');
107
+ return false;
108
+ }
109
+
110
+ setEmailError('');
111
+ return true;
112
+ };
113
+
114
+ const generateEmailSuggestions = (inputEmail) => {
115
+ if (!inputEmail || inputEmail.length < 3) {
116
+ setEmailSuggestions([]);
117
+ setShowEmailSuggestions(false);
118
+ return;
119
+ }
120
+
121
+ const commonDomains = [
122
+ 'gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com',
123
+ 'icloud.com', 'live.com', 'msn.com', 'aol.com',
124
+ 'company.com', 'business.com', 'work.com'
125
+ ];
126
+
127
+ const suggestions = [];
128
+ const [localPart, domainPart] = inputEmail.split('@');
129
+
130
+ if (!localPart) return;
131
+
132
+ if (!domainPart || domainPart.length === 0) {
133
+ // If no @ symbol or no domain part, suggest common domains
134
+ commonDomains.forEach(domain => {
135
+ suggestions.push(`${localPart}@${domain}`);
136
+ });
137
+ } else if (domainPart.length > 0 && domainPart.length < 4) {
138
+ // If domain part is short, suggest completions
139
+ commonDomains.forEach(domain => {
140
+ if (domain.toLowerCase().startsWith(domainPart.toLowerCase())) {
141
+ suggestions.push(`${localPart}@${domain}`);
142
+ }
143
+ });
144
+ } else {
145
+ // Check for common typos and suggest corrections
146
+ const typoMap = {
147
+ 'gmial.com': 'gmail.com',
148
+ 'gmai.com': 'gmail.com',
149
+ 'yahooo.com': 'yahoo.com',
150
+ 'hotmial.com': 'hotmail.com',
151
+ 'outlok.com': 'outlook.com',
152
+ 'iclou.com': 'icloud.com'
153
+ };
154
+
155
+ if (typoMap[domainPart.toLowerCase()]) {
156
+ suggestions.push(`${localPart}@${typoMap[domainPart.toLowerCase()]}`);
157
+ }
158
+ }
159
+
160
+ setEmailSuggestions(suggestions.slice(0, 5)); // Limit to 5 suggestions
161
+ setShowEmailSuggestions(suggestions.length > 0);
162
+ };
163
+
164
+ const handleEmailChange = (e) => {
165
+ const email = e.target.value;
166
+ validateEmail(email);
167
+ generateEmailSuggestions(email);
168
+ onChange(e);
169
+ };
170
+
171
+ const selectEmailSuggestion = (suggestion) => {
172
+ const syntheticEvent = {
173
+ target: {
174
+ dataset: { name: settings.id },
175
+ value: suggestion
176
+ }
177
+ };
178
+ validateEmail(suggestion);
179
+ setShowEmailSuggestions(false);
180
+ onChange(syntheticEvent);
181
+ };
182
+
94
183
  /** Canvas Functions */
95
184
  const handleCanvasSave = async () => {
96
185
  // Save the current canvas state
@@ -469,6 +558,7 @@ function Field({
469
558
  'dropdown',
470
559
  'dropdown-ajax',
471
560
  'dynamicdata',
561
+ 'email',
472
562
  'file',
473
563
  'gallery',
474
564
  'image',
@@ -2073,6 +2163,49 @@ function Field({
2073
2163
  autoComplete="off"
2074
2164
  />
2075
2165
  );
2166
+ case 'email':
2167
+ return (
2168
+ <div className={styles.emailFieldContainer}>
2169
+ <input
2170
+ type="email"
2171
+ data-name={settings.id}
2172
+ className={`${inputClass[settings.id]} ${emailError ? styles.emailError : ''}`}
2173
+ placeholder="Enter email address"
2174
+ onChange={handleEmailChange}
2175
+ onBlur={() => setShowEmailSuggestions(false)}
2176
+ onFocus={() => generateEmailSuggestions(inputValue || '')}
2177
+ value={
2178
+ inputValue && inputValue !== 'null'
2179
+ ? inputValue
2180
+ : ''
2181
+ }
2182
+ readOnly={settings.readOnly ? settings.readOnly : false}
2183
+ autoComplete="email"
2184
+ spellCheck={false}
2185
+ />
2186
+ {emailError && (
2187
+ <div className={styles.emailErrorMessage}>
2188
+ {emailError}
2189
+ </div>
2190
+ )}
2191
+ {showEmailSuggestions && emailSuggestions.length > 0 && (
2192
+ <div className={styles.emailSuggestions}>
2193
+ {emailSuggestions.map((suggestion, index) => (
2194
+ <div
2195
+ key={index}
2196
+ className={styles.emailSuggestion}
2197
+ onMouseDown={(e) => {
2198
+ e.preventDefault(); // Prevent blur event
2199
+ selectEmailSuggestion(suggestion);
2200
+ }}
2201
+ >
2202
+ {suggestion}
2203
+ </div>
2204
+ ))}
2205
+ </div>
2206
+ )}
2207
+ </div>
2208
+ );
2076
2209
  case 'password':
2077
2210
  return (
2078
2211
  <input
@@ -2109,6 +2242,7 @@ function Field({
2109
2242
  'dropdown',
2110
2243
  'dropdown-ajax',
2111
2244
  'dynamicdata',
2245
+ 'email',
2112
2246
  'file',
2113
2247
  'gallery',
2114
2248
  'image',
@@ -1303,3 +1303,102 @@ input[type='file']:hover {
1303
1303
  font-size: 0.85em;
1304
1304
  }
1305
1305
  }
1306
+
1307
+ /* Email Field Styles */
1308
+ .emailFieldContainer {
1309
+ position: relative;
1310
+ width: 100%;
1311
+ }
1312
+
1313
+ .emailError {
1314
+ border-color: var(--error-color, #dc3545) !important;
1315
+ box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25) !important;
1316
+ }
1317
+
1318
+ .emailErrorMessage {
1319
+ color: var(--error-color, #dc3545);
1320
+ font-size: 0.875rem;
1321
+ margin-top: 0.25rem;
1322
+ display: flex;
1323
+ align-items: center;
1324
+ gap: 0.25rem;
1325
+ }
1326
+
1327
+ .emailErrorMessage::before {
1328
+ content: "⚠";
1329
+ font-size: 1rem;
1330
+ }
1331
+
1332
+ .emailSuggestions {
1333
+ position: absolute;
1334
+ top: 100%;
1335
+ left: 0;
1336
+ right: 0;
1337
+ background: var(--item-color, #fff);
1338
+ border: 1px solid var(--border-color, #e9ecef);
1339
+ border-top: none;
1340
+ border-radius: 0 0 4px 4px; /* Match react-select border radius */
1341
+ box-shadow: none; /* Match react-select no-shadow pattern */
1342
+ z-index: 999; /* Match react-select z-index */
1343
+ max-height: 200px;
1344
+ overflow-y: auto;
1345
+ font-size: 14px; /* Match react-select font size */
1346
+ }
1347
+
1348
+ .emailSuggestion {
1349
+ padding: 8px 12px; /* Match react-select option padding */
1350
+ cursor: pointer;
1351
+ transition: background-color 0.15s ease; /* Match react-select transition */
1352
+ color: var(--text-color, #333);
1353
+ line-height: 1.4;
1354
+ }
1355
+
1356
+ .emailSuggestion:hover {
1357
+ background-color: #f5f5f5; /* Match react-select hover state */
1358
+ color: var(--text-color, #333);
1359
+ }
1360
+
1361
+ .emailSuggestion:active {
1362
+ background-color: var(--primary-color, #007bff);
1363
+ color: white;
1364
+ }
1365
+
1366
+ /* Enhanced email field input styles */
1367
+ .emailFieldContainer input[type="email"] {
1368
+ border-radius: var(--br, 6px);
1369
+ transition: border-color 0.15s ease; /* Match react-select transition */
1370
+ position: relative;
1371
+ }
1372
+
1373
+ .emailFieldContainer input[type="email"]:focus {
1374
+ outline: none;
1375
+ border-color: var(--primary-color, #007bff);
1376
+ box-shadow: none; /* Match react-select no-shadow pattern */
1377
+ }
1378
+
1379
+ .emailFieldContainer input[type="email"]:valid {
1380
+ border-color: var(--success-color, #28a745);
1381
+ }
1382
+
1383
+ .emailFieldContainer input[type="email"]:valid:focus {
1384
+ border-color: var(--success-color, #28a745);
1385
+ box-shadow: none; /* Match react-select no-shadow pattern */
1386
+ }
1387
+
1388
+ /* Dark mode support */
1389
+ @media (prefers-color-scheme: dark) {
1390
+ .emailSuggestions {
1391
+ background: var(--dark-item-color, #2d3748);
1392
+ border-color: var(--dark-border-color, #4a5568);
1393
+ }
1394
+
1395
+ .emailSuggestion {
1396
+ color: var(--dark-text-color, #e2e8f0);
1397
+ border-bottom-color: var(--dark-border-color, #4a5568);
1398
+ }
1399
+
1400
+ .emailSuggestion:hover {
1401
+ background: var(--dark-primary-color, #3182ce);
1402
+ color: var(--dark-primary-text-color, #fff);
1403
+ }
1404
+ }