mado-ui 0.3.2 → 0.4.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/css/index.css +41 -6
- package/dist/components/button.d.ts +3 -23
- package/dist/components/details.d.ts +2 -2
- package/dist/components/drop-down.d.ts +1 -1
- package/dist/components/form/fieldset.d.ts +35 -7
- package/dist/components/form/index.d.ts +5 -6
- package/dist/components/form/input/date/index.d.ts +36 -0
- package/dist/components/form/input/index.d.ts +78 -12
- package/dist/components/form/submit-button.d.ts +1 -1
- package/dist/components/form/textarea.d.ts +1 -1
- package/dist/components/ghost.d.ts +1 -1
- package/dist/components/heading.d.ts +1 -1
- package/dist/components/iframe.d.ts +15 -0
- package/dist/components/index.d.ts +10 -11
- package/dist/components/link.d.ts +3 -14
- package/dist/components/modal.d.ts +1 -1
- package/dist/components/time.d.ts +1 -1
- package/dist/components/tooltip.d.ts +1 -1
- package/dist/components.esm.js +466 -122
- package/dist/components.esm.js.map +1 -1
- package/dist/components.js +465 -119
- package/dist/components.js.map +1 -1
- package/dist/hooks/index.d.ts +4 -3
- package/dist/hooks/use-fieldset-context.d.ts +22 -0
- package/dist/hooks/use-form-context.d.ts +18 -10
- package/dist/hooks.esm.js +99 -5
- package/dist/hooks.esm.js.map +1 -1
- package/dist/hooks.js +100 -3
- package/dist/hooks.js.map +1 -1
- package/dist/icons/3-people.d.ts +2 -2
- package/dist/icons/3-rectangles-desktop-fill.d.ts +2 -2
- package/dist/icons/3-rectangles-desktop.d.ts +2 -2
- package/dist/icons/airplane.d.ts +2 -2
- package/dist/icons/arrow-triangle-2-circlepath-circle-fill.d.ts +2 -2
- package/dist/icons/arrow-triangle-2-circlepath-circle.d.ts +2 -2
- package/dist/icons/bag-fill.d.ts +2 -2
- package/dist/icons/banknote.d.ts +2 -2
- package/dist/icons/bell-fill.d.ts +2 -2
- package/dist/icons/bolt-car.d.ts +2 -2
- package/dist/icons/bolt-fill.d.ts +2 -2
- package/dist/icons/bolt-ring-closed.d.ts +2 -2
- package/dist/icons/bolt-trianglebadge-exclamationmark.d.ts +2 -2
- package/dist/icons/book-fill.d.ts +2 -2
- package/dist/icons/bookmark-fill.d.ts +2 -2
- package/dist/icons/briefcase-fill.d.ts +2 -2
- package/dist/icons/bubble-left-fill.d.ts +2 -2
- package/dist/icons/building-2-fill.d.ts +2 -2
- package/dist/icons/calendar.d.ts +2 -2
- package/dist/icons/camera-fill.d.ts +2 -2
- package/dist/icons/car-fill.d.ts +2 -2
- package/dist/icons/cart-fill.d.ts +2 -2
- package/dist/icons/chart-bar-doc-horizontal.d.ts +2 -2
- package/dist/icons/checkmark-seal.d.ts +2 -2
- package/dist/icons/checkmark.d.ts +2 -2
- package/dist/icons/chevron-down.d.ts +2 -2
- package/dist/icons/chevron-left-forwardslash-chevron-right.d.ts +2 -2
- package/dist/icons/chevron-left.d.ts +2 -2
- package/dist/icons/chevron-right.d.ts +2 -2
- package/dist/icons/chevron-up-chevron-down.d.ts +2 -2
- package/dist/icons/circle-fill.d.ts +2 -2
- package/dist/icons/clock-badge-checkmark.d.ts +2 -2
- package/dist/icons/clock-fill.d.ts +2 -2
- package/dist/icons/cloud-fill.d.ts +2 -2
- package/dist/icons/cube-fill.d.ts +2 -2
- package/dist/icons/curve-point-left.d.ts +2 -2
- package/dist/icons/dial-high.d.ts +2 -2
- package/dist/icons/doc-fill.d.ts +2 -2
- package/dist/icons/doc-on-clipboard.d.ts +2 -2
- package/dist/icons/doc-on-doc-fill.d.ts +2 -2
- package/dist/icons/doc-on-doc.d.ts +2 -2
- package/dist/icons/doc-text-magnifyingglass.d.ts +2 -2
- package/dist/icons/dollar-sign.d.ts +2 -2
- package/dist/icons/ellipsis-circle-fill.d.ts +2 -2
- package/dist/icons/ellipsis-circle.d.ts +2 -2
- package/dist/icons/envelope-fill.d.ts +2 -2
- package/dist/icons/envelope.d.ts +2 -2
- package/dist/icons/exclamationmark-octagon.d.ts +2 -2
- package/dist/icons/eye.d.ts +2 -2
- package/dist/icons/figure-water-fitness.d.ts +2 -2
- package/dist/icons/flag-fill.d.ts +2 -2
- package/dist/icons/flame-fill.d.ts +2 -2
- package/dist/icons/folder-fill.d.ts +2 -2
- package/dist/icons/folder.d.ts +2 -2
- package/dist/icons/gearshape-fill.d.ts +2 -2
- package/dist/icons/gearshape.d.ts +2 -2
- package/dist/icons/gift-fill.d.ts +2 -2
- package/dist/icons/globe-americas-fill.d.ts +2 -2
- package/dist/icons/hare-fill.d.ts +2 -2
- package/dist/icons/house-deskclock.d.ts +2 -2
- package/dist/icons/house-fill.d.ts +2 -2
- package/dist/icons/house.d.ts +2 -2
- package/dist/icons/iphone-house.d.ts +2 -2
- package/dist/icons/light-ribbon.d.ts +2 -2
- package/dist/icons/lightbulb-fill.d.ts +2 -2
- package/dist/icons/lightbulb-led.d.ts +2 -2
- package/dist/icons/list-bullet-clipboard-fill.d.ts +2 -2
- package/dist/icons/magnifyingglass.d.ts +2 -2
- package/dist/icons/map-pin-ellipse.d.ts +2 -2
- package/dist/icons/minus-plus-batterblock.d.ts +2 -2
- package/dist/icons/network-shield.d.ts +2 -2
- package/dist/icons/network.d.ts +2 -2
- package/dist/icons/newspaper-fill.d.ts +2 -2
- package/dist/icons/number.d.ts +2 -2
- package/dist/icons/paperplane-fill.d.ts +2 -2
- package/dist/icons/person-crop-square.d.ts +2 -2
- package/dist/icons/person-fill-questionmark.d.ts +2 -2
- package/dist/icons/person-fill.d.ts +2 -2
- package/dist/icons/person.d.ts +2 -2
- package/dist/icons/phone-arrow-up-right.d.ts +2 -2
- package/dist/icons/phone-fill.d.ts +2 -2
- package/dist/icons/phone.d.ts +2 -2
- package/dist/icons/play-rectangle-fill.d.ts +2 -2
- package/dist/icons/plus.d.ts +2 -2
- package/dist/icons/qrcode.d.ts +2 -2
- package/dist/icons/rectangle-portrait-and-arrow-left-fill.d.ts +2 -2
- package/dist/icons/rectangle-portrait-and-arrow-left.d.ts +2 -2
- package/dist/icons/sensor.d.ts +2 -2
- package/dist/icons/signature.d.ts +2 -2
- package/dist/icons/solar-panel.d.ts +2 -2
- package/dist/icons/square-and-arrow-down-fill.d.ts +2 -2
- package/dist/icons/square-and-arrow-down.d.ts +2 -2
- package/dist/icons/square-and-arrow-up-fill.d.ts +2 -2
- package/dist/icons/square-and-arrow-up.d.ts +2 -2
- package/dist/icons/square-and-pencil-fill.d.ts +2 -2
- package/dist/icons/square-and-pencil.d.ts +2 -2
- package/dist/icons/text-bubble.d.ts +2 -2
- package/dist/icons/trash-fill.d.ts +2 -2
- package/dist/icons/trash.d.ts +2 -2
- package/dist/icons/tree.d.ts +2 -2
- package/dist/icons/umbrella-fill.d.ts +2 -2
- package/dist/icons/xmark.d.ts +2 -2
- package/dist/icons.esm.js.map +1 -1
- package/dist/icons.js.map +1 -1
- package/dist/index.esm.js +466 -122
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +465 -119
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.ts +1 -2
- package/dist/types/utils.d.ts +20 -0
- package/dist/utils/get-date.d.ts +17 -0
- package/dist/utils/index.d.ts +8 -9
- package/dist/utils/string-manipulation.d.ts +38 -3
- package/dist/utils/tw-sort.d.ts +1 -1
- package/dist/utils.esm.js +112 -21
- package/dist/utils.esm.js.map +1 -1
- package/dist/utils.js +113 -20
- package/dist/utils.js.map +1 -1
- package/package.json +4 -4
package/dist/components.js
CHANGED
|
@@ -208,6 +208,9 @@ function getMonth(month = d) {
|
|
|
208
208
|
formattedMonth = `0${formattedMonth}`;
|
|
209
209
|
return formattedMonth;
|
|
210
210
|
}
|
|
211
|
+
function getMonthIndexFromName(name) {
|
|
212
|
+
return monthNamesList.findIndex(monthName => monthName === name);
|
|
213
|
+
}
|
|
211
214
|
/**
|
|
212
215
|
* ### Get Month Name
|
|
213
216
|
* - Returns the name of the specified month
|
|
@@ -233,6 +236,30 @@ function getSeconds(seconds = d) {
|
|
|
233
236
|
formattedSeconds = `0${formattedSeconds}`;
|
|
234
237
|
return formattedSeconds;
|
|
235
238
|
}
|
|
239
|
+
/**
|
|
240
|
+
* ### Get User Readable Date
|
|
241
|
+
* - Returns a string of the current date in a user-friendly way
|
|
242
|
+
* - Includes `'Yesterday'`, '`Today'`, and `'Tomorrow'`
|
|
243
|
+
* @param date (default: `new Date()`)
|
|
244
|
+
* @returns {'Today'|'Tomorrow'|'Yesterday'|string} `'weekday, month day, year'` | `'Yesterday'` | `'Today'` | `'Tomorrow'`
|
|
245
|
+
*/
|
|
246
|
+
function getUserReadableDate(date = d) {
|
|
247
|
+
const dateTime = date.getTime();
|
|
248
|
+
const today = new Date(), isToday = dateTime === today.getTime();
|
|
249
|
+
if (isToday)
|
|
250
|
+
return 'Today';
|
|
251
|
+
const yesterday = new Date(today.getDate() - 1), isYesterday = dateTime === yesterday.getTime();
|
|
252
|
+
if (isYesterday)
|
|
253
|
+
return 'Yesterday';
|
|
254
|
+
const tomorrow = new Date(today.getDate() + 1), isTomorrow = dateTime === tomorrow.getTime();
|
|
255
|
+
if (isTomorrow)
|
|
256
|
+
return 'Tomorrow';
|
|
257
|
+
const thisYear = today.getFullYear(), isSameYear = date.getFullYear() === thisYear;
|
|
258
|
+
const fullDateString = toFullDateString(date, {
|
|
259
|
+
year: !isSameYear,
|
|
260
|
+
});
|
|
261
|
+
return fullDateString;
|
|
262
|
+
}
|
|
236
263
|
/**
|
|
237
264
|
* ### Get Weekday Name
|
|
238
265
|
* - Returns the weekday name of the specified day
|
|
@@ -245,6 +272,45 @@ function getWeekdayName(weekday = d) {
|
|
|
245
272
|
// Return the name of the day of the week
|
|
246
273
|
return weekdayNamesList[weekday.getDay()];
|
|
247
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* ### To Full Date String
|
|
277
|
+
* - Returns a formatted string to display the date
|
|
278
|
+
* @param {Date} date (default: `new Date()`)
|
|
279
|
+
* @param {ToFullDateStringOptionsProps} options Change how to display the weekday, month name, day of the month, and year.
|
|
280
|
+
* @returns {string} '`weekday`, `month` `day`, `year`'
|
|
281
|
+
*/
|
|
282
|
+
function toFullDateString(date = d, options) {
|
|
283
|
+
let weekdayName = getWeekdayName(date), monthName = getMonthName(date), dayOfMonth = date.getDate(), year = date.getFullYear().toString();
|
|
284
|
+
if (options) {
|
|
285
|
+
const includesDay = options.day !== false, includesMonth = options.month !== false, includesYear = options.year !== false;
|
|
286
|
+
{
|
|
287
|
+
weekdayName = weekdayName.slice(0, 3);
|
|
288
|
+
if (includesMonth || includesDay || includesYear)
|
|
289
|
+
weekdayName += ', ';
|
|
290
|
+
}
|
|
291
|
+
if (includesMonth) {
|
|
292
|
+
if (options.month === 'code')
|
|
293
|
+
monthName = monthName.slice(0, 3);
|
|
294
|
+
if (includesDay)
|
|
295
|
+
monthName += ' ';
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
monthName = '';
|
|
299
|
+
}
|
|
300
|
+
if (!includesDay)
|
|
301
|
+
dayOfMonth = '';
|
|
302
|
+
if (includesYear) {
|
|
303
|
+
if (options.year === 'code')
|
|
304
|
+
year = year.slice(-2);
|
|
305
|
+
if (includesMonth || includesDay)
|
|
306
|
+
year = ', ' + year;
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
year = '';
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return weekdayName + monthName + dayOfMonth + year;
|
|
313
|
+
}
|
|
248
314
|
|
|
249
315
|
function findComponentByType(children, componentType) {
|
|
250
316
|
const childrenArray = React.Children.toArray(children);
|
|
@@ -293,25 +359,97 @@ function isPhoneNumber(tel) {
|
|
|
293
359
|
/**
|
|
294
360
|
* # Format Phone Number
|
|
295
361
|
* Converts any string containing at least 10 numbers to a formatted phone number
|
|
296
|
-
* @param {string}
|
|
297
|
-
* @
|
|
362
|
+
* @param {string} phoneNumber
|
|
363
|
+
* @param options
|
|
364
|
+
* @property {string} `countryCode`
|
|
365
|
+
* @property {'continuous' | 'dot' | 'hyphenated' | 'none' | 'space' | 'standard'} `format`
|
|
366
|
+
*
|
|
367
|
+
* Input: a555b555c5555d
|
|
368
|
+
*
|
|
369
|
+
* @example
|
|
370
|
+
* format: 'continuous'
|
|
371
|
+
* countryCode: '1'
|
|
372
|
+
* returns: +1 5555555555
|
|
373
|
+
*
|
|
374
|
+
* @example
|
|
375
|
+
* format: 'dot'
|
|
376
|
+
* returns: 555.555.5555
|
|
377
|
+
*
|
|
378
|
+
* @example
|
|
379
|
+
* format: 'hyphenated'
|
|
380
|
+
* returns: 555-555-5555
|
|
381
|
+
*
|
|
382
|
+
* @example
|
|
383
|
+
* format: 'none'
|
|
384
|
+
* countryCode: '1'
|
|
385
|
+
* returns: +1 a555b555c5555d
|
|
386
|
+
*
|
|
387
|
+
* @example
|
|
388
|
+
* format: 'space'
|
|
389
|
+
* returns: 555 555 5555
|
|
390
|
+
*
|
|
391
|
+
* @example
|
|
392
|
+
* format: 'standard' (default)
|
|
393
|
+
* returns: (555) 555-5555
|
|
394
|
+
*
|
|
395
|
+
* @returns {string} string formatted
|
|
298
396
|
*/
|
|
299
|
-
function formatPhoneNumber(
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
397
|
+
function formatPhoneNumber(phoneNumber, options) {
|
|
398
|
+
const format = options?.format || 'standard';
|
|
399
|
+
if (format !== 'none')
|
|
400
|
+
phoneNumber = phoneNumber.replace(/\D/g, '').slice(-10);
|
|
401
|
+
switch (format) {
|
|
402
|
+
case 'dot':
|
|
403
|
+
phoneNumber = phoneNumber
|
|
404
|
+
.split('')
|
|
405
|
+
.map((char, index) => {
|
|
406
|
+
if (index === 2)
|
|
407
|
+
return `${char}.`;
|
|
408
|
+
if (index === 5)
|
|
409
|
+
return `${char}.`;
|
|
410
|
+
return char;
|
|
411
|
+
})
|
|
412
|
+
.join('');
|
|
413
|
+
break;
|
|
414
|
+
case 'hyphenated':
|
|
415
|
+
phoneNumber = phoneNumber
|
|
416
|
+
.split('')
|
|
417
|
+
.map((char, index) => {
|
|
418
|
+
if (index === 2)
|
|
419
|
+
return `${char}-`;
|
|
420
|
+
if (index === 5)
|
|
421
|
+
return `${char}-`;
|
|
422
|
+
return char;
|
|
423
|
+
})
|
|
424
|
+
.join('');
|
|
425
|
+
break;
|
|
426
|
+
case 'space':
|
|
427
|
+
phoneNumber = phoneNumber
|
|
428
|
+
.split('')
|
|
429
|
+
.map((char, index) => {
|
|
430
|
+
if (index === 2)
|
|
431
|
+
return `${char} `;
|
|
432
|
+
if (index === 5)
|
|
433
|
+
return `${char} `;
|
|
434
|
+
return char;
|
|
435
|
+
})
|
|
436
|
+
.join('');
|
|
437
|
+
break;
|
|
438
|
+
case 'standard':
|
|
439
|
+
phoneNumber = phoneNumber
|
|
440
|
+
.split('')
|
|
441
|
+
.map((char, index) => {
|
|
442
|
+
if (index === 0)
|
|
443
|
+
return `(${char}`;
|
|
444
|
+
if (index === 2)
|
|
445
|
+
return `${char}) `;
|
|
446
|
+
if (index === 5)
|
|
447
|
+
return `${char}-`;
|
|
448
|
+
return char;
|
|
449
|
+
})
|
|
450
|
+
.join('');
|
|
451
|
+
}
|
|
452
|
+
return `${options?.countryCode ? `+${options?.countryCode} ` : ''}` + phoneNumber;
|
|
315
453
|
}
|
|
316
454
|
/**
|
|
317
455
|
* # To Lower Case
|
|
@@ -935,7 +1073,7 @@ function DropDownItems({ anchor, children, className, containerClassName, style,
|
|
|
935
1073
|
}
|
|
936
1074
|
function DropDownSection({ children, label, labelProps, separatorAbove, separatorBelow, ...props }) {
|
|
937
1075
|
const { labelClassName, ...restLabelProps } = { labelClassName: labelProps?.className || '', ...labelProps };
|
|
938
|
-
return (jsxRuntime.jsx(react.MenuSection, { ...props, children: sectionBag => (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [separatorAbove && jsxRuntime.jsx(DropDownSeparator, {}), label && (jsxRuntime.jsx(react.MenuHeading, { ...restLabelProps, className: headingBag => twMerge('text-[size:larger] font-bold', typeof labelClassName === 'function' ? labelClassName(headingBag) : labelClassName), children: label })), typeof children === 'function' ? children(sectionBag) : children, separatorBelow && jsxRuntime.jsx(DropDownSeparator, {})] })) }));
|
|
1076
|
+
return (jsxRuntime.jsx(react.MenuSection, { ...props, children: sectionBag => (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [separatorAbove && jsxRuntime.jsx(DropDownSeparator, {}), label && (jsxRuntime.jsx(react.MenuHeading, { ...restLabelProps, className: headingBag => twMerge('text-[size:larger] font-bold text-current/80', typeof labelClassName === 'function' ? labelClassName(headingBag) : labelClassName), children: label })), typeof children === 'function' ? children(sectionBag) : children, separatorBelow && jsxRuntime.jsx(DropDownSeparator, {})] })) }));
|
|
939
1077
|
}
|
|
940
1078
|
function DropDownSeparator({ className, ...props }) {
|
|
941
1079
|
return (jsxRuntime.jsx(react.MenuSeparator, { ...props, className: bag => twMerge('my-4 block h-px rounded-full bg-neutral-950/20', typeof className === 'function' ? className(bag) : className) }));
|
|
@@ -995,12 +1133,106 @@ function createFastContext(defaultInitialState) {
|
|
|
995
1133
|
function defineField(fieldDefinition) {
|
|
996
1134
|
return fieldDefinition;
|
|
997
1135
|
}
|
|
998
|
-
|
|
1136
|
+
function isStringField(field) {
|
|
1137
|
+
return Boolean(field.type !== 'object' && field.type !== 'array');
|
|
1138
|
+
}
|
|
1139
|
+
const { Provider: Provider$2, useStore: useStore$2 } = createFastContext([]);
|
|
999
1140
|
function FormContextProvider({ children }) {
|
|
1000
|
-
return jsxRuntime.jsx(Provider$
|
|
1141
|
+
return jsxRuntime.jsx(Provider$2, { children: children });
|
|
1001
1142
|
}
|
|
1002
1143
|
function useFormContext() {
|
|
1003
|
-
|
|
1144
|
+
const [formContext, setFormContext] = useStore$2(store => store);
|
|
1145
|
+
const registerField = React.useCallback((field) => {
|
|
1146
|
+
setFormContext?.(prevContext => {
|
|
1147
|
+
const otherFields = (prevContext || []).filter(otherField => otherField.id !== field.id);
|
|
1148
|
+
return [...otherFields, field];
|
|
1149
|
+
});
|
|
1150
|
+
}, []);
|
|
1151
|
+
const removeField = React.useCallback((fieldID) => {
|
|
1152
|
+
setFormContext?.(prevContext => (prevContext || []).filter(field => field.id !== fieldID));
|
|
1153
|
+
}, []);
|
|
1154
|
+
const updateField = React.useCallback((fieldID, updates) => {
|
|
1155
|
+
setFormContext?.(prevContext => {
|
|
1156
|
+
const field = prevContext.find(({ id }) => id === fieldID);
|
|
1157
|
+
if (!field)
|
|
1158
|
+
throw new Error(`Field with id "${fieldID}" not found in form context.`);
|
|
1159
|
+
const otherFields = prevContext.filter(({ id }) => id !== fieldID);
|
|
1160
|
+
const updatedField = { ...field, ...updates };
|
|
1161
|
+
return [...otherFields, updatedField];
|
|
1162
|
+
});
|
|
1163
|
+
}, []);
|
|
1164
|
+
return [formContext, { registerField, removeField, updateField }];
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
const { Provider: Provider$1, useStore: useStore$1 } = createFastContext(undefined);
|
|
1168
|
+
function FieldsetContextProvider({ children, initialValue, }) {
|
|
1169
|
+
return jsxRuntime.jsx(Provider$1, { initialValue: initialValue, children: children });
|
|
1170
|
+
}
|
|
1171
|
+
function useFieldsetContext() {
|
|
1172
|
+
const [fieldsetContext, setFieldsetContext] = useStore$1(store => store), [, formContextFunctions] = useFormContext();
|
|
1173
|
+
const registerField = React.useCallback((field) => {
|
|
1174
|
+
setFieldsetContext?.(prev => {
|
|
1175
|
+
if (!prev)
|
|
1176
|
+
return prev;
|
|
1177
|
+
const existingFieldIndex = prev.fieldList.findIndex(existingField => existingField.id === field.id);
|
|
1178
|
+
const newFieldList = existingFieldIndex >= 0
|
|
1179
|
+
? prev.fieldList.map((existingField, index) => (index === existingFieldIndex ? field : existingField))
|
|
1180
|
+
: [...prev.fieldList, field];
|
|
1181
|
+
return { ...prev, fieldList: newFieldList };
|
|
1182
|
+
});
|
|
1183
|
+
}, []);
|
|
1184
|
+
const removeField = React.useCallback((fieldID) => {
|
|
1185
|
+
setFieldsetContext?.(prev => {
|
|
1186
|
+
if (!prev)
|
|
1187
|
+
return prev;
|
|
1188
|
+
return {
|
|
1189
|
+
...prev,
|
|
1190
|
+
fieldList: prev.fieldList.filter(field => field.id !== fieldID),
|
|
1191
|
+
};
|
|
1192
|
+
});
|
|
1193
|
+
}, []);
|
|
1194
|
+
const updateField = React.useCallback((fieldID, updates) => {
|
|
1195
|
+
setFieldsetContext?.(prev => {
|
|
1196
|
+
if (!prev)
|
|
1197
|
+
return prev;
|
|
1198
|
+
return {
|
|
1199
|
+
...prev,
|
|
1200
|
+
fieldList: prev.fieldList.map(field => (field.id === fieldID ? { ...field, ...updates } : field)),
|
|
1201
|
+
};
|
|
1202
|
+
});
|
|
1203
|
+
}, []);
|
|
1204
|
+
const createFormContextEntry = React.useCallback((fieldsetEntry) => {
|
|
1205
|
+
if (fieldsetEntry.join) {
|
|
1206
|
+
const joinedValue = fieldsetEntry.fieldList
|
|
1207
|
+
.filter(field => isStringField(field) && field.value !== '')
|
|
1208
|
+
.map(field => field.value)
|
|
1209
|
+
.join(fieldsetEntry.join);
|
|
1210
|
+
return defineField({
|
|
1211
|
+
type: 'string',
|
|
1212
|
+
id: fieldsetEntry.id,
|
|
1213
|
+
name: fieldsetEntry.name,
|
|
1214
|
+
value: joinedValue,
|
|
1215
|
+
required: fieldsetEntry.fieldList.some(field => field.required),
|
|
1216
|
+
invalid: fieldsetEntry.fieldList.some(field => field.invalid),
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
return defineField({
|
|
1220
|
+
type: 'object',
|
|
1221
|
+
id: fieldsetEntry.id,
|
|
1222
|
+
name: fieldsetEntry.name,
|
|
1223
|
+
fields: fieldsetEntry.fieldList,
|
|
1224
|
+
});
|
|
1225
|
+
}, []);
|
|
1226
|
+
React.useEffect(() => {
|
|
1227
|
+
if (!fieldsetContext || fieldsetContext.decorative)
|
|
1228
|
+
return;
|
|
1229
|
+
const formContextEntry = createFormContextEntry(fieldsetContext);
|
|
1230
|
+
formContextFunctions.registerField(formContextEntry);
|
|
1231
|
+
return () => {
|
|
1232
|
+
formContextFunctions.removeField(fieldsetContext.id);
|
|
1233
|
+
};
|
|
1234
|
+
}, [fieldsetContext, formContextFunctions.registerField, formContextFunctions.removeField, createFormContextEntry]);
|
|
1235
|
+
return [fieldsetContext, { registerField, removeField, updateField }];
|
|
1004
1236
|
}
|
|
1005
1237
|
|
|
1006
1238
|
const DEFAULT_STATUS = 'incomplete';
|
|
@@ -1012,9 +1244,18 @@ function useFormStatus() {
|
|
|
1012
1244
|
return useStore(store => store);
|
|
1013
1245
|
}
|
|
1014
1246
|
|
|
1015
|
-
function Fieldset({ children, className, legend, legendProps, ...props }) {
|
|
1247
|
+
function Fieldset({ children, className, decorative = false, join, legend, legendProps, name, ...props }) {
|
|
1248
|
+
const uniqueID = React.useId();
|
|
1249
|
+
const fieldsetId = toLowerCase(legend || name, [' ', '_']) + '§' + uniqueID;
|
|
1016
1250
|
const { className: legendClassName, ...restLegendProps } = legendProps || {};
|
|
1017
|
-
|
|
1251
|
+
name = legend || name;
|
|
1252
|
+
return (jsxRuntime.jsx(FieldsetContextProvider, { initialValue: {
|
|
1253
|
+
decorative,
|
|
1254
|
+
fieldList: [],
|
|
1255
|
+
id: fieldsetId,
|
|
1256
|
+
join,
|
|
1257
|
+
name,
|
|
1258
|
+
}, children: jsxRuntime.jsx(react.Fieldset, { ...props, name: name, className: bag => twMerge('contents', typeof className === 'function' ? className(bag) : className), children: bag => (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [legend && (jsxRuntime.jsx(react.Legend, { ...restLegendProps, className: twMerge('text-xl font-bold text-current/80', typeof legendClassName === 'function' ? legendClassName(bag) : legendClassName), children: legend })), typeof children === 'function' ? children(bag) : children] })) }) }));
|
|
1018
1259
|
}
|
|
1019
1260
|
|
|
1020
1261
|
/**
|
|
@@ -3206,15 +3447,17 @@ function ArrowSvg({ className, ...props }) {
|
|
|
3206
3447
|
return (jsxRuntime.jsxs("svg", { viewBox: '0 0 20 10', className: twMerge('h-2.5 w-5 fill-none', className), ...props, children: [jsxRuntime.jsx("path", { d: 'M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z', className: 'fill-neutral-50 dark:fill-neutral-800' }), jsxRuntime.jsx("path", { d: 'M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z', className: 'fill-neutral-400 dark:fill-none' }), jsxRuntime.jsx("path", { d: 'M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z', className: 'dark:fill-neutral-600' })] }));
|
|
3207
3448
|
}
|
|
3208
3449
|
|
|
3209
|
-
|
|
3210
|
-
|
|
3450
|
+
const specialCharacterRegex = new RegExp(/[!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~€‚ƒ„…†‡‰‹‘’“”•–—™›¡¢£¥§©«¬®°±¶º»¿×÷]/);
|
|
3451
|
+
function Input({ checked, className, defaultValue, description, descriptionProps, disabled, fieldProps, invalid = true, label, labelProps, max, min, name, onBlur, onChange, options, placeholder, ref, required = true, type, value, ...props }) {
|
|
3452
|
+
const [formContext, formContextFunctions] = useFormContext(), [fieldsetContext, fieldsetContextFunctions] = useFieldsetContext(), [errorMessage, setErrorMessage] = React.useState(undefined);
|
|
3453
|
+
if (type === 'password' && !placeholder)
|
|
3454
|
+
placeholder = '••••••••' + (required && !label ? '*' : '');
|
|
3211
3455
|
if (placeholder === '*')
|
|
3212
3456
|
placeholder = name + (required && !label ? '*' : '');
|
|
3213
3457
|
if (label === '*')
|
|
3214
3458
|
label = name;
|
|
3215
3459
|
const uniqueID = React.useId(), fieldContextID = toLowerCase(name, [, '_']) + '§' + uniqueID;
|
|
3216
|
-
|
|
3217
|
-
invalid = true;
|
|
3460
|
+
const isInFieldset = fieldsetContext && !fieldsetContext.decorative;
|
|
3218
3461
|
const getFieldContextType = () => {
|
|
3219
3462
|
switch (type) {
|
|
3220
3463
|
case 'email':
|
|
@@ -3232,26 +3475,27 @@ function Input({ checked, className, defaultValue, description, descriptionProps
|
|
|
3232
3475
|
}
|
|
3233
3476
|
};
|
|
3234
3477
|
const fieldContextType = getFieldContextType();
|
|
3235
|
-
const
|
|
3236
|
-
type: fieldContextType,
|
|
3237
|
-
id: fieldContextID,
|
|
3238
|
-
invalid,
|
|
3239
|
-
name,
|
|
3240
|
-
required,
|
|
3241
|
-
value: value ? `${value}` : defaultValue ? `${defaultValue}` : '',
|
|
3242
|
-
});
|
|
3478
|
+
const fieldContext = (isInFieldset ? fieldsetContext.fieldList : formContext)?.find(({ id: fieldID }) => fieldID === fieldContextID);
|
|
3243
3479
|
React.useEffect(() => {
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3480
|
+
const initialFieldContext = defineField({
|
|
3481
|
+
type: fieldContextType,
|
|
3482
|
+
id: fieldContextID,
|
|
3483
|
+
invalid,
|
|
3484
|
+
name,
|
|
3485
|
+
required,
|
|
3486
|
+
value: value ? `${value}` : defaultValue ? `${defaultValue}` : '',
|
|
3249
3487
|
});
|
|
3488
|
+
if (isInFieldset) {
|
|
3489
|
+
fieldsetContextFunctions.registerField(initialFieldContext);
|
|
3490
|
+
return () => {
|
|
3491
|
+
fieldsetContextFunctions.removeField(initialFieldContext.id);
|
|
3492
|
+
};
|
|
3493
|
+
}
|
|
3494
|
+
formContextFunctions.registerField(initialFieldContext);
|
|
3250
3495
|
return () => {
|
|
3251
|
-
|
|
3496
|
+
formContextFunctions.removeField(initialFieldContext.id);
|
|
3252
3497
|
};
|
|
3253
|
-
}, [
|
|
3254
|
-
const fieldContext = formContext?.find(({ id: fieldID }) => fieldID === initialFieldContext.id) || initialFieldContext;
|
|
3498
|
+
}, [isInFieldset]);
|
|
3255
3499
|
const validateField = (validValue) => {
|
|
3256
3500
|
const noValue = !validValue || validValue === '';
|
|
3257
3501
|
if (!required && noValue)
|
|
@@ -3267,15 +3511,86 @@ function Input({ checked, className, defaultValue, description, descriptionProps
|
|
|
3267
3511
|
if (!isEmail(validValue))
|
|
3268
3512
|
errorMessageList.push('This is not a valid email.');
|
|
3269
3513
|
break;
|
|
3514
|
+
case 'date':
|
|
3515
|
+
const valueAsTime = new Date().getTime();
|
|
3516
|
+
if (min && !(min instanceof Date) && typeof min !== 'number') {
|
|
3517
|
+
if (Array.isArray(min)) {
|
|
3518
|
+
const monthIndex = typeof min[1] === 'number' ? min[1] - 1 : getMonthIndexFromName(min[1]);
|
|
3519
|
+
min = new Date(min[0], monthIndex, min[2]);
|
|
3520
|
+
}
|
|
3521
|
+
else if ('year' in min && 'month' in min && 'day' in min) {
|
|
3522
|
+
const monthIndex = typeof min.month === 'number' ? min.month - 1 : getMonthIndexFromName(min.month);
|
|
3523
|
+
min = new Date(min.year, monthIndex, min.day);
|
|
3524
|
+
}
|
|
3525
|
+
if (valueAsTime < min.getTime())
|
|
3526
|
+
errorMessageList.push(`Value cannot be lower than ${getUserReadableDate(min)}.`);
|
|
3527
|
+
}
|
|
3528
|
+
if (max && !(max instanceof Date) && typeof max !== 'number') {
|
|
3529
|
+
if (Array.isArray(max)) {
|
|
3530
|
+
const monthIndex = typeof max[1] === 'number' ? max[1] - 1 : getMonthIndexFromName(max[1]);
|
|
3531
|
+
max = new Date(max[0], monthIndex, max[2]);
|
|
3532
|
+
}
|
|
3533
|
+
else if ('year' in max && 'month' in max && 'day' in max) {
|
|
3534
|
+
const monthIndex = typeof max.month === 'number' ? max.month - 1 : getMonthIndexFromName(max.month);
|
|
3535
|
+
max = new Date(max.year, monthIndex, max.day);
|
|
3536
|
+
}
|
|
3537
|
+
if (valueAsTime > max.getTime())
|
|
3538
|
+
errorMessageList.push(`Value cannot be higher than ${getUserReadableDate(max)}.`);
|
|
3539
|
+
}
|
|
3540
|
+
break;
|
|
3270
3541
|
case 'number':
|
|
3271
|
-
|
|
3542
|
+
const valueAsNumber = Number(validValue);
|
|
3543
|
+
if (isNaN(valueAsNumber))
|
|
3272
3544
|
errorMessageList.push('This is not a valid number.');
|
|
3545
|
+
if (typeof max === 'number' && valueAsNumber > max)
|
|
3546
|
+
errorMessageList.push(`Value cannot be higher than ${max}.`);
|
|
3547
|
+
if (typeof min === 'number' && valueAsNumber < min)
|
|
3548
|
+
errorMessageList.push(`Value cannot be lower than ${min}.`);
|
|
3549
|
+
break;
|
|
3550
|
+
case 'password':
|
|
3551
|
+
if (options) {
|
|
3552
|
+
const { matchPreviousInput, requireLowercaseCharacter, requireNumber, requireSpecialCharacter, requireUppercaseCharacter, } = options;
|
|
3553
|
+
if (matchPreviousInput && formContext && formContext.length >= 2) {
|
|
3554
|
+
if (isInFieldset && fieldsetContext.fieldList.length > 1) {
|
|
3555
|
+
const currentInputIndex = fieldsetContext.fieldList.findIndex(({ id: fieldID }) => fieldID === fieldContext?.id);
|
|
3556
|
+
if (currentInputIndex > 0) {
|
|
3557
|
+
const previousInput = fieldsetContext.fieldList.find((_, index) => index === currentInputIndex - 1);
|
|
3558
|
+
if (previousInput &&
|
|
3559
|
+
isStringField(previousInput) &&
|
|
3560
|
+
previousInput.value !== validValue)
|
|
3561
|
+
errorMessageList.push('Passwords must match.');
|
|
3562
|
+
}
|
|
3563
|
+
}
|
|
3564
|
+
else {
|
|
3565
|
+
const currentInputIndex = formContext.findIndex(({ id: fieldID }) => fieldID === fieldContext?.id);
|
|
3566
|
+
if (currentInputIndex > 0) {
|
|
3567
|
+
const previousInput = formContext.find((_, index) => index === currentInputIndex - 1);
|
|
3568
|
+
if (previousInput &&
|
|
3569
|
+
isStringField(previousInput) &&
|
|
3570
|
+
previousInput.value !== validValue)
|
|
3571
|
+
errorMessageList.push('Passwords must match.');
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3575
|
+
if (requireLowercaseCharacter && !/[a-z]/g.test(validValue))
|
|
3576
|
+
errorMessageList.push('You must include a lowercase character.');
|
|
3577
|
+
if (requireNumber && !/[0-9]/g.test(validValue))
|
|
3578
|
+
errorMessageList.push('You must include a number.');
|
|
3579
|
+
if (requireSpecialCharacter && !specialCharacterRegex.test(validValue))
|
|
3580
|
+
errorMessageList.push('You must include a special character.');
|
|
3581
|
+
if (requireUppercaseCharacter && !/[A-Z]/g.test(validValue))
|
|
3582
|
+
errorMessageList.push('You must include an uppercase character.');
|
|
3583
|
+
}
|
|
3273
3584
|
break;
|
|
3274
3585
|
case 'tel':
|
|
3275
3586
|
if (!isPhoneNumber(validValue))
|
|
3276
3587
|
errorMessageList.push('This is not a valid phone number.');
|
|
3277
3588
|
break;
|
|
3278
3589
|
}
|
|
3590
|
+
if (props.maxLength && validValue.length > Number(props.maxLength))
|
|
3591
|
+
errorMessageList.push(`This may not have more than ${props.maxLength} characters.`);
|
|
3592
|
+
if (props.minLength && validValue.length < Number(props.minLength))
|
|
3593
|
+
errorMessageList.push(`This must have at least ${props.minLength} characters.`);
|
|
3279
3594
|
if (errorMessageList.length === 0)
|
|
3280
3595
|
return true;
|
|
3281
3596
|
setErrorMessage(errorMessageList.join(' '));
|
|
@@ -3287,19 +3602,15 @@ function Input({ checked, className, defaultValue, description, descriptionProps
|
|
|
3287
3602
|
return;
|
|
3288
3603
|
}
|
|
3289
3604
|
const { currentTarget } = e, { value: newValue } = currentTarget;
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
if (invalidField !== field.invalid)
|
|
3300
|
-
updatedField.invalid = invalidField;
|
|
3301
|
-
return [...otherFields, updatedField];
|
|
3302
|
-
});
|
|
3605
|
+
if (isInFieldset) {
|
|
3606
|
+
fieldsetContextFunctions.updateField(fieldContextID, {
|
|
3607
|
+
value: newValue,
|
|
3608
|
+
invalid: validateField(newValue) === false,
|
|
3609
|
+
});
|
|
3610
|
+
}
|
|
3611
|
+
else {
|
|
3612
|
+
formContextFunctions.updateField(fieldContextID, { value: newValue, invalid: validateField(newValue) === false });
|
|
3613
|
+
}
|
|
3303
3614
|
onChange?.(e);
|
|
3304
3615
|
};
|
|
3305
3616
|
const handleBlur = e => {
|
|
@@ -3310,32 +3621,21 @@ function Input({ checked, className, defaultValue, description, descriptionProps
|
|
|
3310
3621
|
const { currentTarget } = e, { value: newValue } = currentTarget;
|
|
3311
3622
|
if (required)
|
|
3312
3623
|
validateField(newValue);
|
|
3624
|
+
let processedValue = newValue;
|
|
3313
3625
|
switch (type) {
|
|
3314
3626
|
case 'email':
|
|
3315
|
-
|
|
3316
|
-
if (!prevContext)
|
|
3317
|
-
return [];
|
|
3318
|
-
const field = prevContext.find(({ id: fieldID }) => fieldID === initialFieldContext.id);
|
|
3319
|
-
if (!field)
|
|
3320
|
-
throw new Error(`Field with id "${initialFieldContext.id}" not found in form context.`);
|
|
3321
|
-
const otherFields = prevContext.filter(({ id: fieldID }) => fieldID !== initialFieldContext.id);
|
|
3322
|
-
const updatedField = { ...field, value: newValue.toLowerCase() };
|
|
3323
|
-
return [...otherFields, updatedField];
|
|
3324
|
-
});
|
|
3627
|
+
processedValue = newValue.toLowerCase();
|
|
3325
3628
|
break;
|
|
3326
3629
|
case 'tel':
|
|
3327
|
-
|
|
3328
|
-
if (!prevContext)
|
|
3329
|
-
return [];
|
|
3330
|
-
const field = prevContext.find(({ id: fieldID }) => fieldID === initialFieldContext.id);
|
|
3331
|
-
if (!field)
|
|
3332
|
-
throw new Error(`Field with id "${initialFieldContext.id}" not found in form context.`);
|
|
3333
|
-
const otherFields = prevContext.filter(({ id: fieldID }) => fieldID !== initialFieldContext.id);
|
|
3334
|
-
const updatedField = { ...field, value: formatPhoneNumber(newValue, '1') };
|
|
3335
|
-
return [...otherFields, updatedField];
|
|
3336
|
-
});
|
|
3630
|
+
processedValue = formatPhoneNumber(newValue, options);
|
|
3337
3631
|
break;
|
|
3338
3632
|
}
|
|
3633
|
+
if (isInFieldset) {
|
|
3634
|
+
fieldsetContextFunctions.updateField(fieldContextID, { value: processedValue });
|
|
3635
|
+
}
|
|
3636
|
+
else {
|
|
3637
|
+
formContextFunctions.updateField(fieldContextID, { value: processedValue });
|
|
3638
|
+
}
|
|
3339
3639
|
onBlur?.(e);
|
|
3340
3640
|
};
|
|
3341
3641
|
const restFieldProps = fieldProps
|
|
@@ -3355,7 +3655,7 @@ function Input({ checked, className, defaultValue, description, descriptionProps
|
|
|
3355
3655
|
// user-invalid styles
|
|
3356
3656
|
'user-invalid:border-ui-red user-invalid:bg-[color-mix(in_oklab,var(--color-ui-red)_20%,var(--color-neutral-100))] user-invalid:focus-visible:bg-[color-mix(in_oklab,var(--color-ui-red)_1%,var(--color-neutral-100))] user-invalid:active:bg-[color-mix(in_oklab,var(--color-ui-red)_25%,var(--color-neutral-100))] dark:user-invalid:bg-[color-mix(in_oklab,var(--color-ui-red)_20%,var(--color-neutral-800))] dark:user-invalid:focus-visible:bg-[color-mix(in_oklab,var(--color-ui-red)_1%,var(--color-neutral-800))] dark:user-invalid:active:bg-[color-mix(in_oklab,var(--color-ui-red)_25%,var(--color-neutral-800))] user-invalid:pointer-fine:hover:bg-[color-mix(in_oklab,var(--color-ui-red)_10%,var(--color-neutral-100))] user-invalid:pointer-fine:focus-visible:bg-[color-mix(in_oklab,var(--color-ui-red)_1%,var(--color-neutral-100))] user-invalid:pointer-fine:active:bg-[color-mix(in_oklab,var(--color-ui-red)_25%,var(--color-neutral-100))] dark:user-invalid:pointer-fine:hover:bg-[color-mix(in_oklab,var(--color-ui-red)_10%,var(--color-neutral-800))] dark:user-invalid:pointer-fine:focus-visible:bg-[color-mix(in_oklab,var(--color-ui-red)_1%,var(--color-neutral-800))] dark:user-invalid:pointer-fine:active:bg-[color-mix(in_oklab,var(--color-ui-red)_25%,var(--color-neutral-800))]',
|
|
3357
3657
|
// Custom styles
|
|
3358
|
-
typeof className === 'function' ? className(bag) : className), invalid: invalid, onBlur: handleBlur, onChange: handleChange, placeholder: placeholder, ref: ref, required: required, type: type, value: fieldContext?.value }), fieldContext
|
|
3658
|
+
typeof className === 'function' ? className(bag) : className), invalid: invalid, onBlur: handleBlur, onChange: handleChange, placeholder: placeholder, ref: ref, required: required, type: type, value: fieldContext?.value || '' }), fieldContext?.invalid && errorMessage && (jsxRuntime.jsxs(Tooltip, { anchor: 'top-end', arrow: true, portal: true, children: [jsxRuntime.jsx(TooltipTrigger, { as: Button, className: 'absolute top-1.25 right-1.25 z-10 size-6 min-w-0', padding: 'none', rounded: 'md', theme: 'red', children: jsxRuntime.jsx(ExclamationmarkOctagon, { className: 'absolute top-1/2 left-1/2 size-full -translate-x-1/2 -translate-y-1/2 scale-70' }) }), jsxRuntime.jsx(TooltipPanel, { children: errorMessage })] }))] }), description && (jsxRuntime.jsx(react.Description, { ...restDescriptionProps, className: bag => twMerge('text-xs', typeof descriptionProps?.className === 'function'
|
|
3359
3659
|
? descriptionProps?.className(bag)
|
|
3360
3660
|
: descriptionProps?.className), children: description }))] }));
|
|
3361
3661
|
}
|
|
@@ -3414,34 +3714,37 @@ function SubmitButton({ children, className, customTheme, error, incomplete, loa
|
|
|
3414
3714
|
}
|
|
3415
3715
|
|
|
3416
3716
|
function Textarea({ className, defaultValue, description, descriptionProps, disabled, fieldProps, invalid = true, label, labelProps, name, onBlur, onChange, placeholder, ref, required = true, value, ...props }) {
|
|
3417
|
-
const [formContext,
|
|
3717
|
+
const [formContext, formContextFunctions] = useFormContext();
|
|
3718
|
+
const [fieldsetContext, fieldsetContextFunctions] = useFieldsetContext();
|
|
3719
|
+
const [errorMessage, setErrorMessage] = React.useState(undefined);
|
|
3418
3720
|
if (placeholder === '*')
|
|
3419
3721
|
placeholder = name + (required && !label ? '*' : '');
|
|
3420
3722
|
if (label === '*')
|
|
3421
3723
|
label = name;
|
|
3422
|
-
const uniqueID = React.useId()
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
const initialFieldContext = defineField({
|
|
3426
|
-
type: 'textarea',
|
|
3427
|
-
id: fieldContextID,
|
|
3428
|
-
invalid,
|
|
3429
|
-
name,
|
|
3430
|
-
required,
|
|
3431
|
-
value: value ? `${value}` : defaultValue ? `${defaultValue}` : '',
|
|
3432
|
-
});
|
|
3724
|
+
const uniqueID = React.useId();
|
|
3725
|
+
const fieldContextID = toLowerCase(name, [' ', '_']) + '§' + uniqueID;
|
|
3726
|
+
const isInFieldset = fieldsetContext && !fieldsetContext.decorative;
|
|
3433
3727
|
React.useEffect(() => {
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3728
|
+
const initialFieldContext = defineField({
|
|
3729
|
+
type: 'textarea',
|
|
3730
|
+
id: fieldContextID,
|
|
3731
|
+
invalid,
|
|
3732
|
+
name,
|
|
3733
|
+
required,
|
|
3734
|
+
value: value ? `${value}` : defaultValue ? `${defaultValue}` : '',
|
|
3439
3735
|
});
|
|
3736
|
+
if (isInFieldset) {
|
|
3737
|
+
fieldsetContextFunctions.registerField(initialFieldContext);
|
|
3738
|
+
return () => {
|
|
3739
|
+
fieldsetContextFunctions.removeField(initialFieldContext.id);
|
|
3740
|
+
};
|
|
3741
|
+
}
|
|
3742
|
+
formContextFunctions.registerField(initialFieldContext);
|
|
3440
3743
|
return () => {
|
|
3441
|
-
|
|
3744
|
+
formContextFunctions.removeField(initialFieldContext.id);
|
|
3442
3745
|
};
|
|
3443
|
-
}, [
|
|
3444
|
-
const fieldContext = formContext?.find(({ id: fieldID }) => fieldID ===
|
|
3746
|
+
}, [isInFieldset]);
|
|
3747
|
+
const fieldContext = (isInFieldset ? fieldsetContext.fieldList : formContext)?.find(({ id: fieldID }) => fieldID === fieldContextID);
|
|
3445
3748
|
const validateField = (validValue) => {
|
|
3446
3749
|
const noValue = !validValue || validValue === '';
|
|
3447
3750
|
if (!required && noValue)
|
|
@@ -3452,6 +3755,10 @@ function Textarea({ className, defaultValue, description, descriptionProps, disa
|
|
|
3452
3755
|
setErrorMessage(errorMessageList.join(' '));
|
|
3453
3756
|
return false;
|
|
3454
3757
|
}
|
|
3758
|
+
if (props.maxLength && validValue.length > Number(props.maxLength))
|
|
3759
|
+
errorMessageList.push(`This may not have more than ${props.maxLength} characters.`);
|
|
3760
|
+
if (props.minLength && validValue.length < Number(props.minLength))
|
|
3761
|
+
errorMessageList.push(`This must have at least ${props.minLength} characters.`);
|
|
3455
3762
|
if (errorMessageList.length === 0)
|
|
3456
3763
|
return true;
|
|
3457
3764
|
setErrorMessage(errorMessageList.join(' '));
|
|
@@ -3462,22 +3769,41 @@ function Textarea({ className, defaultValue, description, descriptionProps, disa
|
|
|
3462
3769
|
e.preventDefault();
|
|
3463
3770
|
return;
|
|
3464
3771
|
}
|
|
3465
|
-
const { currentTarget } = e
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
}
|
|
3772
|
+
const { currentTarget } = e;
|
|
3773
|
+
const { value: newValue } = currentTarget;
|
|
3774
|
+
if (isInFieldset) {
|
|
3775
|
+
fieldsetContextFunctions.updateField(fieldContextID, {
|
|
3776
|
+
value: newValue,
|
|
3777
|
+
invalid: validateField(newValue) === false,
|
|
3778
|
+
});
|
|
3779
|
+
}
|
|
3780
|
+
else {
|
|
3781
|
+
formContextFunctions.updateField(fieldContextID, {
|
|
3782
|
+
value: newValue,
|
|
3783
|
+
invalid: validateField(newValue) === false,
|
|
3784
|
+
});
|
|
3785
|
+
}
|
|
3479
3786
|
onChange?.(e);
|
|
3480
3787
|
};
|
|
3788
|
+
const handleBlur = e => {
|
|
3789
|
+
if (disabled) {
|
|
3790
|
+
e.preventDefault();
|
|
3791
|
+
return;
|
|
3792
|
+
}
|
|
3793
|
+
const { currentTarget } = e;
|
|
3794
|
+
const { value: newValue } = currentTarget;
|
|
3795
|
+
if (required)
|
|
3796
|
+
validateField(newValue);
|
|
3797
|
+
// No special processing needed for textarea like email/phone formatting
|
|
3798
|
+
const processedValue = newValue;
|
|
3799
|
+
if (isInFieldset) {
|
|
3800
|
+
fieldsetContextFunctions.updateField(fieldContextID, { value: processedValue });
|
|
3801
|
+
}
|
|
3802
|
+
else {
|
|
3803
|
+
formContextFunctions.updateField(fieldContextID, { value: processedValue });
|
|
3804
|
+
}
|
|
3805
|
+
onBlur?.(e);
|
|
3806
|
+
};
|
|
3481
3807
|
const restFieldProps = fieldProps
|
|
3482
3808
|
? Object.fromEntries(Object.entries(fieldProps).filter(([key]) => key !== 'className'))
|
|
3483
3809
|
: {};
|
|
@@ -3495,23 +3821,34 @@ function Textarea({ className, defaultValue, description, descriptionProps, disa
|
|
|
3495
3821
|
// user-invalid styles
|
|
3496
3822
|
'user-invalid:border-ui-red user-invalid:bg-[color-mix(in_oklab,var(--color-ui-red)_20%,var(--color-neutral-100))] user-invalid:focus-visible:bg-[color-mix(in_oklab,var(--color-ui-red)_1%,var(--color-neutral-100))] user-invalid:active:bg-[color-mix(in_oklab,var(--color-ui-red)_25%,var(--color-neutral-100))] dark:user-invalid:bg-[color-mix(in_oklab,var(--color-ui-red)_20%,var(--color-neutral-800))] dark:user-invalid:focus-visible:bg-[color-mix(in_oklab,var(--color-ui-red)_1%,var(--color-neutral-800))] dark:user-invalid:active:bg-[color-mix(in_oklab,var(--color-ui-red)_25%,var(--color-neutral-800))] user-invalid:pointer-fine:hover:bg-[color-mix(in_oklab,var(--color-ui-red)_10%,var(--color-neutral-100))] user-invalid:pointer-fine:focus-visible:bg-[color-mix(in_oklab,var(--color-ui-red)_1%,var(--color-neutral-100))] user-invalid:pointer-fine:active:bg-[color-mix(in_oklab,var(--color-ui-red)_25%,var(--color-neutral-100))] dark:user-invalid:pointer-fine:hover:bg-[color-mix(in_oklab,var(--color-ui-red)_10%,var(--color-neutral-800))] dark:user-invalid:pointer-fine:focus-visible:bg-[color-mix(in_oklab,var(--color-ui-red)_1%,var(--color-neutral-800))] dark:user-invalid:pointer-fine:active:bg-[color-mix(in_oklab,var(--color-ui-red)_25%,var(--color-neutral-800))]',
|
|
3497
3823
|
// Custom styles
|
|
3498
|
-
typeof className === 'function' ? className(bag) : className), id: fieldContext?.id, invalid: invalid, onChange: handleChange, placeholder: placeholder, ref: ref, required: required, value: fieldContext?.value }), fieldContext
|
|
3824
|
+
typeof className === 'function' ? className(bag) : className), id: fieldContext?.id, invalid: invalid, onChange: handleChange, onBlur: handleBlur, placeholder: placeholder, ref: ref, required: required, value: fieldContext?.value || '' }), fieldContext?.invalid && errorMessage && (jsxRuntime.jsxs(Tooltip, { anchor: 'top-end', arrow: true, portal: true, children: [jsxRuntime.jsx(TooltipTrigger, { as: Button, className: 'absolute top-1.25 right-1.25 z-10 size-6 min-w-0', padding: 'none', rounded: 'md', theme: 'red', children: jsxRuntime.jsx(ExclamationmarkOctagon, { className: 'absolute top-1/2 left-1/2 size-full -translate-x-1/2 -translate-y-1/2 scale-70' }) }), jsxRuntime.jsx(TooltipPanel, { children: errorMessage })] }))] }), description && (jsxRuntime.jsx(react.Description, { ...restDescriptionProps, className: bag => twMerge('text-xs', typeof descriptionProps?.className === 'function'
|
|
3499
3825
|
? descriptionProps?.className(bag)
|
|
3500
3826
|
: descriptionProps?.className), children: description }))] }));
|
|
3501
3827
|
}
|
|
3502
3828
|
|
|
3503
|
-
// import { findComponentByType } from '../../utils'
|
|
3504
3829
|
function FormComponent({ as, children, className, handleSubmit, onError, onSubmit, onSuccess, ...props }) {
|
|
3505
3830
|
const [formContext] = useFormContext(), [formStatus, setFormStatus] = useFormStatus();
|
|
3506
|
-
|
|
3831
|
+
const checkField = React.useCallback((field) => {
|
|
3832
|
+
if (field.type !== 'array' && field.type !== 'object' && !field.invalid)
|
|
3833
|
+
return true;
|
|
3834
|
+
if (field.type === 'object')
|
|
3835
|
+
return field.fields.every(objectField => checkField(objectField));
|
|
3836
|
+
return false;
|
|
3837
|
+
}, []);
|
|
3838
|
+
const everyFieldIsValid = React.useCallback(() => {
|
|
3839
|
+
if (!formContext)
|
|
3840
|
+
return false;
|
|
3841
|
+
return formContext.every(field => checkField(field));
|
|
3842
|
+
}, [formContext, checkField]);
|
|
3507
3843
|
React.useEffect(() => {
|
|
3508
3844
|
if (!formContext)
|
|
3509
3845
|
return;
|
|
3510
|
-
if (formStatus !== 'incomplete' &&
|
|
3846
|
+
if (formStatus !== 'incomplete' &&
|
|
3847
|
+
formContext.find(context => context.type !== 'array' && context.type !== 'object' && context.invalid))
|
|
3511
3848
|
setFormStatus?.('incomplete');
|
|
3512
|
-
if (formStatus !== 'ready' &&
|
|
3849
|
+
if (formStatus !== 'ready' && everyFieldIsValid())
|
|
3513
3850
|
setFormStatus?.('ready');
|
|
3514
|
-
}, [formContext]);
|
|
3851
|
+
}, [formContext, everyFieldIsValid]);
|
|
3515
3852
|
const processSubmit = handleSubmit ||
|
|
3516
3853
|
(async (e) => {
|
|
3517
3854
|
e.preventDefault();
|
|
@@ -3584,9 +3921,16 @@ function ModalTrigger({ as, ...props }) {
|
|
|
3584
3921
|
const Element = as || react.Button;
|
|
3585
3922
|
return jsxRuntime.jsx(Element, { ...props });
|
|
3586
3923
|
}
|
|
3924
|
+
function ModalTitle(props) {
|
|
3925
|
+
return jsxRuntime.jsx(react.DialogTitle, { ...props });
|
|
3926
|
+
}
|
|
3587
3927
|
function ModalDialog(props) {
|
|
3588
3928
|
return jsxRuntime.jsx("div", { ...props });
|
|
3589
3929
|
}
|
|
3930
|
+
function ModalClose({ as, ...props }) {
|
|
3931
|
+
const Element = as || react.Button;
|
|
3932
|
+
return jsxRuntime.jsx(Element, { ...props });
|
|
3933
|
+
}
|
|
3590
3934
|
function Modal({ children, className, onClose, onOpen, place = 'bottom' }) {
|
|
3591
3935
|
const [bodyElement, setBodyElement] = React.useState(null);
|
|
3592
3936
|
React.useEffect(() => {
|
|
@@ -3770,7 +4114,9 @@ exports.Heading = Heading;
|
|
|
3770
4114
|
exports.Input = Input;
|
|
3771
4115
|
exports.Link = Link;
|
|
3772
4116
|
exports.Modal = Modal;
|
|
4117
|
+
exports.ModalClose = ModalClose;
|
|
3773
4118
|
exports.ModalDialog = ModalDialog;
|
|
4119
|
+
exports.ModalTitle = ModalTitle;
|
|
3774
4120
|
exports.ModalTrigger = ModalTrigger;
|
|
3775
4121
|
exports.SubmitButton = SubmitButton;
|
|
3776
4122
|
exports.Textarea = Textarea;
|