@dynamic-framework/ui-react 2.6.0 → 2.8.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/css/dynamic-ui.css +278 -3
- package/dist/css/dynamic-ui.min.css +2 -2
- package/dist/index.esm.js +575 -284
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +577 -281
- package/dist/index.js.map +1 -1
- package/dist/types/components/DCarousel/DCarousel.d.ts +9 -2
- package/dist/types/components/DDropdown/DDropdown.d.ts +10 -2
- package/dist/types/components/DIcon/DIcon.d.ts +4 -4
- package/dist/types/components/DIconBase/DIconBase.d.ts +3 -2
- package/dist/types/components/DIconBase/index.d.ts +1 -0
- package/dist/types/components/DOffcanvas/DOffcanvas.d.ts +15 -3
- package/dist/types/components/DOtp/DOtp.d.ts +1 -0
- package/dist/types/components/DOtp/components/OtpCountdown.d.ts +3 -2
- package/dist/types/components/DVoucher/DVoucher.d.ts +3 -1
- package/dist/types/components/DVoucher/hooks/useScreenshotDownload.d.ts +1 -1
- package/dist/types/components/DVoucher/hooks/useScreenshotDownload.spec.d.ts +1 -0
- package/dist/types/components/DVoucher/hooks/useScreenshotWebShare.d.ts +1 -1
- package/dist/types/components/DVoucher/hooks/useScreenshotWebShare.spec.d.ts +1 -0
- package/dist/types/components/DVoucher/index.d.ts +3 -0
- package/dist/types/components/index.d.ts +1 -1
- package/dist/types/components/interface.d.ts +5 -3
- package/dist/types/contexts/DContext.d.ts +3 -2
- package/dist/types/hooks/index.d.ts +3 -0
- package/dist/types/hooks/tests/useCountdown.spec.d.ts +1 -0
- package/dist/types/hooks/useCountdown.d.ts +14 -0
- package/dist/types/hooks/useInputCurrency.d.ts +1 -6
- package/dist/types/hooks/useOtp.d.ts +98 -0
- package/package.json +3 -1
- package/src/style/abstracts/variables/_+import.scss +1 -0
- package/src/style/abstracts/variables/_input-currency.scss +1 -0
- package/src/style/abstracts/variables/_offcanvas.scss +8 -0
- package/src/style/components/_+import.scss +7 -0
- package/src/style/components/_d-box-file.scss +5 -0
- package/src/style/components/_d-carousel.scss +19 -1
- package/src/style/components/_d-close.scss +1 -0
- package/src/style/components/_d-confirm-modal.scss +17 -0
- package/src/style/components/_d-data-state-wrapper.scss +47 -0
- package/src/style/components/_d-datepicker.scss +11 -0
- package/src/style/components/_d-error-boundary.scss +7 -0
- package/src/style/components/_d-input-currency.scss +5 -0
- package/src/style/components/_d-otp.scss +54 -0
- package/src/style/components/_d-password-strength-meter.scss +76 -0
- package/src/style/components/_d-popover.scss +4 -0
- package/src/style/components/_d-stepper.scss +8 -0
- package/src/style/components/_d-tabs.scss +18 -0
- package/src/style/components/_d-voucher.scss +30 -0
- package/dist/types/components/DOtp/DOtpspec.d.ts +0 -0
- package/dist/types/components/DOtp/hooks/useCountdown.d.ts +0 -4
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ var classNames = require('classnames');
|
|
|
5
5
|
var React = require('react');
|
|
6
6
|
var tslib = require('tslib');
|
|
7
7
|
var LucideIcons = require('lucide-react');
|
|
8
|
+
var reactIs = require('react-is');
|
|
8
9
|
var reactDom = require('react-dom');
|
|
9
10
|
var framerMotion = require('framer-motion');
|
|
10
11
|
var fileSelector = require('file-selector');
|
|
@@ -329,6 +330,7 @@ const DEFAULT_STATE = {
|
|
|
329
330
|
familyPrefix: 'bi-',
|
|
330
331
|
materialStyle: false,
|
|
331
332
|
},
|
|
333
|
+
iconRegistry: undefined,
|
|
332
334
|
iconMap: {
|
|
333
335
|
x: 'X',
|
|
334
336
|
xLg: 'X',
|
|
@@ -394,11 +396,12 @@ const DContext = React.createContext(DEFAULT_STATE);
|
|
|
394
396
|
* @template T - Map of portal name → payload shape (e.g. `{ modal: { title: string } }`).
|
|
395
397
|
* Pass it once at the top level: `<DContextProvider<MyPortals> ...>`.
|
|
396
398
|
*/
|
|
397
|
-
function DContextProvider({ language = DEFAULT_STATE.language, currency = DEFAULT_STATE.currency, icon = DEFAULT_STATE.icon, iconMap = DEFAULT_STATE.iconMap, portalName = 'd-portal', availablePortals, children, }) {
|
|
399
|
+
function DContextProvider({ language = DEFAULT_STATE.language, currency = DEFAULT_STATE.currency, icon = DEFAULT_STATE.icon, iconRegistry = DEFAULT_STATE.iconRegistry, iconMap = DEFAULT_STATE.iconMap, portalName = 'd-portal', availablePortals, children, }) {
|
|
398
400
|
const [internalContext, setInternalContext,] = React.useState({
|
|
399
401
|
language,
|
|
400
402
|
currency,
|
|
401
403
|
icon,
|
|
404
|
+
iconRegistry,
|
|
402
405
|
iconMap,
|
|
403
406
|
breakpoints: DEFAULT_STATE.breakpoints,
|
|
404
407
|
});
|
|
@@ -515,17 +518,21 @@ function useResponsiveProp(useListener = false) {
|
|
|
515
518
|
return { responsivePropValue };
|
|
516
519
|
}
|
|
517
520
|
|
|
521
|
+
function isIconComponent(value) {
|
|
522
|
+
return typeof value !== 'string' && reactIs.isValidElementType(value);
|
|
523
|
+
}
|
|
518
524
|
function DIconBase({ icon, color, style, className, size, useListenerSize = false, hasCircle = false, materialStyle = false, familyClass, familyPrefix, strokeWidth = 2, dataAttributes, }) {
|
|
519
525
|
// If materialStyle is true, use Material Design icons (legacy)
|
|
520
|
-
const
|
|
526
|
+
const isStringIcon = typeof icon === 'string';
|
|
527
|
+
const useMaterialIcons = materialStyle && isStringIcon;
|
|
521
528
|
// Get Lucide icon component
|
|
522
529
|
const LucideIcon = React.useMemo(() => {
|
|
523
|
-
if (useMaterialIcons)
|
|
530
|
+
if (!isStringIcon || useMaterialIcons)
|
|
524
531
|
return null;
|
|
525
532
|
// Try to find the icon in Lucide (expects PascalCase)
|
|
526
533
|
const icons = LucideIcons__namespace;
|
|
527
534
|
return icons[icon] || null;
|
|
528
|
-
}, [icon, useMaterialIcons]);
|
|
535
|
+
}, [icon, isStringIcon, useMaterialIcons]);
|
|
529
536
|
const { responsivePropValue } = useResponsiveProp(useListenerSize);
|
|
530
537
|
const resolvedSize = React.useMemo(() => {
|
|
531
538
|
if (!size)
|
|
@@ -545,24 +552,35 @@ function DIconBase({ icon, color, style, className, size, useListenerSize = fals
|
|
|
545
552
|
}, [resolvedSize]);
|
|
546
553
|
// Render Material Design icon (legacy support)
|
|
547
554
|
if (useMaterialIcons) {
|
|
548
|
-
return (jsxRuntime.jsx("i", Object.assign({ className: classNames(generateClasses, familyClass), style: generateStyleVariables }, dataAttributes, { children: icon })));
|
|
555
|
+
return (jsxRuntime.jsx("i", Object.assign({ className: classNames(generateClasses, familyClass), style: generateStyleVariables }, dataAttributes, { children: isStringIcon ? icon : null })));
|
|
556
|
+
}
|
|
557
|
+
if (isIconComponent(icon)) {
|
|
558
|
+
return (jsxRuntime.jsx("span", Object.assign({ className: classNames(generateClasses), style: generateStyleVariables }, dataAttributes, { children: React.createElement(icon, {
|
|
559
|
+
width: resolvedSize || 24,
|
|
560
|
+
height: resolvedSize || 24,
|
|
561
|
+
strokeWidth,
|
|
562
|
+
}) })));
|
|
549
563
|
}
|
|
550
564
|
// Render Lucide icon
|
|
551
565
|
if (!LucideIcon) {
|
|
552
|
-
if (familyClass && familyPrefix) {
|
|
566
|
+
if (isStringIcon && familyClass && familyPrefix) {
|
|
553
567
|
return (jsxRuntime.jsx("i", Object.assign({ className: classNames(generateClasses, familyClass, `${familyPrefix}${icon}`), style: generateStyleVariables }, dataAttributes)));
|
|
554
568
|
}
|
|
555
569
|
// eslint-disable-next-line no-console
|
|
556
|
-
console.warn(`Icon "${icon}" not found in Lucide. Make sure to use PascalCase names (e.g., "Home", "User", "Settings")`);
|
|
570
|
+
console.warn(`Icon "${String(icon)}" not found in Lucide. Make sure to use PascalCase names (e.g., "Home", "User", "Settings")`);
|
|
557
571
|
return (jsxRuntime.jsx("span", Object.assign({ className: classNames(generateClasses), style: generateStyleVariables }, dataAttributes, { children: "?" })));
|
|
558
572
|
}
|
|
559
573
|
return (jsxRuntime.jsx("span", Object.assign({ className: classNames(generateClasses), style: generateStyleVariables }, dataAttributes, { children: jsxRuntime.jsx(LucideIcon, { size: iconSize || 24, strokeWidth: strokeWidth }) })));
|
|
560
574
|
}
|
|
561
575
|
|
|
562
576
|
function DIcon(_a) {
|
|
563
|
-
var { familyClass: propFamilyClass, familyPrefix: propFamilyPrefix, materialStyle: propMaterialStyle } = _a, props = tslib.__rest(_a, ["familyClass", "familyPrefix", "materialStyle"]);
|
|
564
|
-
const { icon: { familyClass, familyPrefix, materialStyle, }, } = useDContext();
|
|
565
|
-
|
|
577
|
+
var { icon, familyClass: propFamilyClass, familyPrefix: propFamilyPrefix, materialStyle: propMaterialStyle } = _a, props = tslib.__rest(_a, ["icon", "familyClass", "familyPrefix", "materialStyle"]);
|
|
578
|
+
const { icon: { familyClass, familyPrefix, materialStyle, }, iconRegistry, } = useDContext();
|
|
579
|
+
const registryIcon = typeof icon === 'string'
|
|
580
|
+
? iconRegistry === null || iconRegistry === void 0 ? void 0 : iconRegistry[icon]
|
|
581
|
+
: undefined;
|
|
582
|
+
const resolvedIcon = registryIcon || icon;
|
|
583
|
+
return (jsxRuntime.jsx(DIconBase, Object.assign({ icon: resolvedIcon, familyClass: propFamilyClass !== null && propFamilyClass !== void 0 ? propFamilyClass : familyClass, familyPrefix: propFamilyPrefix !== null && propFamilyPrefix !== void 0 ? propFamilyPrefix : familyPrefix, materialStyle: propMaterialStyle !== null && propMaterialStyle !== void 0 ? propMaterialStyle : materialStyle }, props)));
|
|
566
584
|
}
|
|
567
585
|
|
|
568
586
|
function DAlert({ color = 'success', icon: iconProp, iconFamilyClass, iconFamilyPrefix, iconMaterialStyle = false, iconClose: iconCloseProp, iconCloseFamilyClass, iconCloseFamilyPrefix, iconCloseMaterialStyle = false, showClose, onClose, children, id, className, style, dataAttributes, }) {
|
|
@@ -1108,7 +1126,7 @@ function DBoxFile(_a) {
|
|
|
1108
1126
|
'd-box-file-invalid': isDragInvalid,
|
|
1109
1127
|
}, className), style: style }, dataAttributes, { children: jsxRuntime.jsxs("div", Object.assign({ className: "d-box-file-dropzone", ref: rootRef, onDragEnter: handleDragEnter, onDragOver: (e) => e.preventDefault(), onDragLeave: handleDragLeave, onDrop: handleDrop, onClick: handleClick, onKeyDown: handleKeyDown }, (!props.disabled && !props.noKeyboard ? { tabIndex: 0 } : {}), { role: "presentation", children: [jsxRuntime.jsx("input", { type: "file", multiple: props.multiple, style: { display: 'none' }, ref: inputRef, disabled: props.disabled, onChange: handleFileSelect, onClick: (e) => e.stopPropagation(), tabIndex: -1, accept: acceptAttr }), icon && iconProp !== false && (jsxRuntime.jsx(DIcon, { icon: icon, familyClass: iconFamilyClass, familyPrefix: iconFamilyPrefix, materialStyle: iconMaterialStyle })), jsxRuntime.jsx("div", { className: "d-box-content", children: typeof children === 'function'
|
|
1110
1128
|
? children(openFileDialog)
|
|
1111
|
-
: children || (jsxRuntime.jsx("p", { className: "
|
|
1129
|
+
: children || (jsxRuntime.jsx("p", { className: "d-box-file-empty-text", children: "Drag and drop some files here, or click to select files" })) })] })) })), !!files.length && (jsxRuntime.jsx("ul", { className: "d-box-files", children: files.map((file, index) => (jsxRuntime.jsx(ForwardedDInput, { value: file.name, iconStart: "Paperclip", iconEnd: "Trash", readOnly: true, onIconEndClick: () => handleRemoveFile(index) }, `${file.name} ${index}`))) }))] }));
|
|
1112
1130
|
}
|
|
1113
1131
|
|
|
1114
1132
|
const DButton = React.forwardRef((props, ref) => {
|
|
@@ -1242,8 +1260,14 @@ function DCarouselSlide(_a) {
|
|
|
1242
1260
|
return (jsxRuntime.jsx(reactSplide.SplideSlide, Object.assign({ className: classNames('d-carousel-slide', className) }, props)));
|
|
1243
1261
|
}
|
|
1244
1262
|
|
|
1263
|
+
const DEFAULT_ARROW_PATH = 'm15.5 0.932-4.3 4.38 14.5 14.6-14.5 14.5 4.3 4.4 14.6-14.6 4.4-4.3-4.4-4.4-14.6-14.6z';
|
|
1264
|
+
function DefaultArrowIcon() {
|
|
1265
|
+
return (jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 40 40", width: 40, height: 40, focusable: "false", "aria-hidden": "true", children: jsxRuntime.jsx("path", { d: DEFAULT_ARROW_PATH }) }));
|
|
1266
|
+
}
|
|
1245
1267
|
function DCarousel$1(_a, ref) {
|
|
1246
|
-
var { children, className, style, options, dataAttributes } = _a, props = tslib.__rest(_a, ["children", "className", "style", "options", "dataAttributes"]);
|
|
1268
|
+
var { children, className, style, options, dataAttributes, iconArrowLeft, iconArrowRight, hasTrack: propsHasTrack } = _a, props = tslib.__rest(_a, ["children", "className", "style", "options", "dataAttributes", "iconArrowLeft", "iconArrowRight", "hasTrack"]);
|
|
1269
|
+
// Explicit `options.arrows === false` always wins, even when icon props are set.
|
|
1270
|
+
const hasCustomArrows = Boolean((iconArrowLeft || iconArrowRight) && (options === null || options === void 0 ? void 0 : options.arrows) !== false);
|
|
1247
1271
|
return (jsxRuntime.jsx(reactSplide.Splide, Object.assign({ className: classNames('d-carousel', className), style: style, ref: ref, options: Object.assign(Object.assign({}, options), { classes: {
|
|
1248
1272
|
// Arrows
|
|
1249
1273
|
arrows: 'splide__arrows d-carousel-arrows',
|
|
@@ -1253,7 +1277,11 @@ function DCarousel$1(_a, ref) {
|
|
|
1253
1277
|
// Paginator
|
|
1254
1278
|
pagination: 'splide__pagination d-carousel-pagination',
|
|
1255
1279
|
page: 'splide__pagination__page d-carousel-pagination-page',
|
|
1256
|
-
} }) }, dataAttributes, props, {
|
|
1280
|
+
} }) }, dataAttributes, props, {
|
|
1281
|
+
// Rendering our own arrows requires taking over the track wrapper, so this
|
|
1282
|
+
// must be applied after `...props` to prevent a consumer-provided `hasTrack`
|
|
1283
|
+
// from re-enabling Splide's automatic track when custom arrows are active.
|
|
1284
|
+
hasTrack: hasCustomArrows ? false : propsHasTrack, children: hasCustomArrows ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(reactSplide.SplideTrack, { children: children }), jsxRuntime.jsxs("div", { className: "splide__arrows d-carousel-arrows", children: [jsxRuntime.jsx("button", { type: "button", className: "splide__arrow splide__arrow--prev d-carousel-arrow d-carousel-arrow-prev", children: iconArrowLeft ? jsxRuntime.jsx(DIcon, Object.assign({}, iconArrowLeft)) : jsxRuntime.jsx(DefaultArrowIcon, {}) }), jsxRuntime.jsx("button", { type: "button", className: "splide__arrow splide__arrow--next d-carousel-arrow d-carousel-arrow-next", children: iconArrowRight ? jsxRuntime.jsx(DIcon, Object.assign({}, iconArrowRight)) : jsxRuntime.jsx(DefaultArrowIcon, {}) })] })] })) : children })));
|
|
1257
1285
|
}
|
|
1258
1286
|
const ForwardedDCarousel = React.forwardRef(DCarousel$1);
|
|
1259
1287
|
ForwardedDCarousel.displayName = 'DCarousel';
|
|
@@ -1474,9 +1502,9 @@ function DDatePickerHeaderSelector({ date, changeYear, changeMonth, decreaseMont
|
|
|
1474
1502
|
return (jsxRuntime.jsxs("div", { className: classNames('react-datepicker__header-selector react-datepicker__header-year-selector', className), style: style, children: [jsxRuntime.jsx(DButtonIcon, { icon: iconPrev || chevronLeft, size: iconSize, variant: "link", onClick: decreaseYear, disabled: prevYearButtonDisabled, "aria-label": prevYearAriaLabel, className: "header-button" }), jsxRuntime.jsx("p", { children: `${startYear} - ${endYear}` }), jsxRuntime.jsx(DButtonIcon, { icon: iconNext || chevronRight, size: iconSize, variant: "link", onClick: increaseYear, disabled: nextYearButtonDisabled, "aria-label": nextYearAriaLabel, className: "header-button" })] }));
|
|
1475
1503
|
}
|
|
1476
1504
|
if (pickerType === PickerType.Quarter || pickerType === PickerType.Month) {
|
|
1477
|
-
return (jsxRuntime.jsxs("div", { className: classNames(`react-datepicker__header-selector react-datepicker__header-${pickerType}-selector`, className), style: style, children: [jsxRuntime.jsx(DButtonIcon, { icon: iconPrev || chevronLeft, size: iconSize, variant: "link", onClick: decreaseYear, disabled: prevMonthButtonDisabled, "aria-label": prevMonthAriaLabel, className: "header-button", style: { visibility: customHeaderCount === 0 ? 'visible' : 'hidden' } }), jsxRuntime.jsx("div", { className: "
|
|
1505
|
+
return (jsxRuntime.jsxs("div", { className: classNames(`react-datepicker__header-selector react-datepicker__header-${pickerType}-selector`, className), style: style, children: [jsxRuntime.jsx(DButtonIcon, { icon: iconPrev || chevronLeft, size: iconSize, variant: "link", onClick: decreaseYear, disabled: prevMonthButtonDisabled, "aria-label": prevMonthAriaLabel, className: "header-button", style: { visibility: customHeaderCount === 0 ? 'visible' : 'hidden' } }), jsxRuntime.jsx("div", { className: "react-datepicker__header-year-selector", children: showHeaderSelectors ? (jsxRuntime.jsx(DSelect, { options: years, value: defaultYear, defaultValue: defaultYear, onChange: (year) => changeYear(Number(year === null || year === void 0 ? void 0 : year.value)), searchable: false })) : (jsxRuntime.jsx("p", { children: defaultYear === null || defaultYear === void 0 ? void 0 : defaultYear.label })) }), jsxRuntime.jsx(DButtonIcon, { icon: iconNext || chevronRight, size: iconSize, variant: "link", onClick: increaseYear, disabled: nextMonthButtonDisabled, "aria-label": nextMonthAriaLabel, className: "header-button", style: { visibility: customHeaderCount === monthsShown - 1 ? 'visible' : 'hidden' } })] }));
|
|
1478
1506
|
}
|
|
1479
|
-
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: "datepicker-top-header", children: [showHeaderSelectors && (jsxRuntime.jsx(DSelect, { options: years, value: defaultYear, defaultValue: defaultYear, onChange: (year) => changeYear(Number(year === null || year === void 0 ? void 0 : year.value)), searchable: false, className: "custom-year-selector" })), jsxRuntime.jsx("h4", { className: "
|
|
1507
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: "datepicker-top-header", children: [showHeaderSelectors && (jsxRuntime.jsx(DSelect, { options: years, value: defaultYear, defaultValue: defaultYear, onChange: (year) => changeYear(Number(year === null || year === void 0 ? void 0 : year.value)), searchable: false, className: "custom-year-selector" })), jsxRuntime.jsx("h4", { className: "datepicker-top-header-title", children: dateFns.format(monthDate, formatHeaderDate, { locale }) })] }), jsxRuntime.jsxs("div", { className: classNames('react-datepicker__header-selector react-datepicker__header-day-selector', className), style: style, children: [jsxRuntime.jsx(DButtonIcon, { icon: iconPrev || chevronLeft, size: iconSize, variant: "link", onClick: decreaseMonth, disabled: prevMonthButtonDisabled, "aria-label": prevMonthAriaLabel, className: "header-button", style: { visibility: customHeaderCount === 0 ? 'visible' : 'hidden' } }), showHeaderSelectors ? (jsxRuntime.jsx(DSelect, { options: months, value: defaultMonth, defaultValue: defaultMonth, onChange: (month) => changeMonth((month === null || month === void 0 ? void 0 : month.value) || 0), searchable: false, className: "custom-month-selector" })) : (jsxRuntime.jsx("p", { children: `${defaultMonth.label} ${defaultYear === null || defaultYear === void 0 ? void 0 : defaultYear.label}` })), jsxRuntime.jsx(DButtonIcon, { icon: iconNext || chevronRight, size: iconSize, variant: "link", onClick: increaseMonth, disabled: nextMonthButtonDisabled, "aria-label": nextMonthAriaLabel, className: "header-button", style: { visibility: customHeaderCount === monthsShown - 1 ? 'visible' : 'hidden' } })] })] }));
|
|
1480
1508
|
}
|
|
1481
1509
|
|
|
1482
1510
|
function DDatePicker(_a) {
|
|
@@ -1611,11 +1639,24 @@ function formatValue(value, currencyOptions) {
|
|
|
1611
1639
|
}
|
|
1612
1640
|
return currency(value, Object.assign(Object.assign({}, currencyOptions), { symbol: '' })).format();
|
|
1613
1641
|
}
|
|
1614
|
-
function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref) {
|
|
1642
|
+
function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref, minValue, maxValue) {
|
|
1615
1643
|
const inputRef = useProvidedRefOrCreate(ref);
|
|
1644
|
+
const clampValue = React.useCallback((newValue) => {
|
|
1645
|
+
if (newValue === undefined) {
|
|
1646
|
+
return newValue;
|
|
1647
|
+
}
|
|
1648
|
+
let clampedValue = newValue;
|
|
1649
|
+
if (minValue !== undefined) {
|
|
1650
|
+
clampedValue = Math.max(clampedValue, minValue);
|
|
1651
|
+
}
|
|
1652
|
+
if (maxValue !== undefined) {
|
|
1653
|
+
clampedValue = Math.min(clampedValue, maxValue);
|
|
1654
|
+
}
|
|
1655
|
+
return clampedValue;
|
|
1656
|
+
}, [minValue, maxValue]);
|
|
1616
1657
|
const [innerType, setInnerType] = React.useState('text');
|
|
1617
|
-
const [innerNumber, setInnerNumber] = React.useState(value);
|
|
1618
|
-
const [innerString, setInnerString] = React.useState(formatValue(value, currencyOptions));
|
|
1658
|
+
const [innerNumber, setInnerNumber] = React.useState(clampValue(value));
|
|
1659
|
+
const [innerString, setInnerString] = React.useState(formatValue(clampValue(value), currencyOptions));
|
|
1619
1660
|
const handleOnFocus = React.useCallback((event) => {
|
|
1620
1661
|
event.stopPropagation();
|
|
1621
1662
|
setInnerType('number');
|
|
@@ -1624,15 +1665,14 @@ function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref
|
|
|
1624
1665
|
const handleOnBlur = React.useCallback((event) => {
|
|
1625
1666
|
event.stopPropagation();
|
|
1626
1667
|
setInnerType('text');
|
|
1668
|
+
const clampedNumber = clampValue(innerNumber);
|
|
1669
|
+
if (clampedNumber !== innerNumber) {
|
|
1670
|
+
setInnerNumber(clampedNumber);
|
|
1671
|
+
setInnerString(formatValue(clampedNumber, currencyOptions));
|
|
1672
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(clampedNumber);
|
|
1673
|
+
}
|
|
1627
1674
|
onBlur === null || onBlur === void 0 ? void 0 : onBlur(event);
|
|
1628
|
-
}, [onBlur]);
|
|
1629
|
-
const generateStyleVariables = React.useMemo(() => ({
|
|
1630
|
-
[`--${PREFIX_BS}input-currency-component-symbol-color`]: `var(--${PREFIX_BS}secondary)`,
|
|
1631
|
-
[`--${PREFIX_BS}input-currency-symbol-color`]: `var(--${PREFIX_BS}input-currency-component-symbol-color)`,
|
|
1632
|
-
}), []);
|
|
1633
|
-
const generateSymbolStyleVariables = React.useMemo(() => ({
|
|
1634
|
-
color: `var(--${PREFIX_BS}input-currency-symbol-color)`,
|
|
1635
|
-
}), []);
|
|
1675
|
+
}, [onBlur, innerNumber, clampValue, currencyOptions, onChange]);
|
|
1636
1676
|
const handleOnChange = React.useCallback((newValue) => {
|
|
1637
1677
|
const newNumber = (newValue === undefined || newValue === '') ? undefined : Number(newValue);
|
|
1638
1678
|
if (newNumber !== innerNumber) {
|
|
@@ -1641,7 +1681,12 @@ function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref
|
|
|
1641
1681
|
onChange === null || onChange === void 0 ? void 0 : onChange(newNumber);
|
|
1642
1682
|
}
|
|
1643
1683
|
}, [currencyOptions, onChange, innerNumber]);
|
|
1684
|
+
const isMountedRef = React.useRef(false);
|
|
1644
1685
|
React.useEffect(() => {
|
|
1686
|
+
if (!isMountedRef.current) {
|
|
1687
|
+
isMountedRef.current = true;
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1645
1690
|
if (value !== innerNumber) {
|
|
1646
1691
|
setInnerNumber(value);
|
|
1647
1692
|
setInnerString(formatValue(value, currencyOptions));
|
|
@@ -1655,8 +1700,6 @@ function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref
|
|
|
1655
1700
|
handleOnFocus,
|
|
1656
1701
|
handleOnChange,
|
|
1657
1702
|
handleOnBlur,
|
|
1658
|
-
generateStyleVariables,
|
|
1659
|
-
generateSymbolStyleVariables,
|
|
1660
1703
|
};
|
|
1661
1704
|
}
|
|
1662
1705
|
|
|
@@ -1761,6 +1804,149 @@ function useConfirmModal(config) {
|
|
|
1761
1804
|
return { open };
|
|
1762
1805
|
}
|
|
1763
1806
|
|
|
1807
|
+
/**
|
|
1808
|
+
* Generic seconds-based countdown hook.
|
|
1809
|
+
*
|
|
1810
|
+
* Starts counting down from `seconds` to `0` and exposes `restartCountdown`
|
|
1811
|
+
* to reset and restart the timer (e.g. for "resend code in Xs" patterns,
|
|
1812
|
+
* session-expiry banners, or any other cooldown/throttle UI).
|
|
1813
|
+
*
|
|
1814
|
+
* @example
|
|
1815
|
+
* const { secondsLeft, restartCountdown } = useCountdown(30);
|
|
1816
|
+
*/
|
|
1817
|
+
function useCountdown(seconds) {
|
|
1818
|
+
const [secondsLeft, setSecondsLeft] = React.useState(seconds);
|
|
1819
|
+
const [isActive, setIsActive] = React.useState(true);
|
|
1820
|
+
const resetCountdown = React.useCallback((newSeconds = seconds) => {
|
|
1821
|
+
setIsActive(false);
|
|
1822
|
+
setSecondsLeft(newSeconds);
|
|
1823
|
+
}, [seconds]);
|
|
1824
|
+
const restartCountdown = React.useCallback(() => {
|
|
1825
|
+
resetCountdown(seconds);
|
|
1826
|
+
setIsActive(true);
|
|
1827
|
+
}, [resetCountdown, seconds]);
|
|
1828
|
+
React.useEffect(() => {
|
|
1829
|
+
if (!isActive) {
|
|
1830
|
+
return () => { };
|
|
1831
|
+
}
|
|
1832
|
+
const interval = setInterval(() => {
|
|
1833
|
+
setSecondsLeft((prevSeconds) => {
|
|
1834
|
+
const newSeconds = prevSeconds - 1;
|
|
1835
|
+
if (newSeconds <= 0) {
|
|
1836
|
+
clearInterval(interval);
|
|
1837
|
+
setIsActive(false);
|
|
1838
|
+
return 0;
|
|
1839
|
+
}
|
|
1840
|
+
return newSeconds;
|
|
1841
|
+
});
|
|
1842
|
+
}, 1000);
|
|
1843
|
+
return () => clearInterval(interval);
|
|
1844
|
+
}, [isActive]);
|
|
1845
|
+
return { secondsLeft, restartCountdown };
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
/**
|
|
1849
|
+
* Headless hook exposing the same OTP logic used internally by `DOtp`
|
|
1850
|
+
* (code state, length + correctness validation, submit action and resend
|
|
1851
|
+
* countdown), without any bundled markup/styles.
|
|
1852
|
+
*
|
|
1853
|
+
* Use this when the default `DOtp` component doesn't match your design and
|
|
1854
|
+
* you need to build a fully custom UI while reusing the same behavior.
|
|
1855
|
+
*
|
|
1856
|
+
* Internally composes `useCountdown` for the resend timer and re-exposes
|
|
1857
|
+
* `secondsLeft`/`restartCountdown`, so there is no need to call
|
|
1858
|
+
* `useCountdown` separately.
|
|
1859
|
+
*
|
|
1860
|
+
* `invalid` becomes `true` both when the OTP is shorter than `otpSize` and
|
|
1861
|
+
* when `action` throws/rejects (e.g. the backend rejected the entered code
|
|
1862
|
+
* as incorrect). In the latter case, the thrown value is exposed as `error`
|
|
1863
|
+
* so custom UIs can render an appropriate message (e.g. "Invalid code" vs.
|
|
1864
|
+
* "Enter all digits"). `invalid`/`error` are cleared automatically as soon
|
|
1865
|
+
* as the user edits the code via `setOtp`.
|
|
1866
|
+
*
|
|
1867
|
+
* @example
|
|
1868
|
+
* function CustomOtp() {
|
|
1869
|
+
* const {
|
|
1870
|
+
* otp,
|
|
1871
|
+
* setOtp,
|
|
1872
|
+
* invalid,
|
|
1873
|
+
* error,
|
|
1874
|
+
* submit,
|
|
1875
|
+
* isLoading,
|
|
1876
|
+
* secondsLeft,
|
|
1877
|
+
* restartCountdown,
|
|
1878
|
+
* } = useOtp({ action: async () => verifyOtp(otp), otpSize: 6, seconds: 15 });
|
|
1879
|
+
*
|
|
1880
|
+
* return (
|
|
1881
|
+
* <>
|
|
1882
|
+
* <MyCustomPinInput value={otp} onChange={setOtp} invalid={invalid} />
|
|
1883
|
+
* {invalid && <span>{error ? 'Invalid code' : 'Enter all digits'}</span>}
|
|
1884
|
+
* <button disabled={isLoading} onClick={() => { submit().catch(() => {}); }}>
|
|
1885
|
+
* {isLoading ? 'Submitting...' : 'Submit'}
|
|
1886
|
+
* </button>
|
|
1887
|
+
* <button disabled={secondsLeft > 0} onClick={restartCountdown}>
|
|
1888
|
+
* Resend {secondsLeft > 0 ? `(${secondsLeft}s)` : ''}
|
|
1889
|
+
* </button>
|
|
1890
|
+
* </>
|
|
1891
|
+
* );
|
|
1892
|
+
* }
|
|
1893
|
+
*/
|
|
1894
|
+
function useOtp({ action, otpSize = 6, seconds = 15, }) {
|
|
1895
|
+
const [otp, setOtpValue] = React.useState('');
|
|
1896
|
+
const [invalid, setInvalid] = React.useState(false);
|
|
1897
|
+
const [error, setError] = React.useState(null);
|
|
1898
|
+
const [isLoading, setIsLoading] = React.useState(false);
|
|
1899
|
+
const { secondsLeft, restartCountdown } = useCountdown(seconds);
|
|
1900
|
+
const setOtp = React.useCallback((value) => {
|
|
1901
|
+
setOtpValue((prevOtp) => {
|
|
1902
|
+
// Guard against no-op calls (e.g. DInputPin re-notifying the same
|
|
1903
|
+
// value when its onChange reference changes) so we don't wipe out
|
|
1904
|
+
// an `invalid`/`error` state that was just set by submit().
|
|
1905
|
+
if (prevOtp === value) {
|
|
1906
|
+
return prevOtp;
|
|
1907
|
+
}
|
|
1908
|
+
setInvalid(false);
|
|
1909
|
+
setError(null);
|
|
1910
|
+
return value;
|
|
1911
|
+
});
|
|
1912
|
+
}, []);
|
|
1913
|
+
const submit = React.useCallback(async () => {
|
|
1914
|
+
if (otp.length < otpSize) {
|
|
1915
|
+
setInvalid(true);
|
|
1916
|
+
setError(null);
|
|
1917
|
+
return;
|
|
1918
|
+
}
|
|
1919
|
+
setIsLoading(true);
|
|
1920
|
+
try {
|
|
1921
|
+
await action();
|
|
1922
|
+
setInvalid(false);
|
|
1923
|
+
setError(null);
|
|
1924
|
+
}
|
|
1925
|
+
catch (err) {
|
|
1926
|
+
setInvalid(true);
|
|
1927
|
+
setError(err);
|
|
1928
|
+
throw err;
|
|
1929
|
+
}
|
|
1930
|
+
finally {
|
|
1931
|
+
setIsLoading(false);
|
|
1932
|
+
}
|
|
1933
|
+
}, [
|
|
1934
|
+
otp.length,
|
|
1935
|
+
action,
|
|
1936
|
+
otpSize,
|
|
1937
|
+
]);
|
|
1938
|
+
return {
|
|
1939
|
+
otp,
|
|
1940
|
+
setOtp,
|
|
1941
|
+
invalid,
|
|
1942
|
+
error,
|
|
1943
|
+
submit,
|
|
1944
|
+
isLoading,
|
|
1945
|
+
secondsLeft,
|
|
1946
|
+
restartCountdown,
|
|
1947
|
+
};
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1764
1950
|
function DInputCounter(_a, ref) {
|
|
1765
1951
|
var { minValue, maxValue, value = minValue, invalid, iconStart: iconStartProp, iconEnd: iconEndProp, iconStartAriaLabel = 'decrease action', iconEndAriaLabel = 'increase action', style, onChange } = _a, props = tslib.__rest(_a, ["minValue", "maxValue", "value", "invalid", "iconStart", "iconEnd", "iconStartAriaLabel", "iconEndAriaLabel", "style", "onChange"]);
|
|
1766
1952
|
const { handleOnWheel, } = useDisableInputWheel(ref);
|
|
@@ -1803,8 +1989,8 @@ function DInputCurrency(_a, ref) {
|
|
|
1803
1989
|
var { value, minValue, maxValue, currencyCode, onFocus, onBlur, onChange } = _a, props = tslib.__rest(_a, ["value", "minValue", "maxValue", "currencyCode", "onFocus", "onBlur", "onChange"]);
|
|
1804
1990
|
const { currency: currencyOptions } = useDContext();
|
|
1805
1991
|
const { handleOnWheel, } = useDisableInputWheel(ref);
|
|
1806
|
-
const { inputRef, innerValue, innerType, handleOnFocus, handleOnChange, handleOnBlur,
|
|
1807
|
-
return (jsxRuntime.jsx(ForwardedDInput, Object.assign({ ref: inputRef, value: innerValue, onChange: handleOnChange,
|
|
1992
|
+
const { inputRef, innerValue, innerType, handleOnFocus, handleOnChange, handleOnBlur, } = useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref, minValue, maxValue);
|
|
1993
|
+
return (jsxRuntime.jsx(ForwardedDInput, Object.assign({ ref: inputRef, value: innerValue, onChange: handleOnChange, inputMode: "decimal", type: innerType, onFocus: handleOnFocus, onBlur: handleOnBlur, onWheel: handleOnWheel, inputStart: (jsxRuntime.jsx("span", { slot: "input-start", className: "d-input-currency-symbol", children: currencyCode || currencyOptions.symbol })) }, props)));
|
|
1808
1994
|
}
|
|
1809
1995
|
const ForwardedDInputCurrency = React.forwardRef(DInputCurrency);
|
|
1810
1996
|
ForwardedDInputCurrency.displayName = 'DInputCurrency';
|
|
@@ -1826,26 +2012,25 @@ ForwardedDInputPassword.displayName = 'DInputPassword';
|
|
|
1826
2012
|
|
|
1827
2013
|
function PasswordCheckItem({ password, regex, text, }) {
|
|
1828
2014
|
const isValid = regex.test(password);
|
|
1829
|
-
return (jsxRuntime.jsxs("li", { className:
|
|
2015
|
+
return (jsxRuntime.jsxs("li", { className: `d-password-check-item${isValid ? ' is-valid' : ''}`, children: [jsxRuntime.jsx(DIcon, { className: "d-password-check-item-icon", icon: isValid ? 'CircleCheck' : 'Circle', size: "16px" }), jsxRuntime.jsx("span", { children: text })] }));
|
|
1830
2016
|
}
|
|
1831
2017
|
|
|
1832
|
-
const
|
|
2018
|
+
const getStrengthModifier = (strength, total) => {
|
|
1833
2019
|
const percentage = total > 0 ? strength / total : 0;
|
|
1834
2020
|
if (percentage === 0)
|
|
1835
|
-
return '
|
|
2021
|
+
return 'is-empty';
|
|
1836
2022
|
if (percentage <= 0.25)
|
|
1837
|
-
return '
|
|
2023
|
+
return 'is-weak';
|
|
1838
2024
|
if (percentage <= 0.5)
|
|
1839
|
-
return '
|
|
2025
|
+
return 'is-fair';
|
|
1840
2026
|
if (percentage <= 0.75)
|
|
1841
|
-
return '
|
|
1842
|
-
return '
|
|
2027
|
+
return 'is-good';
|
|
2028
|
+
return 'is-strong';
|
|
1843
2029
|
};
|
|
1844
2030
|
function PasswordStrengthBar({ strength, total }) {
|
|
1845
2031
|
const percentage = total > 0 ? (strength / total) * 100 : 0;
|
|
1846
|
-
return (jsxRuntime.jsx("div", { className: "
|
|
2032
|
+
return (jsxRuntime.jsx("div", { className: "d-password-strength-bar", children: jsxRuntime.jsx("div", { className: `d-password-strength-bar-fill ${getStrengthModifier(strength, total)}`, style: {
|
|
1847
2033
|
width: `${percentage}%`,
|
|
1848
|
-
transition: 'width 0.3s ease-in-out',
|
|
1849
2034
|
} }) }));
|
|
1850
2035
|
}
|
|
1851
2036
|
|
|
@@ -1881,7 +2066,7 @@ function PasswordChecksList({ password, validationMessages, enabledChecks, }) {
|
|
|
1881
2066
|
const passwordChecks = allChecks.filter((check) => enabledChecks.includes(check.key));
|
|
1882
2067
|
const passed = passwordChecks.filter((r) => r.regex.test(password)).length;
|
|
1883
2068
|
const total = passwordChecks.length;
|
|
1884
|
-
return (jsxRuntime.jsxs("div", { className: "
|
|
2069
|
+
return (jsxRuntime.jsxs("div", { className: "d-password-strength-meter", children: [jsxRuntime.jsx(PasswordStrengthBar, { strength: passed, total: total }), jsxRuntime.jsx("ul", { className: "d-password-check-list", children: passwordChecks.map(({ key, regex, text }) => (jsxRuntime.jsx(PasswordCheckItem, { password: password, regex: regex, text: text }, key))) })] }));
|
|
1885
2070
|
}
|
|
1886
2071
|
|
|
1887
2072
|
const DEFAULT_VALIDATION_MESSAGES = {
|
|
@@ -2231,7 +2416,7 @@ var DListGroup = Object.assign(DListGroup$1, {
|
|
|
2231
2416
|
function DModalHeader({ showCloseButton, onClose, children, className, style, iconFamilyClass, iconFamilyPrefix, icon: iconProp, materialStyle = false, }) {
|
|
2232
2417
|
const { iconMap: { xLg, }, } = useDContext();
|
|
2233
2418
|
const icon = React.useMemo(() => iconProp || xLg, [iconProp, xLg]);
|
|
2234
|
-
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: classNames('modal-header', className), style: style, children: [jsxRuntime.jsx("div", { children: children }), showCloseButton && (jsxRuntime.jsx("button", { type: "button", className: "d-close
|
|
2419
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: classNames('modal-header', className), style: style, children: [jsxRuntime.jsx("div", { children: children }), showCloseButton && (jsxRuntime.jsx("button", { type: "button", className: "d-close", "aria-label": "Close", onClick: onClose, children: jsxRuntime.jsx(DIcon, { icon: icon, familyClass: iconFamilyClass, familyPrefix: iconFamilyPrefix, materialStyle: materialStyle }) }))] }), jsxRuntime.jsx("div", { className: "d-modal-separator" })] }));
|
|
2235
2420
|
}
|
|
2236
2421
|
|
|
2237
2422
|
function DModalBody({ children, className, style, }) {
|
|
@@ -2275,7 +2460,7 @@ var DModal = Object.assign(DModal$1, {
|
|
|
2275
2460
|
function DOffcanvasHeader({ showCloseButton, onClose, children, className, style, iconFamilyClass, iconFamilyPrefix, icon: iconProp, materialStyle = false, }) {
|
|
2276
2461
|
const { iconMap: { xLg, }, } = useDContext();
|
|
2277
2462
|
const icon = React.useMemo(() => iconProp || xLg, [iconProp, xLg]);
|
|
2278
|
-
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: classNames('offcanvas-header', className), style: style, children: [jsxRuntime.jsx("div", { children: children }), showCloseButton && (jsxRuntime.jsx("button", { type: "button", className: "d-close
|
|
2463
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: classNames('offcanvas-header', className), style: style, children: [jsxRuntime.jsx("div", { children: children }), showCloseButton && (jsxRuntime.jsx("button", { type: "button", className: "d-close", "aria-label": "Close", onClick: onClose, children: jsxRuntime.jsx(DIcon, { icon: icon, familyClass: iconFamilyClass, familyPrefix: iconFamilyPrefix, materialStyle: materialStyle }) }))] }), jsxRuntime.jsx("div", { className: "d-offcanvas-separator" })] }));
|
|
2279
2464
|
}
|
|
2280
2465
|
|
|
2281
2466
|
function DOffcanvasBody({ children, className, style, }) {
|
|
@@ -2316,10 +2501,37 @@ const defaultTransition = {
|
|
|
2316
2501
|
ease: 'easeInOut',
|
|
2317
2502
|
duration: 0.3,
|
|
2318
2503
|
};
|
|
2319
|
-
function DOffcanvas$1({ name, className, style, staticBackdrop, scrollable, openFrom = 'end', transition, children, dataAttributes, }) {
|
|
2504
|
+
function DOffcanvas$1({ name, className, style, staticBackdrop, scrollable, openFrom = 'end', width, height, transition, children, dataAttributes, }) {
|
|
2505
|
+
// Only subscribe to breakpoint-change listeners when a responsive object is
|
|
2506
|
+
// actually provided, avoiding unnecessary matchMedia subscriptions for the
|
|
2507
|
+
// common case of plain string values.
|
|
2508
|
+
const hasResponsiveProp = typeof openFrom === 'object'
|
|
2509
|
+
|| typeof width === 'object'
|
|
2510
|
+
|| typeof height === 'object';
|
|
2511
|
+
const { responsivePropValue } = useResponsiveProp(hasResponsiveProp);
|
|
2512
|
+
const resolvedOpenFrom = React.useMemo(() => {
|
|
2513
|
+
var _a;
|
|
2514
|
+
if (typeof openFrom === 'string')
|
|
2515
|
+
return openFrom;
|
|
2516
|
+
return (_a = responsivePropValue(openFrom)) !== null && _a !== void 0 ? _a : 'end';
|
|
2517
|
+
}, [responsivePropValue, openFrom]);
|
|
2518
|
+
const resolvedWidth = React.useMemo(() => {
|
|
2519
|
+
if (!width)
|
|
2520
|
+
return undefined;
|
|
2521
|
+
if (typeof width === 'string')
|
|
2522
|
+
return width;
|
|
2523
|
+
return responsivePropValue(width);
|
|
2524
|
+
}, [responsivePropValue, width]);
|
|
2525
|
+
const resolvedHeight = React.useMemo(() => {
|
|
2526
|
+
if (!height)
|
|
2527
|
+
return undefined;
|
|
2528
|
+
if (typeof height === 'string')
|
|
2529
|
+
return height;
|
|
2530
|
+
return responsivePropValue(height);
|
|
2531
|
+
}, [responsivePropValue, height]);
|
|
2320
2532
|
return (jsxRuntime.jsx(framerMotion.motion.div, Object.assign({ className: classNames('offcanvas portal show', {
|
|
2321
|
-
[`offcanvas-${
|
|
2322
|
-
}, className), style: Object.assign(Object.assign({}, style), { transition: 'none' }), id: name, tabIndex: -1, "aria-labelledby": `${name}Label`, "aria-hidden": "false", custom:
|
|
2533
|
+
[`offcanvas-${resolvedOpenFrom}`]: resolvedOpenFrom,
|
|
2534
|
+
}, className), style: Object.assign(Object.assign(Object.assign(Object.assign({}, style), { transition: 'none' }), (resolvedWidth && { [`--${PREFIX_BS}offcanvas-width`]: resolvedWidth })), (resolvedHeight && { [`--${PREFIX_BS}offcanvas-height`]: resolvedHeight })), id: name, tabIndex: -1, "aria-labelledby": `${name}Label`, "aria-hidden": "false", custom: resolvedOpenFrom, variants: variants, initial: "hidden", animate: "visible", exit: "hidden", transition: Object.assign(Object.assign({}, (transition !== null && transition !== void 0 ? transition : defaultTransition)), { delay: 0.15 }) }, staticBackdrop && ({
|
|
2323
2535
|
[`data-${PREFIX_BS}backdrop`]: 'static',
|
|
2324
2536
|
[`data-${PREFIX_BS}keyboard`]: 'false',
|
|
2325
2537
|
}), scrollable && ({
|
|
@@ -2375,7 +2587,7 @@ function DPopover({ children, renderComponent, open, setOpen, adjustContentToRen
|
|
|
2375
2587
|
}
|
|
2376
2588
|
const reference = React.cloneElement(triggerElement, Object.assign({ ref: refs.setReference }, getReferenceProps(triggerElement.props)));
|
|
2377
2589
|
return (jsxRuntime.jsxs("div", Object.assign({ className: classNames('d-popover', className), style: generateStyleVariables }, dataAttributes, { children: [reference, isOpen && (jsxRuntime.jsx(react.FloatingFocusManager, { context: context, modal: false, children: jsxRuntime.jsx("div", Object.assign({ className: classNames('d-popover-content', {
|
|
2378
|
-
'
|
|
2590
|
+
'd-popover-content-fill': adjustContentToRender,
|
|
2379
2591
|
}), ref: refs.setFloating, style: floatingStyles, "aria-labelledby": headingId }, getFloatingProps(), { children: children })) }))] })));
|
|
2380
2592
|
}
|
|
2381
2593
|
|
|
@@ -2439,13 +2651,13 @@ function DStepper$1({ options, currentStep, className, style, }) {
|
|
|
2439
2651
|
}
|
|
2440
2652
|
|
|
2441
2653
|
function DStepper({ options, currentStep, iconSuccess, iconSuccessFamilyClass, iconSuccessFamilyPrefix, iconSuccessMaterialStyle = false, vertical = false, breakpoint = 'lg', className, completed = false, style, dataAttributes, }) {
|
|
2442
|
-
return (jsxRuntime.jsxs("div", Object.assign({ className: className, style: style }, dataAttributes, { children: [jsxRuntime.jsx("div", { className: `d-block d-${breakpoint}-none
|
|
2654
|
+
return (jsxRuntime.jsxs("div", Object.assign({ className: className, style: style }, dataAttributes, { children: [jsxRuntime.jsx("div", { className: classNames('d-stepper-mobile', `d-block d-${breakpoint}-none`), children: jsxRuntime.jsx(DStepper$1, { options: options, currentStep: currentStep }) }), jsxRuntime.jsx("div", { className: classNames('d-stepper-desktop', `d-none d-${breakpoint}-block`), children: jsxRuntime.jsx(DStepper$2, { options: options, currentStep: currentStep, vertical: vertical, iconSuccess: iconSuccess, iconSuccessFamilyClass: iconSuccessFamilyClass, iconSuccessFamilyPrefix: iconSuccessFamilyPrefix, iconSuccessMaterialStyle: iconSuccessMaterialStyle, completed: completed }) })] })));
|
|
2443
2655
|
}
|
|
2444
2656
|
|
|
2445
2657
|
const ARROW_WIDTH = 8;
|
|
2446
2658
|
const ARROW_HEIGHT = 4;
|
|
2447
|
-
const GAP = 2;
|
|
2448
|
-
function DTooltip({ className, childrenClassName, style, offSet = ARROW_HEIGHT + GAP, padding, withFocus = false, withClick = false, withHover = true, open = false, placement = 'top', size, Component, children, }) {
|
|
2659
|
+
const GAP$1 = 2;
|
|
2660
|
+
function DTooltip({ className, childrenClassName, style, offSet = ARROW_HEIGHT + GAP$1, padding, withFocus = false, withClick = false, withHover = true, open = false, placement = 'top', size, Component, children, }) {
|
|
2449
2661
|
const [isOpen, setIsOpen] = React.useState(open);
|
|
2450
2662
|
const arrowRef = React.useRef(null);
|
|
2451
2663
|
const { refs, context, floatingStyles, } = react.useFloating({
|
|
@@ -2514,8 +2726,16 @@ function DTabs$1({ children, defaultSelected, onChange, options, className, clas
|
|
|
2514
2726
|
React.useEffect(() => {
|
|
2515
2727
|
setSelected(defaultSelected);
|
|
2516
2728
|
}, [defaultSelected]);
|
|
2517
|
-
const generateClasses = React.useMemo(() => (Object.assign({ nav: true, '
|
|
2729
|
+
const generateClasses = React.useMemo(() => (Object.assign({ nav: true, 'd-tabs-nav-vertical': vertical && variant !== 'tabs', [`nav-${variant}`]: true }, className && { [className]: true })), [vertical, variant, className]);
|
|
2518
2730
|
const tabRefs = React.useRef([]);
|
|
2731
|
+
// Always holds the latest `options` without needing to be a dependency:
|
|
2732
|
+
// `options` is commonly passed as an inline array literal (e.g.
|
|
2733
|
+
// `options={[{ label: 'SMS', tab: 'sms' }, ...]}`), so it's a new array
|
|
2734
|
+
// reference on every parent render even when its content hasn't changed.
|
|
2735
|
+
// Reading it from this ref (updated synchronously on every render) lets
|
|
2736
|
+
// the focus effect below react only to `selected` changing.
|
|
2737
|
+
const optionsRef = React.useRef(options);
|
|
2738
|
+
optionsRef.current = options;
|
|
2519
2739
|
React.useEffect(() => {
|
|
2520
2740
|
tabRefs.current = options.map((_, i) => tabRefs.current[i] || React.createRef());
|
|
2521
2741
|
}, [options]);
|
|
@@ -2530,20 +2750,28 @@ function DTabs$1({ children, defaultSelected, onChange, options, className, clas
|
|
|
2530
2750
|
setSelected(firstEnabled.tab);
|
|
2531
2751
|
}
|
|
2532
2752
|
}, [options, selected]);
|
|
2533
|
-
// Declarative focus management
|
|
2534
|
-
|
|
2753
|
+
// Declarative focus management. Wrapped in `useCallback` with an empty
|
|
2754
|
+
// dependency array since it only reads from the `tabRefs` ref, so its
|
|
2755
|
+
// identity stays stable across renders and it can safely be used as an
|
|
2756
|
+
// effect dependency below.
|
|
2757
|
+
const focusTab = React.useCallback((idx) => {
|
|
2535
2758
|
var _a;
|
|
2536
2759
|
if ((_a = tabRefs.current[idx]) === null || _a === void 0 ? void 0 : _a.current) {
|
|
2537
2760
|
tabRefs.current[idx].current.focus();
|
|
2538
2761
|
}
|
|
2539
|
-
};
|
|
2540
|
-
// Focus selected tab when selected changes
|
|
2762
|
+
}, []);
|
|
2763
|
+
// Focus selected tab when selected changes.
|
|
2764
|
+
// Reads `options` from `optionsRef` (see comment above) instead of
|
|
2765
|
+
// depending on `options` directly, so a parent re-render that merely
|
|
2766
|
+
// creates a new `options` reference with identical content doesn't call
|
|
2767
|
+
// `focusTab` again and steal focus away from unrelated elements on the
|
|
2768
|
+
// page (e.g. an OTP input).
|
|
2541
2769
|
React.useEffect(() => {
|
|
2542
|
-
const idx =
|
|
2770
|
+
const idx = optionsRef.current.findIndex((opt) => opt.tab === selected && !opt.disabled);
|
|
2543
2771
|
if (idx !== -1) {
|
|
2544
2772
|
focusTab(idx);
|
|
2545
2773
|
}
|
|
2546
|
-
}, [selected,
|
|
2774
|
+
}, [selected, focusTab]);
|
|
2547
2775
|
const handleKeyDown = React.useCallback((idx, e) => {
|
|
2548
2776
|
const count = options.length;
|
|
2549
2777
|
if (count === 0)
|
|
@@ -2572,7 +2800,7 @@ function DTabs$1({ children, defaultSelected, onChange, options, className, clas
|
|
|
2572
2800
|
}
|
|
2573
2801
|
}
|
|
2574
2802
|
}
|
|
2575
|
-
}, [options, vertical]);
|
|
2803
|
+
}, [options, vertical, focusTab]);
|
|
2576
2804
|
let tablistProps = {};
|
|
2577
2805
|
if (ariaLabelledBy) {
|
|
2578
2806
|
tablistProps = { 'aria-labelledby': ariaLabelledBy };
|
|
@@ -2585,12 +2813,12 @@ function DTabs$1({ children, defaultSelected, onChange, options, className, clas
|
|
|
2585
2813
|
isSelected,
|
|
2586
2814
|
}), [isSelected]);
|
|
2587
2815
|
return (jsxRuntime.jsx(TabContext.Provider, { value: value, children: jsxRuntime.jsxs("div", Object.assign({ className: classNames({
|
|
2588
|
-
'd-
|
|
2589
|
-
'
|
|
2816
|
+
'd-tabs': true,
|
|
2817
|
+
'd-tabs-column': !vertical || variant === 'tabs',
|
|
2590
2818
|
}), style: style }, dataAttributes, { children: [jsxRuntime.jsx("ul", Object.assign({ className: classNames(generateClasses), role: "tablist", "aria-orientation": vertical ? 'vertical' : undefined }, tablistProps, { children: options.map((option, idx) => {
|
|
2591
2819
|
const isTabSelected = !!option.tab && option.tab === selected;
|
|
2592
2820
|
return (jsxRuntime.jsx("li", { role: "presentation", className: "nav-item", children: jsxRuntime.jsx("button", { ref: tabRefs.current[idx], id: `${option.tab}Tab`, className: classNames('nav-link', { active: isTabSelected }, classNameTab), type: "button", role: "tab", "aria-controls": `${option.tab}Pane`, "aria-selected": isTabSelected, tabIndex: isTabSelected ? 0 : -1, disabled: option.disabled, onClick: () => onSelect(option), onKeyDown: (e) => handleKeyDown(idx, e), children: option.label }) }, option.tab));
|
|
2593
|
-
}) })), jsxRuntime.jsx("div", { className: "
|
|
2821
|
+
}) })), jsxRuntime.jsx("div", { className: "d-tabs-content tab-content", children: children })] })) }));
|
|
2594
2822
|
}
|
|
2595
2823
|
var DTabs = Object.assign(DTabs$1, {
|
|
2596
2824
|
Tab: DTabContent,
|
|
@@ -2652,11 +2880,11 @@ function useDToast() {
|
|
|
2652
2880
|
if (!description) {
|
|
2653
2881
|
return (jsxRuntime.jsx(DToast, { className: classNames({
|
|
2654
2882
|
[`toast-${color}`]: !!color,
|
|
2655
|
-
}, 'show'), children: jsxRuntime.jsxs(DToast.Body, { children: [icon && (jsxRuntime.jsx(DIcon, { className: "toast-icon", icon: icon })), jsxRuntime.jsx("p", { className: "toast-title", children: title }), jsxRuntime.jsx("button", { type: "button", className: "d-close
|
|
2883
|
+
}, 'show'), children: jsxRuntime.jsxs(DToast.Body, { children: [icon && (jsxRuntime.jsx(DIcon, { className: "toast-icon", icon: icon })), jsxRuntime.jsx("p", { className: "toast-title", children: title }), jsxRuntime.jsx("button", { type: "button", className: "d-close", "aria-label": "Close", onClick: () => reactHotToast.toast.dismiss(id), children: jsxRuntime.jsx(DIcon, { icon: closeIcon || xLg }) })] }) }));
|
|
2656
2884
|
}
|
|
2657
2885
|
return (jsxRuntime.jsxs(DToast, { className: classNames({
|
|
2658
2886
|
[`toast-${color}`]: !!color,
|
|
2659
|
-
}, 'show'), children: [jsxRuntime.jsxs(DToast.Header, { children: [icon && (jsxRuntime.jsx(DIcon, { className: "toast-icon", icon: icon })), jsxRuntime.jsx("p", { className: "toast-title", children: title }), timestamp && (jsxRuntime.jsx("small", { className: "toast-timestamp", children: timestamp })), jsxRuntime.jsx("button", { type: "button", className: "d-close
|
|
2887
|
+
}, 'show'), children: [jsxRuntime.jsxs(DToast.Header, { children: [icon && (jsxRuntime.jsx(DIcon, { className: "toast-icon", icon: icon })), jsxRuntime.jsx("p", { className: "toast-title", children: title }), timestamp && (jsxRuntime.jsx("small", { className: "toast-timestamp", children: timestamp })), jsxRuntime.jsx("button", { type: "button", className: "d-close", "aria-label": "Close", onClick: () => reactHotToast.toast.dismiss(id), children: jsxRuntime.jsx(DIcon, { icon: closeIcon || xLg }) })] }), jsxRuntime.jsx(DToast.Body, { children: jsxRuntime.jsx("span", { children: description }) })] }));
|
|
2660
2888
|
}, toastProps);
|
|
2661
2889
|
}, [xLg]);
|
|
2662
2890
|
return {
|
|
@@ -2678,11 +2906,10 @@ async function configureI8n(resources, _a = {}) {
|
|
|
2678
2906
|
.then((t) => t);
|
|
2679
2907
|
}
|
|
2680
2908
|
|
|
2681
|
-
function getQueryString(value, config = {
|
|
2682
|
-
useSearch
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
return urlParams.get(value) || config.default;
|
|
2909
|
+
function getQueryString(value, config = {}) {
|
|
2910
|
+
const { useSearch = true, default: defaultValue } = config;
|
|
2911
|
+
const urlParams = new URLSearchParams(useSearch ? window.location.search : '');
|
|
2912
|
+
return urlParams.get(value) || defaultValue;
|
|
2686
2913
|
}
|
|
2687
2914
|
|
|
2688
2915
|
function changeQueryString(values, { useSearch = true, pushState = false, } = {}) {
|
|
@@ -2805,89 +3032,228 @@ function DCreditCard({ brand = 'visa', name, number, holderText = 'Card Holder',
|
|
|
2805
3032
|
return (jsxRuntime.jsxs("div", { className: classNames('d-credit-card', isVertical && 'is-vertical', className), children: [jsxRuntime.jsxs("div", { className: "d-credit-card-header", children: [jsxRuntime.jsx("img", { src: logoImage || BRAND_LOGOS[brand] || DEFAULT_IMAGE, alt: brand, className: "d-credit-card-logo", width: 100 }), isChipVisible && (jsxRuntime.jsx("div", { className: "d-credit-card-chip", children: jsxRuntime.jsx("img", { src: CHIP_IMAGE, alt: "chip", width: 30, className: "d-credit-card-chip-image" }) }))] }), jsxRuntime.jsxs("div", { className: "d-credit-card-details", children: [jsxRuntime.jsx("div", { className: "d-credit-card-number", children: number }), jsxRuntime.jsx("small", { className: "d-credit-card-holder-text", children: holderText }), jsxRuntime.jsx("span", { className: "d-credit-card-name", children: name })] })] }));
|
|
2806
3033
|
}
|
|
2807
3034
|
|
|
3035
|
+
// Gap between the toggle and the menu.
|
|
3036
|
+
const GAP = 4;
|
|
3037
|
+
// Minimum distance the menu should keep from the viewport edges.
|
|
3038
|
+
const VIEWPORT_PADDING = 8;
|
|
3039
|
+
// Minimum usable height left for the menu before we consider a side unusable.
|
|
3040
|
+
const MIN_USABLE_SPACE = 120;
|
|
2808
3041
|
const getItemClass = (action) => classNames({
|
|
2809
3042
|
'dropdown-item d-flex align-items-center': true,
|
|
2810
3043
|
[`dropdown-item-${action.color}`]: !!action.color,
|
|
2811
3044
|
disabled: action.disabled,
|
|
2812
3045
|
});
|
|
2813
|
-
|
|
3046
|
+
const clamp = (value, min, max) => {
|
|
3047
|
+
if (max < min)
|
|
3048
|
+
return min;
|
|
3049
|
+
return Math.min(Math.max(value, min), max);
|
|
3050
|
+
};
|
|
3051
|
+
/**
|
|
3052
|
+
* `position: fixed` is normally relative to the viewport, but any ancestor
|
|
3053
|
+
* with a `transform`, `perspective`, `filter`, `will-change` (of those) or a
|
|
3054
|
+
* non-trivial `contain` establishes a new containing block, making `fixed`
|
|
3055
|
+
* behave like `absolute` relative to that ancestor instead (e.g. Storybook's
|
|
3056
|
+
* docs canvas wrapper, animated cards, sticky containers, etc.). We detect
|
|
3057
|
+
* that ancestor so the computed coordinates can be adjusted accordingly,
|
|
3058
|
+
* keeping the menu correctly anchored to the toggle in every context.
|
|
3059
|
+
*/
|
|
3060
|
+
const getFixedPositioningOffset = (fromEl) => {
|
|
3061
|
+
var _a;
|
|
3062
|
+
let node = (_a = fromEl === null || fromEl === void 0 ? void 0 : fromEl.parentElement) !== null && _a !== void 0 ? _a : null;
|
|
3063
|
+
while (node && node !== document.body) {
|
|
3064
|
+
const cs = window.getComputedStyle(node);
|
|
3065
|
+
// Treat empty/undefined values as "unset" (jsdom returns "" rather than
|
|
3066
|
+
// the CSS initial value like "none" for some of these properties).
|
|
3067
|
+
const isSet = (value) => !!value && value !== 'none';
|
|
3068
|
+
const establishesContainingBlock = isSet(cs.transform)
|
|
3069
|
+
|| isSet(cs.perspective)
|
|
3070
|
+
|| isSet(cs.filter)
|
|
3071
|
+
|| isSet(cs.backdropFilter)
|
|
3072
|
+
|| ['transform', 'perspective', 'filter'].some((prop) => { var _a; return (_a = cs.willChange) === null || _a === void 0 ? void 0 : _a.includes(prop); })
|
|
3073
|
+
|| cs.contain.split(' ').some((value) => ['layout', 'paint', 'strict', 'content'].includes(value));
|
|
3074
|
+
if (establishesContainingBlock) {
|
|
3075
|
+
const rect = node.getBoundingClientRect();
|
|
3076
|
+
return { top: rect.top, left: rect.left };
|
|
3077
|
+
}
|
|
3078
|
+
node = node.parentElement;
|
|
3079
|
+
}
|
|
3080
|
+
return { top: 0, left: 0 };
|
|
3081
|
+
};
|
|
3082
|
+
/**
|
|
3083
|
+
* Resolves where the menu should render (flip) and where it should sit along
|
|
3084
|
+
* the cross axis (shift), so it always stays fully inside the viewport
|
|
3085
|
+
* regardless of where the toggle is placed or how the viewport is resized.
|
|
3086
|
+
* This mimics floating-ui's `flip` + `shift` middlewares without adding the
|
|
3087
|
+
* dependency, keeping the existing Bootstrap markup/classes.
|
|
3088
|
+
*/
|
|
3089
|
+
const computeCoords = (toggleRect, menuSize, placement, alignment) => {
|
|
3090
|
+
const viewportWidth = window.innerWidth;
|
|
3091
|
+
const viewportHeight = window.innerHeight;
|
|
3092
|
+
const spaceBelow = viewportHeight - toggleRect.bottom;
|
|
3093
|
+
const spaceAbove = toggleRect.top;
|
|
3094
|
+
const spaceLeft = toggleRect.left;
|
|
3095
|
+
const spaceRight = viewportWidth - toggleRect.right;
|
|
3096
|
+
let side = placement === 'auto'
|
|
3097
|
+
? 'down'
|
|
3098
|
+
: placement;
|
|
3099
|
+
if (placement === 'auto') {
|
|
3100
|
+
side = spaceBelow >= menuSize.height + GAP || spaceBelow >= spaceAbove ? 'down' : 'up';
|
|
3101
|
+
}
|
|
3102
|
+
// Flip within the current axis if the chosen side can't fit the menu but
|
|
3103
|
+
// the opposite side (same axis) can.
|
|
3104
|
+
if (side === 'down' && spaceBelow < menuSize.height + GAP && spaceAbove > spaceBelow) {
|
|
3105
|
+
side = 'up';
|
|
3106
|
+
}
|
|
3107
|
+
else if (side === 'up' && spaceAbove < menuSize.height + GAP && spaceBelow > spaceAbove) {
|
|
3108
|
+
side = 'down';
|
|
3109
|
+
}
|
|
3110
|
+
else if (side === 'start' && spaceLeft < menuSize.width + GAP && spaceRight > spaceLeft) {
|
|
3111
|
+
side = 'end';
|
|
3112
|
+
}
|
|
3113
|
+
else if (side === 'end' && spaceRight < menuSize.width + GAP && spaceLeft > spaceRight) {
|
|
3114
|
+
side = 'start';
|
|
3115
|
+
}
|
|
3116
|
+
// Cross-axis fallback: e.g. a horizontally-centered toggle on a narrow
|
|
3117
|
+
// viewport can't open sideways (neither `start` nor `end` fit), but there
|
|
3118
|
+
// is plenty of room above/below — switch the whole axis in that case
|
|
3119
|
+
// (and vice versa for `down`/`up` squeezed against a short viewport).
|
|
3120
|
+
const bestVerticalSpace = Math.max(spaceBelow, spaceAbove);
|
|
3121
|
+
const bestHorizontalSpace = Math.max(spaceLeft, spaceRight);
|
|
3122
|
+
if (side === 'down' || side === 'up') {
|
|
3123
|
+
if (bestVerticalSpace < MIN_USABLE_SPACE && bestHorizontalSpace > bestVerticalSpace) {
|
|
3124
|
+
side = spaceRight >= spaceLeft ? 'end' : 'start';
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3127
|
+
else if (bestHorizontalSpace < menuSize.width + GAP
|
|
3128
|
+
&& bestVerticalSpace > bestHorizontalSpace) {
|
|
3129
|
+
side = spaceBelow >= spaceAbove ? 'down' : 'up';
|
|
3130
|
+
}
|
|
3131
|
+
if (side === 'down' || side === 'up') {
|
|
3132
|
+
const top = side === 'down'
|
|
3133
|
+
? toggleRect.bottom + GAP
|
|
3134
|
+
: toggleRect.top - menuSize.height - GAP;
|
|
3135
|
+
// Align horizontally per the requested `alignment` (start/end/center), then
|
|
3136
|
+
// fall back to the opposite edge if the preferred alignment would overflow
|
|
3137
|
+
// the viewport, and clamp as a final safety net so it never overflows.
|
|
3138
|
+
let left;
|
|
3139
|
+
if (alignment === 'end') {
|
|
3140
|
+
left = toggleRect.right - menuSize.width;
|
|
3141
|
+
}
|
|
3142
|
+
else if (alignment === 'center') {
|
|
3143
|
+
left = toggleRect.left + (toggleRect.width / 2) - (menuSize.width / 2);
|
|
3144
|
+
}
|
|
3145
|
+
else {
|
|
3146
|
+
left = toggleRect.left;
|
|
3147
|
+
}
|
|
3148
|
+
if (left + menuSize.width > viewportWidth - VIEWPORT_PADDING && alignment !== 'end') {
|
|
3149
|
+
const altLeft = toggleRect.right - menuSize.width;
|
|
3150
|
+
if (altLeft >= VIEWPORT_PADDING)
|
|
3151
|
+
left = altLeft;
|
|
3152
|
+
}
|
|
3153
|
+
else if (left < VIEWPORT_PADDING && alignment !== 'start') {
|
|
3154
|
+
const altLeft = toggleRect.left;
|
|
3155
|
+
if (altLeft + menuSize.width <= viewportWidth - VIEWPORT_PADDING)
|
|
3156
|
+
left = altLeft;
|
|
3157
|
+
}
|
|
3158
|
+
left = clamp(left, VIEWPORT_PADDING, Math.max(viewportWidth - menuSize.width - VIEWPORT_PADDING, VIEWPORT_PADDING));
|
|
3159
|
+
const availableHeight = side === 'down' ? spaceBelow : spaceAbove;
|
|
3160
|
+
// Cap strictly to the space actually available on the resolved side —
|
|
3161
|
+
// MIN_USABLE_SPACE is only meant to influence the flip/shift decision
|
|
3162
|
+
// above, not to force a taller box than what physically fits and
|
|
3163
|
+
// overflow the viewport.
|
|
3164
|
+
const maxUsableHeight = Math.max(availableHeight - GAP - VIEWPORT_PADDING, 0);
|
|
3165
|
+
return {
|
|
3166
|
+
top,
|
|
3167
|
+
left,
|
|
3168
|
+
minWidth: Math.max(toggleRect.width, 160),
|
|
3169
|
+
maxHeight: `${maxUsableHeight}px`,
|
|
3170
|
+
resolvedSide: side,
|
|
3171
|
+
};
|
|
3172
|
+
}
|
|
3173
|
+
// side is 'start' or 'end'
|
|
3174
|
+
let left = side === 'start'
|
|
3175
|
+
? toggleRect.left - menuSize.width - GAP
|
|
3176
|
+
: toggleRect.right + GAP;
|
|
3177
|
+
// Safety-net clamp: in rare cases where the toggle is squeezed so tightly
|
|
3178
|
+
// that no side of any axis has enough room, keep the menu inside the
|
|
3179
|
+
// viewport rather than letting it render off-screen.
|
|
3180
|
+
left = clamp(left, VIEWPORT_PADDING, Math.max(viewportWidth - menuSize.width - VIEWPORT_PADDING, VIEWPORT_PADDING));
|
|
3181
|
+
// Shift vertically: align with the toggle's top edge by default, but switch
|
|
3182
|
+
// to the bottom edge (or clamp) if that would overflow the viewport.
|
|
3183
|
+
let { top } = toggleRect;
|
|
3184
|
+
if (top + menuSize.height > viewportHeight - VIEWPORT_PADDING) {
|
|
3185
|
+
top = toggleRect.bottom - menuSize.height;
|
|
3186
|
+
}
|
|
3187
|
+
top = clamp(top, VIEWPORT_PADDING, Math.max(viewportHeight - menuSize.height - VIEWPORT_PADDING, VIEWPORT_PADDING));
|
|
3188
|
+
return {
|
|
3189
|
+
top,
|
|
3190
|
+
left,
|
|
3191
|
+
minWidth: Math.max(toggleRect.width, 160),
|
|
3192
|
+
maxHeight: `${Math.max(viewportHeight - top - VIEWPORT_PADDING, 0)}px`,
|
|
3193
|
+
resolvedSide: side,
|
|
3194
|
+
};
|
|
3195
|
+
};
|
|
3196
|
+
function DDropdown({ actions, dropdownToggle, className, classNameMenu, asPortal = false, placement = 'auto', alignment = 'start', }) {
|
|
2814
3197
|
const [open, setOpen] = React.useState(false);
|
|
2815
|
-
const [
|
|
2816
|
-
|
|
2817
|
-
//
|
|
2818
|
-
|
|
2819
|
-
//
|
|
3198
|
+
const [coords, setCoords] = React.useState(null);
|
|
3199
|
+
// Initialize from `placement` (falling back to 'down' for 'auto') so the
|
|
3200
|
+
// wrapper's `drop-*` class reflects the requested side even before the
|
|
3201
|
+
// first open/measurement, matching the previous `drop-${placement}` class
|
|
3202
|
+
// behavior (relevant for Bootstrap caret/orientation styling).
|
|
3203
|
+
const [resolvedSide, setResolvedSide] = React.useState(placement === 'auto' ? 'down' : placement);
|
|
3204
|
+
// Wraps the toggle in both modes — used to read its viewport-relative rect.
|
|
2820
3205
|
const toggleRef = React.useRef(null);
|
|
2821
|
-
// Ref on the rendered <ul> — used for outside-click detection
|
|
3206
|
+
// Ref on the rendered <ul> — used to measure its size and for outside-click detection.
|
|
2822
3207
|
const menuRef = React.useRef(null);
|
|
2823
3208
|
const toggle = () => {
|
|
2824
|
-
|
|
3209
|
+
setCoords(null);
|
|
2825
3210
|
setOpen((prev) => !prev);
|
|
2826
3211
|
};
|
|
2827
|
-
|
|
2828
|
-
//
|
|
2829
|
-
const
|
|
2830
|
-
if (!toggleRef.current)
|
|
3212
|
+
// Measures the toggle + menu and (re)computes fixed, pixel-perfect coords
|
|
3213
|
+
// with collision detection, so the menu never overflows the viewport.
|
|
3214
|
+
const updatePosition = React.useCallback(() => {
|
|
3215
|
+
if (!toggleRef.current || !menuRef.current)
|
|
3216
|
+
return;
|
|
3217
|
+
const toggleRect = toggleRef.current.getBoundingClientRect();
|
|
3218
|
+
// If the toggle itself has scrolled completely out of the viewport
|
|
3219
|
+
// (regardless of `placement`), close the menu instead of leaving it
|
|
3220
|
+
// rendered detached from the toggle that opened it.
|
|
3221
|
+
const toggleIsMeasured = toggleRect.width > 0 || toggleRect.height > 0;
|
|
3222
|
+
const toggleIsOutOfView = toggleRect.bottom <= 0
|
|
3223
|
+
|| toggleRect.top >= window.innerHeight
|
|
3224
|
+
|| toggleRect.right <= 0
|
|
3225
|
+
|| toggleRect.left >= window.innerWidth;
|
|
3226
|
+
if (toggleIsMeasured && toggleIsOutOfView) {
|
|
3227
|
+
setOpen(false);
|
|
2831
3228
|
return;
|
|
2832
|
-
const rect = toggleRef.current.getBoundingClientRect();
|
|
2833
|
-
const spaceRight = window.innerWidth - rect.right;
|
|
2834
|
-
const spaceBottom = window.innerHeight - rect.bottom;
|
|
2835
|
-
const spaceTop = rect.top;
|
|
2836
|
-
let resolvedPlacement;
|
|
2837
|
-
if (placement === 'auto') {
|
|
2838
|
-
if (spaceBottom < 150) {
|
|
2839
|
-
resolvedPlacement = 'up';
|
|
2840
|
-
}
|
|
2841
|
-
else if (spaceRight < 150) {
|
|
2842
|
-
resolvedPlacement = 'start';
|
|
2843
|
-
}
|
|
2844
|
-
else {
|
|
2845
|
-
resolvedPlacement = 'down';
|
|
2846
|
-
}
|
|
2847
|
-
}
|
|
2848
|
-
else {
|
|
2849
|
-
resolvedPlacement = placement;
|
|
2850
|
-
}
|
|
2851
|
-
const openStart = resolvedPlacement === 'start';
|
|
2852
|
-
const openEnd = resolvedPlacement === 'end';
|
|
2853
|
-
const openUp = resolvedPlacement === 'up';
|
|
2854
|
-
let availableHeight = spaceBottom;
|
|
2855
|
-
if (openUp) {
|
|
2856
|
-
availableHeight = spaceTop;
|
|
2857
|
-
}
|
|
2858
|
-
else if (openStart || openEnd) {
|
|
2859
|
-
availableHeight = window.innerHeight - rect.top;
|
|
2860
|
-
}
|
|
2861
|
-
const verticalCoords = openUp
|
|
2862
|
-
? { bottom: window.innerHeight - rect.top + 4 }
|
|
2863
|
-
: { top: openStart || openEnd ? rect.top : rect.bottom + 4 };
|
|
2864
|
-
let horizontalCoords;
|
|
2865
|
-
if (openStart) {
|
|
2866
|
-
horizontalCoords = { right: window.innerWidth - rect.left + 4 };
|
|
2867
|
-
}
|
|
2868
|
-
else if (openEnd) {
|
|
2869
|
-
horizontalCoords = { left: rect.right + 4 };
|
|
2870
|
-
}
|
|
2871
|
-
else {
|
|
2872
|
-
horizontalCoords = { left: rect.left };
|
|
2873
3229
|
}
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
const
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
};
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
React.
|
|
2890
|
-
if (
|
|
3230
|
+
const menuRect = menuRef.current.getBoundingClientRect();
|
|
3231
|
+
const _a = computeCoords(toggleRect, { width: menuRect.width, height: menuRect.height }, placement, alignment), { resolvedSide: side } = _a, menuCoords = tslib.__rest(_a, ["resolvedSide"]);
|
|
3232
|
+
// In portal mode the menu is appended to document.body, whose containing
|
|
3233
|
+
// block for `fixed` is always the viewport (no adjustment needed). In
|
|
3234
|
+
// inline mode the menu stays next to the toggle, so we must look for a
|
|
3235
|
+
// containing-block-establishing ancestor starting from the toggle.
|
|
3236
|
+
const offset = asPortal
|
|
3237
|
+
? { top: 0, left: 0 }
|
|
3238
|
+
: getFixedPositioningOffset(toggleRef.current);
|
|
3239
|
+
setResolvedSide(side);
|
|
3240
|
+
setCoords(Object.assign(Object.assign({}, menuCoords), { top: menuCoords.top - offset.top, left: menuCoords.left - offset.left }));
|
|
3241
|
+
}, [placement, alignment, asPortal]);
|
|
3242
|
+
// Throttles scroll/resize-triggered updates to at most once per frame,
|
|
3243
|
+
// avoiding repeated forced reflows on busy/high-frequency scroll events.
|
|
3244
|
+
const rafIdRef = React.useRef(null);
|
|
3245
|
+
const scheduleUpdatePosition = React.useCallback(() => {
|
|
3246
|
+
if (rafIdRef.current !== null)
|
|
3247
|
+
return;
|
|
3248
|
+
rafIdRef.current = window.requestAnimationFrame(() => {
|
|
3249
|
+
rafIdRef.current = null;
|
|
3250
|
+
updatePosition();
|
|
3251
|
+
});
|
|
3252
|
+
}, [updatePosition]);
|
|
3253
|
+
// Outside-click detection (checks both the toggle and the floating menu,
|
|
3254
|
+
// since in portal mode the menu isn't a DOM descendant of the toggle).
|
|
3255
|
+
React.useLayoutEffect(() => {
|
|
3256
|
+
if (!open)
|
|
2891
3257
|
return () => { };
|
|
2892
3258
|
const handler = (event) => {
|
|
2893
3259
|
var _a, _b;
|
|
@@ -2898,35 +3264,25 @@ function DDropdown({ actions, dropdownToggle, className, classNameMenu, asPortal
|
|
|
2898
3264
|
};
|
|
2899
3265
|
document.addEventListener('mousedown', handler);
|
|
2900
3266
|
return () => document.removeEventListener('mousedown', handler);
|
|
2901
|
-
}, [
|
|
2902
|
-
//
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
const spaceBottom = window.innerHeight - rect.bottom;
|
|
2908
|
-
const spaceRight = window.innerWidth - rect.right;
|
|
2909
|
-
if (spaceBottom < 150)
|
|
2910
|
-
setPosition('up');
|
|
2911
|
-
else if (spaceRight < 150)
|
|
2912
|
-
setPosition('start');
|
|
2913
|
-
else
|
|
2914
|
-
setPosition('down');
|
|
2915
|
-
}, [asPortal, open, placement]);
|
|
2916
|
-
// Compute + track coords: portal mode
|
|
2917
|
-
// Re-runs whenever the menu opens and keeps coords fresh on scroll/resize
|
|
2918
|
-
React.useEffect(() => {
|
|
2919
|
-
if (!asPortal || !open)
|
|
3267
|
+
}, [open]);
|
|
3268
|
+
// Computes the initial position synchronously (before paint, to avoid a
|
|
3269
|
+
// flicker) and keeps it fresh while the menu is open and the viewport
|
|
3270
|
+
// scrolls or resizes (throttled to one update per frame, see above).
|
|
3271
|
+
React.useLayoutEffect(() => {
|
|
3272
|
+
if (!open)
|
|
2920
3273
|
return () => { };
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
window.addEventListener('
|
|
2924
|
-
window.addEventListener('resize', update, { passive: true });
|
|
3274
|
+
updatePosition();
|
|
3275
|
+
window.addEventListener('scroll', scheduleUpdatePosition, { passive: true, capture: true });
|
|
3276
|
+
window.addEventListener('resize', scheduleUpdatePosition, { passive: true });
|
|
2925
3277
|
return () => {
|
|
2926
|
-
window.removeEventListener('scroll',
|
|
2927
|
-
window.removeEventListener('resize',
|
|
3278
|
+
window.removeEventListener('scroll', scheduleUpdatePosition, { capture: true });
|
|
3279
|
+
window.removeEventListener('resize', scheduleUpdatePosition);
|
|
3280
|
+
if (rafIdRef.current !== null) {
|
|
3281
|
+
window.cancelAnimationFrame(rafIdRef.current);
|
|
3282
|
+
rafIdRef.current = null;
|
|
3283
|
+
}
|
|
2928
3284
|
};
|
|
2929
|
-
}, [
|
|
3285
|
+
}, [open, updatePosition, scheduleUpdatePosition]);
|
|
2930
3286
|
let ToggleElement;
|
|
2931
3287
|
if (dropdownToggle) {
|
|
2932
3288
|
ToggleElement = typeof dropdownToggle === 'function'
|
|
@@ -2936,57 +3292,30 @@ function DDropdown({ actions, dropdownToggle, className, classNameMenu, asPortal
|
|
|
2936
3292
|
else {
|
|
2937
3293
|
ToggleElement = (jsxRuntime.jsx(DButtonIcon, { variant: "link", stopPropagationEnabled: false, "aria-label": "Toggle Dropdown", "aria-haspopup": "menu", "aria-expanded": open, onClick: toggle, icon: "MoreVertical" }));
|
|
2938
3294
|
}
|
|
2939
|
-
|
|
3295
|
+
// Until the first measurement is available, keep the menu off-screen but
|
|
3296
|
+
// measurable (visibility: hidden preserves layout, unlike display: none).
|
|
3297
|
+
const menuStyle = coords
|
|
2940
3298
|
? {
|
|
2941
3299
|
position: 'fixed',
|
|
2942
|
-
top:
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
minWidth: menuCoords.minWidth,
|
|
2947
|
-
maxHeight: menuCoords.maxHeight,
|
|
3300
|
+
top: coords.top,
|
|
3301
|
+
left: coords.left,
|
|
3302
|
+
minWidth: coords.minWidth,
|
|
3303
|
+
maxHeight: coords.maxHeight,
|
|
2948
3304
|
overflowY: 'auto',
|
|
2949
3305
|
zIndex: 1050,
|
|
2950
3306
|
transform: 'none',
|
|
3307
|
+
visibility: 'visible',
|
|
2951
3308
|
}
|
|
2952
|
-
:
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
inlineLeft = 'auto';
|
|
2963
|
-
}
|
|
2964
|
-
else if (resolvedInlinePosition === 'end') {
|
|
2965
|
-
inlineLeft = '100%';
|
|
2966
|
-
}
|
|
2967
|
-
let inlineRight = 'auto';
|
|
2968
|
-
if (resolvedInlinePosition === 'start') {
|
|
2969
|
-
inlineRight = '100%';
|
|
2970
|
-
}
|
|
2971
|
-
let inlineTransform = 'translateY(4px)';
|
|
2972
|
-
if (resolvedInlinePosition === 'up') {
|
|
2973
|
-
inlineTransform = 'translateY(-4px)';
|
|
2974
|
-
}
|
|
2975
|
-
else if (resolvedInlinePosition === 'start') {
|
|
2976
|
-
inlineTransform = 'translateX(-4px)';
|
|
2977
|
-
}
|
|
2978
|
-
else if (resolvedInlinePosition === 'end') {
|
|
2979
|
-
inlineTransform = 'translateX(4px)';
|
|
2980
|
-
}
|
|
2981
|
-
const inlineMenuStyle = {
|
|
2982
|
-
position: 'absolute',
|
|
2983
|
-
top: inlineTop,
|
|
2984
|
-
bottom: resolvedInlinePosition === 'up' ? '100%' : 'auto',
|
|
2985
|
-
left: inlineLeft,
|
|
2986
|
-
right: inlineRight,
|
|
2987
|
-
transform: inlineTransform,
|
|
2988
|
-
};
|
|
2989
|
-
const menuItems = (jsxRuntime.jsx("ul", { ref: menuRef, role: "menu", className: classNames('dropdown-menu p-2', { show: open }, classNameMenu), style: portalMenuStyle !== null && portalMenuStyle !== void 0 ? portalMenuStyle : inlineMenuStyle, children: actions.map((action, index) => {
|
|
3309
|
+
: {
|
|
3310
|
+
position: 'fixed',
|
|
3311
|
+
top: 0,
|
|
3312
|
+
left: 0,
|
|
3313
|
+
visibility: 'hidden',
|
|
3314
|
+
pointerEvents: 'none',
|
|
3315
|
+
zIndex: 1050,
|
|
3316
|
+
transform: 'none',
|
|
3317
|
+
};
|
|
3318
|
+
const menuItems = (jsxRuntime.jsx("ul", { ref: menuRef, role: "menu", className: classNames('dropdown-menu p-2', { show: open }, classNameMenu), style: menuStyle, children: actions.map((action, index) => {
|
|
2990
3319
|
if (action.isDivider) {
|
|
2991
3320
|
return jsxRuntime.jsx("hr", { className: "dropdown-divider" }, index);
|
|
2992
3321
|
}
|
|
@@ -3005,10 +3334,7 @@ function DDropdown({ actions, dropdownToggle, className, classNameMenu, asPortal
|
|
|
3005
3334
|
}
|
|
3006
3335
|
}, disabled: action.disabled, children: [action.icon && jsxRuntime.jsx(DIcon, { icon: action.icon, className: "me-2", size: "1rem" }), action.label] })) }, index));
|
|
3007
3336
|
}) }));
|
|
3008
|
-
|
|
3009
|
-
return (jsxRuntime.jsxs("div", { ref: toggleRef, className: classNames('dropdown', className), children: [ToggleElement, open && menuCoords && reactDom.createPortal(menuItems, document.body)] }));
|
|
3010
|
-
}
|
|
3011
|
-
return (jsxRuntime.jsxs("div", { className: classNames(`dropdown drop-${resolvedInlinePosition}`, className), ref: dropdownRef, children: [ToggleElement, menuItems] }));
|
|
3337
|
+
return (jsxRuntime.jsxs("div", { ref: toggleRef, className: classNames(`dropdown drop-${resolvedSide} d-inline-block`, className), children: [ToggleElement, open && (asPortal ? reactDom.createPortal(menuItems, document.body) : menuItems)] }));
|
|
3012
3338
|
}
|
|
3013
3339
|
|
|
3014
3340
|
function useScreenshot() {
|
|
@@ -3038,13 +3364,18 @@ function useScreenshot() {
|
|
|
3038
3364
|
|
|
3039
3365
|
function useScreenshotDownload() {
|
|
3040
3366
|
const { clipRef, takeBlob } = useScreenshot();
|
|
3041
|
-
const download = React.useCallback(async () => {
|
|
3042
|
-
const blob = await takeBlob();
|
|
3367
|
+
const download = React.useCallback(async (fileName = 'voucher') => {
|
|
3368
|
+
const blob = await takeBlob('image/jpeg');
|
|
3043
3369
|
const url = window.URL.createObjectURL(blob);
|
|
3044
3370
|
const link = window.document.createElement('a');
|
|
3045
3371
|
link.style.display = 'none';
|
|
3046
3372
|
link.href = url;
|
|
3047
|
-
|
|
3373
|
+
// Fall back to the default base name when the caller passes a blank/whitespace value,
|
|
3374
|
+
// so the file is never named just ".jpg".
|
|
3375
|
+
const safeFileName = fileName.trim() || 'voucher';
|
|
3376
|
+
// The extension is always appended by the hook to guarantee a valid image file,
|
|
3377
|
+
// regardless of what fileName the caller passes (e.g. "receipt.png" -> "receipt.png.jpg").
|
|
3378
|
+
link.download = `${safeFileName}.jpg`;
|
|
3048
3379
|
document.body.appendChild(link);
|
|
3049
3380
|
link.click();
|
|
3050
3381
|
document.body.removeChild(link);
|
|
@@ -3058,11 +3389,15 @@ function useScreenshotDownload() {
|
|
|
3058
3389
|
|
|
3059
3390
|
function useScreenshotWebShare() {
|
|
3060
3391
|
const { takeBlob, clipRef } = useScreenshot();
|
|
3061
|
-
const share = React.useCallback(async () => {
|
|
3062
|
-
const blob = await takeBlob();
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3392
|
+
const share = React.useCallback(async (fileName = 'voucher') => {
|
|
3393
|
+
const blob = await takeBlob('image/jpeg');
|
|
3394
|
+
// Fall back to the default base name when the caller passes a blank/whitespace value,
|
|
3395
|
+
// so the file is never named just ".jpeg".
|
|
3396
|
+
const safeFileName = fileName.trim() || 'voucher';
|
|
3397
|
+
// The extension is always appended by the hook to guarantee a valid image file,
|
|
3398
|
+
// regardless of what fileName the caller passes (e.g. "receipt.png" -> "receipt.png.jpeg").
|
|
3399
|
+
const image = new File([blob], `${safeFileName}.jpeg`, { type: 'image/jpeg' });
|
|
3400
|
+
if (!navigator.canShare || !navigator.canShare({ files: [image] })) {
|
|
3066
3401
|
window.print();
|
|
3067
3402
|
return;
|
|
3068
3403
|
}
|
|
@@ -3074,11 +3409,11 @@ function useScreenshotWebShare() {
|
|
|
3074
3409
|
};
|
|
3075
3410
|
}
|
|
3076
3411
|
|
|
3077
|
-
function DVoucher({ amount, amountDetails, icon, title, onError, message, downloadText = 'Download', shareText = 'Share', className, children, }) {
|
|
3412
|
+
function DVoucher({ amount, amountDetails, icon, title, onError, message, downloadText = 'Download', shareText = 'Share', fileName = 'voucher', hideActions = false, className, children, }) {
|
|
3078
3413
|
const { shareRef, share } = useScreenshotWebShare();
|
|
3079
3414
|
const { downloadRef, download } = useScreenshotDownload();
|
|
3080
3415
|
const handleShare = () => {
|
|
3081
|
-
share()
|
|
3416
|
+
share(fileName)
|
|
3082
3417
|
.catch(async (err) => {
|
|
3083
3418
|
if (onError) {
|
|
3084
3419
|
await onError(err);
|
|
@@ -3089,7 +3424,7 @@ function DVoucher({ amount, amountDetails, icon, title, onError, message, downlo
|
|
|
3089
3424
|
});
|
|
3090
3425
|
};
|
|
3091
3426
|
const handleDownload = () => {
|
|
3092
|
-
download()
|
|
3427
|
+
download(fileName)
|
|
3093
3428
|
.catch(async (err) => {
|
|
3094
3429
|
if (onError) {
|
|
3095
3430
|
await onError(err);
|
|
@@ -3117,80 +3452,36 @@ function DVoucher({ amount, amountDetails, icon, title, onError, message, downlo
|
|
|
3117
3452
|
return (jsxRuntime.jsx("div", { className: classNames('d-voucher', className), ref: (el) => {
|
|
3118
3453
|
shareRef.current = el;
|
|
3119
3454
|
downloadRef.current = el;
|
|
3120
|
-
}, children: jsxRuntime.jsxs("div", { children: [jsxRuntime.jsxs("div", { className: "d-voucher-header", children: [resolvedIconProps && (jsxRuntime.jsx(DIcon, Object.assign({}, resolvedIconProps))), jsxRuntime.jsxs("div", { className: "
|
|
3121
|
-
}
|
|
3122
|
-
|
|
3123
|
-
function useCountdown(seconds) {
|
|
3124
|
-
const [secondsLeft, setSecondsLeft] = React.useState(seconds);
|
|
3125
|
-
const [isActive, setIsActive] = React.useState(true);
|
|
3126
|
-
const resetCountdown = React.useCallback((newSeconds = seconds) => {
|
|
3127
|
-
setIsActive(false);
|
|
3128
|
-
setSecondsLeft(newSeconds);
|
|
3129
|
-
}, [seconds]);
|
|
3130
|
-
const restartCountdown = React.useCallback(() => {
|
|
3131
|
-
resetCountdown(seconds);
|
|
3132
|
-
setIsActive(true);
|
|
3133
|
-
}, [resetCountdown, seconds]);
|
|
3134
|
-
React.useEffect(() => {
|
|
3135
|
-
if (!isActive) {
|
|
3136
|
-
return () => { };
|
|
3137
|
-
}
|
|
3138
|
-
const interval = setInterval(() => {
|
|
3139
|
-
setSecondsLeft((prevSeconds) => {
|
|
3140
|
-
const newSeconds = prevSeconds - 1;
|
|
3141
|
-
if (newSeconds <= 0) {
|
|
3142
|
-
clearInterval(interval);
|
|
3143
|
-
setIsActive(false);
|
|
3144
|
-
return 0;
|
|
3145
|
-
}
|
|
3146
|
-
return newSeconds;
|
|
3147
|
-
});
|
|
3148
|
-
}, 1000);
|
|
3149
|
-
return () => clearInterval(interval);
|
|
3150
|
-
}, [isActive]);
|
|
3151
|
-
return { secondsLeft, restartCountdown };
|
|
3455
|
+
}, children: jsxRuntime.jsxs("div", { children: [jsxRuntime.jsxs("div", { className: "d-voucher-header", children: [resolvedIconProps && (jsxRuntime.jsx(DIcon, Object.assign({}, resolvedIconProps))), jsxRuntime.jsxs("div", { className: "d-voucher-title-group", children: [jsxRuntime.jsx("h3", { className: "d-voucher-title", children: title }), jsxRuntime.jsx("p", { className: "d-voucher-message", children: message })] })] }), amount && (jsxRuntime.jsxs("div", { className: "d-voucher-amount", children: [jsxRuntime.jsx("div", { className: classNames('d-voucher-amount-value', { 'd-voucher-amount-value-with-details': !!amountDetails }), children: amount }), amountDetails] })), jsxRuntime.jsx("hr", { className: "d-voucher-divider" }), children, !hideActions && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("hr", { className: "my-4" }), jsxRuntime.jsxs("div", { className: "d-voucher-footer", children: [jsxRuntime.jsx(DButton, { onClick: handleShare, iconStart: "Share2", text: shareText, variant: "outline", size: "sm" }), jsxRuntime.jsx(DButton, { onClick: handleDownload, iconStart: "Download", text: downloadText, variant: "outline", size: "sm" })] })] }))] }) }));
|
|
3152
3456
|
}
|
|
3153
3457
|
|
|
3154
3458
|
const defaultMessage = (secs) => (secs > 0
|
|
3155
3459
|
? `Didn't get any code? Resend in: ${secs}s`
|
|
3156
3460
|
: "Didn't get any code?");
|
|
3157
|
-
function OtpCountdown({
|
|
3158
|
-
|
|
3159
|
-
return (jsxRuntime.jsxs("div", { className: "d-flex gap-2 align-items-center", children: [jsxRuntime.jsx("p", { className: "mb-0 flex-1", children: message ? message(secondsLeft) : defaultMessage(secondsLeft) }), jsxRuntime.jsx(DButton, { text: resendText, variant: "link", className: "text-nowrap", disabled: secondsLeft > 0, onClick: restartCountdown })] }));
|
|
3461
|
+
function OtpCountdown({ secondsLeft, restartCountdown, resendText, message, }) {
|
|
3462
|
+
return (jsxRuntime.jsxs("div", { className: "d-otp-countdown", children: [jsxRuntime.jsx("p", { className: "d-otp-countdown-text", children: message ? message(secondsLeft) : defaultMessage(secondsLeft) }), jsxRuntime.jsx(DButton, { text: resendText, variant: "link", className: "text-nowrap", disabled: secondsLeft > 0, onClick: restartCountdown })] }));
|
|
3160
3463
|
}
|
|
3161
3464
|
|
|
3162
3465
|
const TEXT_PROPS = {
|
|
3163
3466
|
resend: 'Resend',
|
|
3164
|
-
resendText: 'Resend
|
|
3467
|
+
resendText: 'Resend',
|
|
3165
3468
|
submit: 'Authorize and continue',
|
|
3166
3469
|
title: 'We will send you a 6-digit code to your associated phone number so you can continue with your request.',
|
|
3470
|
+
invalidCode: 'Invalid code, please try again.',
|
|
3167
3471
|
contact: (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { children: "Problems with your digital token? Contact us" }), ' ', jsxRuntime.jsx("a", { href: "https://www.modyo.com", className: "link-primary text-nowrap", target: "_blank", rel: "noreferrer", children: "Contact us" })] })),
|
|
3168
3472
|
};
|
|
3169
3473
|
function DOtp({ className, action, isLoading, otpSize = 6, texts = TEXT_PROPS, seconds = 15, }) {
|
|
3170
|
-
const
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
if (otp.length < otpSize) {
|
|
3174
|
-
setInvalid(true);
|
|
3175
|
-
return;
|
|
3176
|
-
}
|
|
3177
|
-
setInvalid(false);
|
|
3178
|
-
await action();
|
|
3179
|
-
}, [
|
|
3180
|
-
otp.length,
|
|
3181
|
-
action,
|
|
3182
|
-
otpSize,
|
|
3183
|
-
]);
|
|
3184
|
-
return (jsxRuntime.jsxs("div", { className: className, children: [jsxRuntime.jsx("p", { children: texts.title }), jsxRuntime.jsxs("div", { className: "d-flex flex-column gap-6 pb-4 px-3", children: [jsxRuntime.jsxs("div", { className: "d-flex flex-column gap-6", children: [jsxRuntime.jsx(DInputPin, { className: "modal-otp-pin", characters: otpSize, onChange: (e) => setOtp(e), invalid: invalid && otp.length < otpSize, placeholder: "0" }), jsxRuntime.jsx(OtpCountdown, { seconds: seconds, resendText: texts.resend })] }), jsxRuntime.jsx("hr", { className: "m-0" }), jsxRuntime.jsxs("div", { className: "d-flex flex-column flex-lg-row gap-4 align-items-center", children: [jsxRuntime.jsx(DButton, { text: texts.submit, onClick: () => {
|
|
3185
|
-
handler().catch((err) => {
|
|
3474
|
+
const { setOtp, invalid, submit, isLoading: isSubmitting, secondsLeft, restartCountdown, } = useOtp({ action, otpSize, seconds });
|
|
3475
|
+
return (jsxRuntime.jsxs("div", { className: className, children: [jsxRuntime.jsx("p", { children: texts.title }), jsxRuntime.jsxs("div", { className: "d-otp d-otp-content", children: [jsxRuntime.jsxs("div", { className: "d-otp-fields", children: [jsxRuntime.jsx(DInputPin, { className: "modal-otp-pin", characters: otpSize, onChange: (e) => setOtp(e), invalid: invalid, hint: invalid ? texts.invalidCode : undefined, placeholder: "0" }), jsxRuntime.jsx(OtpCountdown, { secondsLeft: secondsLeft, restartCountdown: restartCountdown, resendText: texts.resendText })] }), jsxRuntime.jsx("hr", { className: "d-otp-divider" }), jsxRuntime.jsxs("div", { className: "d-otp-footer", children: [jsxRuntime.jsx(DButton, { text: texts.submit, onClick: () => {
|
|
3476
|
+
submit().catch((err) => {
|
|
3186
3477
|
// eslint-disable-next-line no-console
|
|
3187
3478
|
console.error('Error in DOtp action:', err);
|
|
3188
3479
|
});
|
|
3189
|
-
}, loading: isLoading }), jsxRuntime.jsx("p", { className: "
|
|
3480
|
+
}, loading: isLoading || isSubmitting }), jsxRuntime.jsx("p", { className: "d-otp-contact", children: texts.contact })] })] })] }));
|
|
3190
3481
|
}
|
|
3191
3482
|
|
|
3192
3483
|
function DefaultErrorBoundary({ resetErrorBoundary }) {
|
|
3193
|
-
return (jsxRuntime.jsx(DAlert, { color: "danger", showClose: false, children: jsxRuntime.jsxs("div", { className: "d-
|
|
3484
|
+
return (jsxRuntime.jsx(DAlert, { color: "danger", showClose: false, children: jsxRuntime.jsxs("div", { className: "d-error-boundary-content", children: [jsxRuntime.jsx("span", { children: "An unexpected error occurred." }), jsxRuntime.jsx(DButton, { color: "secondary", variant: "outline", size: "sm", onClick: resetErrorBoundary, children: "Retry" })] }) }));
|
|
3194
3485
|
}
|
|
3195
3486
|
|
|
3196
3487
|
function DErrorBoundary({ name, fallback, resetKeys, onReset, onError, children, }) {
|
|
@@ -3208,15 +3499,15 @@ function DErrorBoundary({ name, fallback, resetKeys, onReset, onError, children,
|
|
|
3208
3499
|
}
|
|
3209
3500
|
|
|
3210
3501
|
function ErrorState({ message, onRetry, retryMessage = 'Retry', color = 'danger', }) {
|
|
3211
|
-
return (jsxRuntime.jsxs(DAlert, { color: color, className: "d-
|
|
3502
|
+
return (jsxRuntime.jsxs(DAlert, { color: color, className: "d-error-state", children: [jsxRuntime.jsx("div", { className: "d-error-state-content", children: jsxRuntime.jsx("p", { className: "d-error-state-message", children: message !== null && message !== void 0 ? message : 'An unexpected error occurred.' }) }), onRetry && (jsxRuntime.jsx(DButton, { onClick: onRetry, text: retryMessage, variant: "outline", iconStart: "RefreshCw" }))] }));
|
|
3212
3503
|
}
|
|
3213
3504
|
|
|
3214
3505
|
function EmptyState({ message, icon = 'FileText', actionText, onAction, }) {
|
|
3215
|
-
return (jsxRuntime.jsxs("div", { className: "d-
|
|
3506
|
+
return (jsxRuntime.jsxs("div", { className: "d-empty-state", children: [jsxRuntime.jsx(DIcon, { icon: icon, size: "3rem", className: "d-empty-state-icon" }), jsxRuntime.jsx("p", { className: "d-empty-state-message", children: message !== null && message !== void 0 ? message : 'No data available.' }), actionText && onAction && (jsxRuntime.jsx(DButton, { onClick: onAction, text: actionText, variant: "outline" }))] }));
|
|
3216
3507
|
}
|
|
3217
3508
|
|
|
3218
3509
|
function LoadingState({ ariaLabel = 'Loading...', className }) {
|
|
3219
|
-
return (jsxRuntime.jsx("div", { className: `d-
|
|
3510
|
+
return (jsxRuntime.jsx("div", { className: `d-loading-state ${className || ''}`.trim(), "aria-busy": "true", "aria-live": "polite", children: jsxRuntime.jsx("span", { className: "spinner-border", role: "status", "aria-label": ariaLabel }) }));
|
|
3220
3511
|
}
|
|
3221
3512
|
|
|
3222
3513
|
function render(renderable) {
|
|
@@ -3275,7 +3566,7 @@ function DConfirmModalUI({ entry }) {
|
|
|
3275
3566
|
}
|
|
3276
3567
|
});
|
|
3277
3568
|
}, [onConfirmAction]);
|
|
3278
|
-
return (jsxRuntime.jsxs(DModal, { name: entry.id, size: "lg", className: `confirm-modal ${critical ? 'critical-modal' : ''}`, children: [jsxRuntime.jsx(DModal.Header, { onClose: handleClose, showCloseButton: true, children: jsxRuntime.jsx("h5", { className: "fw-bold", children: title }) }), jsxRuntime.jsxs(DModal.Body, { className: "
|
|
3569
|
+
return (jsxRuntime.jsxs(DModal, { name: entry.id, size: "lg", className: `confirm-modal ${critical ? 'critical-modal' : ''}`, children: [jsxRuntime.jsx(DModal.Header, { onClose: handleClose, showCloseButton: true, children: jsxRuntime.jsx("h5", { className: "fw-bold", children: title }) }), jsxRuntime.jsxs(DModal.Body, { className: "d-confirm-modal-body", children: [jsxRuntime.jsx("p", { className: "d-confirm-modal-message", children: message }), critical && (jsxRuntime.jsx(ForwardedDInput, { type: "text", label: critical.codeLabel || 'Confirmation code', placeholder: critical.inputPlaceholder, value: confirmationCode, onChange: (value) => setConfirmationCode(value), className: "d-confirm-modal-field", autoFocus: true }))] }), jsxRuntime.jsxs(DModal.Footer, { children: [jsxRuntime.jsx(DButton, { text: cancelLabel, variant: "outline", color: "secondary", onClick: handleClose, disabled: internalLoading }), jsxRuntime.jsx(DButton, { text: confirmLabel, color: confirmColor, onClick: handleConfirmClick, disabled: isConfirmDisabled, loading: internalLoading })] })] }));
|
|
3279
3570
|
}
|
|
3280
3571
|
|
|
3281
3572
|
/**
|
|
@@ -3413,6 +3704,7 @@ exports.getCssVariable = getCssVariable;
|
|
|
3413
3704
|
exports.getQueryString = getQueryString;
|
|
3414
3705
|
exports.subscribeToMediaQuery = subscribeToMediaQuery;
|
|
3415
3706
|
exports.useConfirmModal = useConfirmModal;
|
|
3707
|
+
exports.useCountdown = useCountdown;
|
|
3416
3708
|
exports.useDContext = useDContext;
|
|
3417
3709
|
exports.useDPortalContext = useDPortalContext;
|
|
3418
3710
|
exports.useDToast = useDToast;
|
|
@@ -3428,7 +3720,11 @@ exports.useMediaBreakpointUpXl = useMediaBreakpointUpXl;
|
|
|
3428
3720
|
exports.useMediaBreakpointUpXs = useMediaBreakpointUpXs;
|
|
3429
3721
|
exports.useMediaBreakpointUpXxl = useMediaBreakpointUpXxl;
|
|
3430
3722
|
exports.useMediaQuery = useMediaQuery;
|
|
3723
|
+
exports.useOtp = useOtp;
|
|
3431
3724
|
exports.useProvidedRefOrCreate = useProvidedRefOrCreate;
|
|
3725
|
+
exports.useScreenshot = useScreenshot;
|
|
3726
|
+
exports.useScreenshotDownload = useScreenshotDownload;
|
|
3727
|
+
exports.useScreenshotWebShare = useScreenshotWebShare;
|
|
3432
3728
|
exports.useStackState = useStackState;
|
|
3433
3729
|
exports.useTabContext = useTabContext;
|
|
3434
3730
|
exports.validatePhoneNumber = validatePhoneNumber;
|