@dynamic-framework/ui-react 2.7.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 +272 -1
- package/dist/css/dynamic-ui.min.css +2 -2
- package/dist/index.esm.js +503 -267
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +507 -266
- package/dist/index.js.map +1 -1
- package/dist/types/components/DDropdown/DDropdown.d.ts +10 -2
- 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/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 +1 -1
- package/src/style/abstracts/variables/_+import.scss +1 -0
- package/src/style/abstracts/variables/_input-currency.scss +1 -0
- package/src/style/components/_+import.scss +7 -0
- package/src/style/components/_d-box-file.scss +5 -0
- 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.esm.js
CHANGED
|
@@ -1106,7 +1106,7 @@ function DBoxFile(_a) {
|
|
|
1106
1106
|
'd-box-file-invalid': isDragInvalid,
|
|
1107
1107
|
}, className), style: style }, dataAttributes, { children: 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: [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 && (jsx(DIcon, { icon: icon, familyClass: iconFamilyClass, familyPrefix: iconFamilyPrefix, materialStyle: iconMaterialStyle })), jsx("div", { className: "d-box-content", children: typeof children === 'function'
|
|
1108
1108
|
? children(openFileDialog)
|
|
1109
|
-
: children || (jsx("p", { className: "
|
|
1109
|
+
: children || (jsx("p", { className: "d-box-file-empty-text", children: "Drag and drop some files here, or click to select files" })) })] })) })), !!files.length && (jsx("ul", { className: "d-box-files", children: files.map((file, index) => (jsx(ForwardedDInput, { value: file.name, iconStart: "Paperclip", iconEnd: "Trash", readOnly: true, onIconEndClick: () => handleRemoveFile(index) }, `${file.name} ${index}`))) }))] }));
|
|
1110
1110
|
}
|
|
1111
1111
|
|
|
1112
1112
|
const DButton = forwardRef((props, ref) => {
|
|
@@ -1482,9 +1482,9 @@ function DDatePickerHeaderSelector({ date, changeYear, changeMonth, decreaseMont
|
|
|
1482
1482
|
return (jsxs("div", { className: classNames('react-datepicker__header-selector react-datepicker__header-year-selector', className), style: style, children: [jsx(DButtonIcon, { icon: iconPrev || chevronLeft, size: iconSize, variant: "link", onClick: decreaseYear, disabled: prevYearButtonDisabled, "aria-label": prevYearAriaLabel, className: "header-button" }), jsx("p", { children: `${startYear} - ${endYear}` }), jsx(DButtonIcon, { icon: iconNext || chevronRight, size: iconSize, variant: "link", onClick: increaseYear, disabled: nextYearButtonDisabled, "aria-label": nextYearAriaLabel, className: "header-button" })] }));
|
|
1483
1483
|
}
|
|
1484
1484
|
if (pickerType === PickerType.Quarter || pickerType === PickerType.Month) {
|
|
1485
|
-
return (jsxs("div", { className: classNames(`react-datepicker__header-selector react-datepicker__header-${pickerType}-selector`, className), style: style, children: [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' } }), jsx("div", { className: "
|
|
1485
|
+
return (jsxs("div", { className: classNames(`react-datepicker__header-selector react-datepicker__header-${pickerType}-selector`, className), style: style, children: [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' } }), jsx("div", { className: "react-datepicker__header-year-selector", children: showHeaderSelectors ? (jsx(DSelect$1, { options: years, value: defaultYear, defaultValue: defaultYear, onChange: (year) => changeYear(Number(year === null || year === void 0 ? void 0 : year.value)), searchable: false })) : (jsx("p", { children: defaultYear === null || defaultYear === void 0 ? void 0 : defaultYear.label })) }), 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' } })] }));
|
|
1486
1486
|
}
|
|
1487
|
-
return (jsxs(Fragment, { children: [jsxs("div", { className: "datepicker-top-header", children: [showHeaderSelectors && (jsx(DSelect$1, { 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" })), jsx("h4", { className: "
|
|
1487
|
+
return (jsxs(Fragment, { children: [jsxs("div", { className: "datepicker-top-header", children: [showHeaderSelectors && (jsx(DSelect$1, { 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" })), jsx("h4", { className: "datepicker-top-header-title", children: format(monthDate, formatHeaderDate, { locale }) })] }), jsxs("div", { className: classNames('react-datepicker__header-selector react-datepicker__header-day-selector', className), style: style, children: [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 ? (jsx(DSelect$1, { 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" })) : (jsx("p", { children: `${defaultMonth.label} ${defaultYear === null || defaultYear === void 0 ? void 0 : defaultYear.label}` })), 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' } })] })] }));
|
|
1488
1488
|
}
|
|
1489
1489
|
|
|
1490
1490
|
function DDatePicker(_a) {
|
|
@@ -1619,11 +1619,24 @@ function formatValue(value, currencyOptions) {
|
|
|
1619
1619
|
}
|
|
1620
1620
|
return currency(value, Object.assign(Object.assign({}, currencyOptions), { symbol: '' })).format();
|
|
1621
1621
|
}
|
|
1622
|
-
function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref) {
|
|
1622
|
+
function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref, minValue, maxValue) {
|
|
1623
1623
|
const inputRef = useProvidedRefOrCreate(ref);
|
|
1624
|
+
const clampValue = useCallback((newValue) => {
|
|
1625
|
+
if (newValue === undefined) {
|
|
1626
|
+
return newValue;
|
|
1627
|
+
}
|
|
1628
|
+
let clampedValue = newValue;
|
|
1629
|
+
if (minValue !== undefined) {
|
|
1630
|
+
clampedValue = Math.max(clampedValue, minValue);
|
|
1631
|
+
}
|
|
1632
|
+
if (maxValue !== undefined) {
|
|
1633
|
+
clampedValue = Math.min(clampedValue, maxValue);
|
|
1634
|
+
}
|
|
1635
|
+
return clampedValue;
|
|
1636
|
+
}, [minValue, maxValue]);
|
|
1624
1637
|
const [innerType, setInnerType] = useState('text');
|
|
1625
|
-
const [innerNumber, setInnerNumber] = useState(value);
|
|
1626
|
-
const [innerString, setInnerString] = useState(formatValue(value, currencyOptions));
|
|
1638
|
+
const [innerNumber, setInnerNumber] = useState(clampValue(value));
|
|
1639
|
+
const [innerString, setInnerString] = useState(formatValue(clampValue(value), currencyOptions));
|
|
1627
1640
|
const handleOnFocus = useCallback((event) => {
|
|
1628
1641
|
event.stopPropagation();
|
|
1629
1642
|
setInnerType('number');
|
|
@@ -1632,15 +1645,14 @@ function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref
|
|
|
1632
1645
|
const handleOnBlur = useCallback((event) => {
|
|
1633
1646
|
event.stopPropagation();
|
|
1634
1647
|
setInnerType('text');
|
|
1648
|
+
const clampedNumber = clampValue(innerNumber);
|
|
1649
|
+
if (clampedNumber !== innerNumber) {
|
|
1650
|
+
setInnerNumber(clampedNumber);
|
|
1651
|
+
setInnerString(formatValue(clampedNumber, currencyOptions));
|
|
1652
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(clampedNumber);
|
|
1653
|
+
}
|
|
1635
1654
|
onBlur === null || onBlur === void 0 ? void 0 : onBlur(event);
|
|
1636
|
-
}, [onBlur]);
|
|
1637
|
-
const generateStyleVariables = useMemo(() => ({
|
|
1638
|
-
[`--${PREFIX_BS}input-currency-component-symbol-color`]: `var(--${PREFIX_BS}secondary)`,
|
|
1639
|
-
[`--${PREFIX_BS}input-currency-symbol-color`]: `var(--${PREFIX_BS}input-currency-component-symbol-color)`,
|
|
1640
|
-
}), []);
|
|
1641
|
-
const generateSymbolStyleVariables = useMemo(() => ({
|
|
1642
|
-
color: `var(--${PREFIX_BS}input-currency-symbol-color)`,
|
|
1643
|
-
}), []);
|
|
1655
|
+
}, [onBlur, innerNumber, clampValue, currencyOptions, onChange]);
|
|
1644
1656
|
const handleOnChange = useCallback((newValue) => {
|
|
1645
1657
|
const newNumber = (newValue === undefined || newValue === '') ? undefined : Number(newValue);
|
|
1646
1658
|
if (newNumber !== innerNumber) {
|
|
@@ -1649,7 +1661,12 @@ function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref
|
|
|
1649
1661
|
onChange === null || onChange === void 0 ? void 0 : onChange(newNumber);
|
|
1650
1662
|
}
|
|
1651
1663
|
}, [currencyOptions, onChange, innerNumber]);
|
|
1664
|
+
const isMountedRef = useRef(false);
|
|
1652
1665
|
useEffect(() => {
|
|
1666
|
+
if (!isMountedRef.current) {
|
|
1667
|
+
isMountedRef.current = true;
|
|
1668
|
+
return;
|
|
1669
|
+
}
|
|
1653
1670
|
if (value !== innerNumber) {
|
|
1654
1671
|
setInnerNumber(value);
|
|
1655
1672
|
setInnerString(formatValue(value, currencyOptions));
|
|
@@ -1663,8 +1680,6 @@ function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref
|
|
|
1663
1680
|
handleOnFocus,
|
|
1664
1681
|
handleOnChange,
|
|
1665
1682
|
handleOnBlur,
|
|
1666
|
-
generateStyleVariables,
|
|
1667
|
-
generateSymbolStyleVariables,
|
|
1668
1683
|
};
|
|
1669
1684
|
}
|
|
1670
1685
|
|
|
@@ -1769,6 +1784,149 @@ function useConfirmModal(config) {
|
|
|
1769
1784
|
return { open };
|
|
1770
1785
|
}
|
|
1771
1786
|
|
|
1787
|
+
/**
|
|
1788
|
+
* Generic seconds-based countdown hook.
|
|
1789
|
+
*
|
|
1790
|
+
* Starts counting down from `seconds` to `0` and exposes `restartCountdown`
|
|
1791
|
+
* to reset and restart the timer (e.g. for "resend code in Xs" patterns,
|
|
1792
|
+
* session-expiry banners, or any other cooldown/throttle UI).
|
|
1793
|
+
*
|
|
1794
|
+
* @example
|
|
1795
|
+
* const { secondsLeft, restartCountdown } = useCountdown(30);
|
|
1796
|
+
*/
|
|
1797
|
+
function useCountdown(seconds) {
|
|
1798
|
+
const [secondsLeft, setSecondsLeft] = useState(seconds);
|
|
1799
|
+
const [isActive, setIsActive] = useState(true);
|
|
1800
|
+
const resetCountdown = useCallback((newSeconds = seconds) => {
|
|
1801
|
+
setIsActive(false);
|
|
1802
|
+
setSecondsLeft(newSeconds);
|
|
1803
|
+
}, [seconds]);
|
|
1804
|
+
const restartCountdown = useCallback(() => {
|
|
1805
|
+
resetCountdown(seconds);
|
|
1806
|
+
setIsActive(true);
|
|
1807
|
+
}, [resetCountdown, seconds]);
|
|
1808
|
+
useEffect(() => {
|
|
1809
|
+
if (!isActive) {
|
|
1810
|
+
return () => { };
|
|
1811
|
+
}
|
|
1812
|
+
const interval = setInterval(() => {
|
|
1813
|
+
setSecondsLeft((prevSeconds) => {
|
|
1814
|
+
const newSeconds = prevSeconds - 1;
|
|
1815
|
+
if (newSeconds <= 0) {
|
|
1816
|
+
clearInterval(interval);
|
|
1817
|
+
setIsActive(false);
|
|
1818
|
+
return 0;
|
|
1819
|
+
}
|
|
1820
|
+
return newSeconds;
|
|
1821
|
+
});
|
|
1822
|
+
}, 1000);
|
|
1823
|
+
return () => clearInterval(interval);
|
|
1824
|
+
}, [isActive]);
|
|
1825
|
+
return { secondsLeft, restartCountdown };
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
/**
|
|
1829
|
+
* Headless hook exposing the same OTP logic used internally by `DOtp`
|
|
1830
|
+
* (code state, length + correctness validation, submit action and resend
|
|
1831
|
+
* countdown), without any bundled markup/styles.
|
|
1832
|
+
*
|
|
1833
|
+
* Use this when the default `DOtp` component doesn't match your design and
|
|
1834
|
+
* you need to build a fully custom UI while reusing the same behavior.
|
|
1835
|
+
*
|
|
1836
|
+
* Internally composes `useCountdown` for the resend timer and re-exposes
|
|
1837
|
+
* `secondsLeft`/`restartCountdown`, so there is no need to call
|
|
1838
|
+
* `useCountdown` separately.
|
|
1839
|
+
*
|
|
1840
|
+
* `invalid` becomes `true` both when the OTP is shorter than `otpSize` and
|
|
1841
|
+
* when `action` throws/rejects (e.g. the backend rejected the entered code
|
|
1842
|
+
* as incorrect). In the latter case, the thrown value is exposed as `error`
|
|
1843
|
+
* so custom UIs can render an appropriate message (e.g. "Invalid code" vs.
|
|
1844
|
+
* "Enter all digits"). `invalid`/`error` are cleared automatically as soon
|
|
1845
|
+
* as the user edits the code via `setOtp`.
|
|
1846
|
+
*
|
|
1847
|
+
* @example
|
|
1848
|
+
* function CustomOtp() {
|
|
1849
|
+
* const {
|
|
1850
|
+
* otp,
|
|
1851
|
+
* setOtp,
|
|
1852
|
+
* invalid,
|
|
1853
|
+
* error,
|
|
1854
|
+
* submit,
|
|
1855
|
+
* isLoading,
|
|
1856
|
+
* secondsLeft,
|
|
1857
|
+
* restartCountdown,
|
|
1858
|
+
* } = useOtp({ action: async () => verifyOtp(otp), otpSize: 6, seconds: 15 });
|
|
1859
|
+
*
|
|
1860
|
+
* return (
|
|
1861
|
+
* <>
|
|
1862
|
+
* <MyCustomPinInput value={otp} onChange={setOtp} invalid={invalid} />
|
|
1863
|
+
* {invalid && <span>{error ? 'Invalid code' : 'Enter all digits'}</span>}
|
|
1864
|
+
* <button disabled={isLoading} onClick={() => { submit().catch(() => {}); }}>
|
|
1865
|
+
* {isLoading ? 'Submitting...' : 'Submit'}
|
|
1866
|
+
* </button>
|
|
1867
|
+
* <button disabled={secondsLeft > 0} onClick={restartCountdown}>
|
|
1868
|
+
* Resend {secondsLeft > 0 ? `(${secondsLeft}s)` : ''}
|
|
1869
|
+
* </button>
|
|
1870
|
+
* </>
|
|
1871
|
+
* );
|
|
1872
|
+
* }
|
|
1873
|
+
*/
|
|
1874
|
+
function useOtp({ action, otpSize = 6, seconds = 15, }) {
|
|
1875
|
+
const [otp, setOtpValue] = useState('');
|
|
1876
|
+
const [invalid, setInvalid] = useState(false);
|
|
1877
|
+
const [error, setError] = useState(null);
|
|
1878
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
1879
|
+
const { secondsLeft, restartCountdown } = useCountdown(seconds);
|
|
1880
|
+
const setOtp = useCallback((value) => {
|
|
1881
|
+
setOtpValue((prevOtp) => {
|
|
1882
|
+
// Guard against no-op calls (e.g. DInputPin re-notifying the same
|
|
1883
|
+
// value when its onChange reference changes) so we don't wipe out
|
|
1884
|
+
// an `invalid`/`error` state that was just set by submit().
|
|
1885
|
+
if (prevOtp === value) {
|
|
1886
|
+
return prevOtp;
|
|
1887
|
+
}
|
|
1888
|
+
setInvalid(false);
|
|
1889
|
+
setError(null);
|
|
1890
|
+
return value;
|
|
1891
|
+
});
|
|
1892
|
+
}, []);
|
|
1893
|
+
const submit = useCallback(async () => {
|
|
1894
|
+
if (otp.length < otpSize) {
|
|
1895
|
+
setInvalid(true);
|
|
1896
|
+
setError(null);
|
|
1897
|
+
return;
|
|
1898
|
+
}
|
|
1899
|
+
setIsLoading(true);
|
|
1900
|
+
try {
|
|
1901
|
+
await action();
|
|
1902
|
+
setInvalid(false);
|
|
1903
|
+
setError(null);
|
|
1904
|
+
}
|
|
1905
|
+
catch (err) {
|
|
1906
|
+
setInvalid(true);
|
|
1907
|
+
setError(err);
|
|
1908
|
+
throw err;
|
|
1909
|
+
}
|
|
1910
|
+
finally {
|
|
1911
|
+
setIsLoading(false);
|
|
1912
|
+
}
|
|
1913
|
+
}, [
|
|
1914
|
+
otp.length,
|
|
1915
|
+
action,
|
|
1916
|
+
otpSize,
|
|
1917
|
+
]);
|
|
1918
|
+
return {
|
|
1919
|
+
otp,
|
|
1920
|
+
setOtp,
|
|
1921
|
+
invalid,
|
|
1922
|
+
error,
|
|
1923
|
+
submit,
|
|
1924
|
+
isLoading,
|
|
1925
|
+
secondsLeft,
|
|
1926
|
+
restartCountdown,
|
|
1927
|
+
};
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1772
1930
|
function DInputCounter(_a, ref) {
|
|
1773
1931
|
var { minValue, maxValue, value = minValue, invalid, iconStart: iconStartProp, iconEnd: iconEndProp, iconStartAriaLabel = 'decrease action', iconEndAriaLabel = 'increase action', style, onChange } = _a, props = __rest(_a, ["minValue", "maxValue", "value", "invalid", "iconStart", "iconEnd", "iconStartAriaLabel", "iconEndAriaLabel", "style", "onChange"]);
|
|
1774
1932
|
const { handleOnWheel, } = useDisableInputWheel(ref);
|
|
@@ -1811,8 +1969,8 @@ function DInputCurrency(_a, ref) {
|
|
|
1811
1969
|
var { value, minValue, maxValue, currencyCode, onFocus, onBlur, onChange } = _a, props = __rest(_a, ["value", "minValue", "maxValue", "currencyCode", "onFocus", "onBlur", "onChange"]);
|
|
1812
1970
|
const { currency: currencyOptions } = useDContext();
|
|
1813
1971
|
const { handleOnWheel, } = useDisableInputWheel(ref);
|
|
1814
|
-
const { inputRef, innerValue, innerType, handleOnFocus, handleOnChange, handleOnBlur,
|
|
1815
|
-
return (jsx(ForwardedDInput, Object.assign({ ref: inputRef, value: innerValue, onChange: handleOnChange,
|
|
1972
|
+
const { inputRef, innerValue, innerType, handleOnFocus, handleOnChange, handleOnBlur, } = useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref, minValue, maxValue);
|
|
1973
|
+
return (jsx(ForwardedDInput, Object.assign({ ref: inputRef, value: innerValue, onChange: handleOnChange, inputMode: "decimal", type: innerType, onFocus: handleOnFocus, onBlur: handleOnBlur, onWheel: handleOnWheel, inputStart: (jsx("span", { slot: "input-start", className: "d-input-currency-symbol", children: currencyCode || currencyOptions.symbol })) }, props)));
|
|
1816
1974
|
}
|
|
1817
1975
|
const ForwardedDInputCurrency = forwardRef(DInputCurrency);
|
|
1818
1976
|
ForwardedDInputCurrency.displayName = 'DInputCurrency';
|
|
@@ -1834,26 +1992,25 @@ ForwardedDInputPassword.displayName = 'DInputPassword';
|
|
|
1834
1992
|
|
|
1835
1993
|
function PasswordCheckItem({ password, regex, text, }) {
|
|
1836
1994
|
const isValid = regex.test(password);
|
|
1837
|
-
return (jsxs("li", { className:
|
|
1995
|
+
return (jsxs("li", { className: `d-password-check-item${isValid ? ' is-valid' : ''}`, children: [jsx(DIcon, { className: "d-password-check-item-icon", icon: isValid ? 'CircleCheck' : 'Circle', size: "16px" }), jsx("span", { children: text })] }));
|
|
1838
1996
|
}
|
|
1839
1997
|
|
|
1840
|
-
const
|
|
1998
|
+
const getStrengthModifier = (strength, total) => {
|
|
1841
1999
|
const percentage = total > 0 ? strength / total : 0;
|
|
1842
2000
|
if (percentage === 0)
|
|
1843
|
-
return '
|
|
2001
|
+
return 'is-empty';
|
|
1844
2002
|
if (percentage <= 0.25)
|
|
1845
|
-
return '
|
|
2003
|
+
return 'is-weak';
|
|
1846
2004
|
if (percentage <= 0.5)
|
|
1847
|
-
return '
|
|
2005
|
+
return 'is-fair';
|
|
1848
2006
|
if (percentage <= 0.75)
|
|
1849
|
-
return '
|
|
1850
|
-
return '
|
|
2007
|
+
return 'is-good';
|
|
2008
|
+
return 'is-strong';
|
|
1851
2009
|
};
|
|
1852
2010
|
function PasswordStrengthBar({ strength, total }) {
|
|
1853
2011
|
const percentage = total > 0 ? (strength / total) * 100 : 0;
|
|
1854
|
-
return (jsx("div", { className: "
|
|
2012
|
+
return (jsx("div", { className: "d-password-strength-bar", children: jsx("div", { className: `d-password-strength-bar-fill ${getStrengthModifier(strength, total)}`, style: {
|
|
1855
2013
|
width: `${percentage}%`,
|
|
1856
|
-
transition: 'width 0.3s ease-in-out',
|
|
1857
2014
|
} }) }));
|
|
1858
2015
|
}
|
|
1859
2016
|
|
|
@@ -1889,7 +2046,7 @@ function PasswordChecksList({ password, validationMessages, enabledChecks, }) {
|
|
|
1889
2046
|
const passwordChecks = allChecks.filter((check) => enabledChecks.includes(check.key));
|
|
1890
2047
|
const passed = passwordChecks.filter((r) => r.regex.test(password)).length;
|
|
1891
2048
|
const total = passwordChecks.length;
|
|
1892
|
-
return (jsxs("div", { className: "
|
|
2049
|
+
return (jsxs("div", { className: "d-password-strength-meter", children: [jsx(PasswordStrengthBar, { strength: passed, total: total }), jsx("ul", { className: "d-password-check-list", children: passwordChecks.map(({ key, regex, text }) => (jsx(PasswordCheckItem, { password: password, regex: regex, text: text }, key))) })] }));
|
|
1893
2050
|
}
|
|
1894
2051
|
|
|
1895
2052
|
const DEFAULT_VALIDATION_MESSAGES = {
|
|
@@ -2239,7 +2396,7 @@ var DListGroup$1 = Object.assign(DListGroup, {
|
|
|
2239
2396
|
function DModalHeader({ showCloseButton, onClose, children, className, style, iconFamilyClass, iconFamilyPrefix, icon: iconProp, materialStyle = false, }) {
|
|
2240
2397
|
const { iconMap: { xLg, }, } = useDContext();
|
|
2241
2398
|
const icon = useMemo(() => iconProp || xLg, [iconProp, xLg]);
|
|
2242
|
-
return (jsxs(Fragment, { children: [jsxs("div", { className: classNames('modal-header', className), style: style, children: [jsx("div", { children: children }), showCloseButton && (jsx("button", { type: "button", className: "d-close
|
|
2399
|
+
return (jsxs(Fragment, { children: [jsxs("div", { className: classNames('modal-header', className), style: style, children: [jsx("div", { children: children }), showCloseButton && (jsx("button", { type: "button", className: "d-close", "aria-label": "Close", onClick: onClose, children: jsx(DIcon, { icon: icon, familyClass: iconFamilyClass, familyPrefix: iconFamilyPrefix, materialStyle: materialStyle }) }))] }), jsx("div", { className: "d-modal-separator" })] }));
|
|
2243
2400
|
}
|
|
2244
2401
|
|
|
2245
2402
|
function DModalBody({ children, className, style, }) {
|
|
@@ -2283,7 +2440,7 @@ var DModal$1 = Object.assign(DModal, {
|
|
|
2283
2440
|
function DOffcanvasHeader({ showCloseButton, onClose, children, className, style, iconFamilyClass, iconFamilyPrefix, icon: iconProp, materialStyle = false, }) {
|
|
2284
2441
|
const { iconMap: { xLg, }, } = useDContext();
|
|
2285
2442
|
const icon = useMemo(() => iconProp || xLg, [iconProp, xLg]);
|
|
2286
|
-
return (jsxs(Fragment, { children: [jsxs("div", { className: classNames('offcanvas-header', className), style: style, children: [jsx("div", { children: children }), showCloseButton && (jsx("button", { type: "button", className: "d-close
|
|
2443
|
+
return (jsxs(Fragment, { children: [jsxs("div", { className: classNames('offcanvas-header', className), style: style, children: [jsx("div", { children: children }), showCloseButton && (jsx("button", { type: "button", className: "d-close", "aria-label": "Close", onClick: onClose, children: jsx(DIcon, { icon: icon, familyClass: iconFamilyClass, familyPrefix: iconFamilyPrefix, materialStyle: materialStyle }) }))] }), jsx("div", { className: "d-offcanvas-separator" })] }));
|
|
2287
2444
|
}
|
|
2288
2445
|
|
|
2289
2446
|
function DOffcanvasBody({ children, className, style, }) {
|
|
@@ -2410,7 +2567,7 @@ function DPopover({ children, renderComponent, open, setOpen, adjustContentToRen
|
|
|
2410
2567
|
}
|
|
2411
2568
|
const reference = cloneElement(triggerElement, Object.assign({ ref: refs.setReference }, getReferenceProps(triggerElement.props)));
|
|
2412
2569
|
return (jsxs("div", Object.assign({ className: classNames('d-popover', className), style: generateStyleVariables }, dataAttributes, { children: [reference, isOpen && (jsx(FloatingFocusManager, { context: context, modal: false, children: jsx("div", Object.assign({ className: classNames('d-popover-content', {
|
|
2413
|
-
'
|
|
2570
|
+
'd-popover-content-fill': adjustContentToRender,
|
|
2414
2571
|
}), ref: refs.setFloating, style: floatingStyles, "aria-labelledby": headingId }, getFloatingProps(), { children: children })) }))] })));
|
|
2415
2572
|
}
|
|
2416
2573
|
|
|
@@ -2474,13 +2631,13 @@ function DStepper$1({ options, currentStep, className, style, }) {
|
|
|
2474
2631
|
}
|
|
2475
2632
|
|
|
2476
2633
|
function DStepper({ options, currentStep, iconSuccess, iconSuccessFamilyClass, iconSuccessFamilyPrefix, iconSuccessMaterialStyle = false, vertical = false, breakpoint = 'lg', className, completed = false, style, dataAttributes, }) {
|
|
2477
|
-
return (jsxs("div", Object.assign({ className: className, style: style }, dataAttributes, { children: [jsx("div", { className: `d-block d-${breakpoint}-none
|
|
2634
|
+
return (jsxs("div", Object.assign({ className: className, style: style }, dataAttributes, { children: [jsx("div", { className: classNames('d-stepper-mobile', `d-block d-${breakpoint}-none`), children: jsx(DStepper$1, { options: options, currentStep: currentStep }) }), jsx("div", { className: classNames('d-stepper-desktop', `d-none d-${breakpoint}-block`), children: jsx(DStepper$2, { options: options, currentStep: currentStep, vertical: vertical, iconSuccess: iconSuccess, iconSuccessFamilyClass: iconSuccessFamilyClass, iconSuccessFamilyPrefix: iconSuccessFamilyPrefix, iconSuccessMaterialStyle: iconSuccessMaterialStyle, completed: completed }) })] })));
|
|
2478
2635
|
}
|
|
2479
2636
|
|
|
2480
2637
|
const ARROW_WIDTH = 8;
|
|
2481
2638
|
const ARROW_HEIGHT = 4;
|
|
2482
|
-
const GAP = 2;
|
|
2483
|
-
function DTooltip({ className, childrenClassName, style, offSet = ARROW_HEIGHT + GAP, padding, withFocus = false, withClick = false, withHover = true, open = false, placement = 'top', size, Component, children, }) {
|
|
2639
|
+
const GAP$1 = 2;
|
|
2640
|
+
function DTooltip({ className, childrenClassName, style, offSet = ARROW_HEIGHT + GAP$1, padding, withFocus = false, withClick = false, withHover = true, open = false, placement = 'top', size, Component, children, }) {
|
|
2484
2641
|
const [isOpen, setIsOpen] = useState(open);
|
|
2485
2642
|
const arrowRef = useRef(null);
|
|
2486
2643
|
const { refs, context, floatingStyles, } = useFloating({
|
|
@@ -2549,8 +2706,16 @@ function DTabs({ children, defaultSelected, onChange, options, className, classN
|
|
|
2549
2706
|
useEffect(() => {
|
|
2550
2707
|
setSelected(defaultSelected);
|
|
2551
2708
|
}, [defaultSelected]);
|
|
2552
|
-
const generateClasses = useMemo(() => (Object.assign({ nav: true, '
|
|
2709
|
+
const generateClasses = useMemo(() => (Object.assign({ nav: true, 'd-tabs-nav-vertical': vertical && variant !== 'tabs', [`nav-${variant}`]: true }, className && { [className]: true })), [vertical, variant, className]);
|
|
2553
2710
|
const tabRefs = useRef([]);
|
|
2711
|
+
// Always holds the latest `options` without needing to be a dependency:
|
|
2712
|
+
// `options` is commonly passed as an inline array literal (e.g.
|
|
2713
|
+
// `options={[{ label: 'SMS', tab: 'sms' }, ...]}`), so it's a new array
|
|
2714
|
+
// reference on every parent render even when its content hasn't changed.
|
|
2715
|
+
// Reading it from this ref (updated synchronously on every render) lets
|
|
2716
|
+
// the focus effect below react only to `selected` changing.
|
|
2717
|
+
const optionsRef = useRef(options);
|
|
2718
|
+
optionsRef.current = options;
|
|
2554
2719
|
useEffect(() => {
|
|
2555
2720
|
tabRefs.current = options.map((_, i) => tabRefs.current[i] || createRef());
|
|
2556
2721
|
}, [options]);
|
|
@@ -2565,20 +2730,28 @@ function DTabs({ children, defaultSelected, onChange, options, className, classN
|
|
|
2565
2730
|
setSelected(firstEnabled.tab);
|
|
2566
2731
|
}
|
|
2567
2732
|
}, [options, selected]);
|
|
2568
|
-
// Declarative focus management
|
|
2569
|
-
|
|
2733
|
+
// Declarative focus management. Wrapped in `useCallback` with an empty
|
|
2734
|
+
// dependency array since it only reads from the `tabRefs` ref, so its
|
|
2735
|
+
// identity stays stable across renders and it can safely be used as an
|
|
2736
|
+
// effect dependency below.
|
|
2737
|
+
const focusTab = useCallback((idx) => {
|
|
2570
2738
|
var _a;
|
|
2571
2739
|
if ((_a = tabRefs.current[idx]) === null || _a === void 0 ? void 0 : _a.current) {
|
|
2572
2740
|
tabRefs.current[idx].current.focus();
|
|
2573
2741
|
}
|
|
2574
|
-
};
|
|
2575
|
-
// Focus selected tab when selected changes
|
|
2742
|
+
}, []);
|
|
2743
|
+
// Focus selected tab when selected changes.
|
|
2744
|
+
// Reads `options` from `optionsRef` (see comment above) instead of
|
|
2745
|
+
// depending on `options` directly, so a parent re-render that merely
|
|
2746
|
+
// creates a new `options` reference with identical content doesn't call
|
|
2747
|
+
// `focusTab` again and steal focus away from unrelated elements on the
|
|
2748
|
+
// page (e.g. an OTP input).
|
|
2576
2749
|
useEffect(() => {
|
|
2577
|
-
const idx =
|
|
2750
|
+
const idx = optionsRef.current.findIndex((opt) => opt.tab === selected && !opt.disabled);
|
|
2578
2751
|
if (idx !== -1) {
|
|
2579
2752
|
focusTab(idx);
|
|
2580
2753
|
}
|
|
2581
|
-
}, [selected,
|
|
2754
|
+
}, [selected, focusTab]);
|
|
2582
2755
|
const handleKeyDown = useCallback((idx, e) => {
|
|
2583
2756
|
const count = options.length;
|
|
2584
2757
|
if (count === 0)
|
|
@@ -2607,7 +2780,7 @@ function DTabs({ children, defaultSelected, onChange, options, className, classN
|
|
|
2607
2780
|
}
|
|
2608
2781
|
}
|
|
2609
2782
|
}
|
|
2610
|
-
}, [options, vertical]);
|
|
2783
|
+
}, [options, vertical, focusTab]);
|
|
2611
2784
|
let tablistProps = {};
|
|
2612
2785
|
if (ariaLabelledBy) {
|
|
2613
2786
|
tablistProps = { 'aria-labelledby': ariaLabelledBy };
|
|
@@ -2620,12 +2793,12 @@ function DTabs({ children, defaultSelected, onChange, options, className, classN
|
|
|
2620
2793
|
isSelected,
|
|
2621
2794
|
}), [isSelected]);
|
|
2622
2795
|
return (jsx(TabContext.Provider, { value: value, children: jsxs("div", Object.assign({ className: classNames({
|
|
2623
|
-
'd-
|
|
2624
|
-
'
|
|
2796
|
+
'd-tabs': true,
|
|
2797
|
+
'd-tabs-column': !vertical || variant === 'tabs',
|
|
2625
2798
|
}), style: style }, dataAttributes, { children: [jsx("ul", Object.assign({ className: classNames(generateClasses), role: "tablist", "aria-orientation": vertical ? 'vertical' : undefined }, tablistProps, { children: options.map((option, idx) => {
|
|
2626
2799
|
const isTabSelected = !!option.tab && option.tab === selected;
|
|
2627
2800
|
return (jsx("li", { role: "presentation", className: "nav-item", children: 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));
|
|
2628
|
-
}) })), jsx("div", { className: "
|
|
2801
|
+
}) })), jsx("div", { className: "d-tabs-content tab-content", children: children })] })) }));
|
|
2629
2802
|
}
|
|
2630
2803
|
var DTabs$1 = Object.assign(DTabs, {
|
|
2631
2804
|
Tab: DTabContent,
|
|
@@ -2687,11 +2860,11 @@ function useDToast() {
|
|
|
2687
2860
|
if (!description) {
|
|
2688
2861
|
return (jsx(DToast$1, { className: classNames({
|
|
2689
2862
|
[`toast-${color}`]: !!color,
|
|
2690
|
-
}, 'show'), children: jsxs(DToast$1.Body, { children: [icon && (jsx(DIcon, { className: "toast-icon", icon: icon })), jsx("p", { className: "toast-title", children: title }), jsx("button", { type: "button", className: "d-close
|
|
2863
|
+
}, 'show'), children: jsxs(DToast$1.Body, { children: [icon && (jsx(DIcon, { className: "toast-icon", icon: icon })), jsx("p", { className: "toast-title", children: title }), jsx("button", { type: "button", className: "d-close", "aria-label": "Close", onClick: () => toast.dismiss(id), children: jsx(DIcon, { icon: closeIcon || xLg }) })] }) }));
|
|
2691
2864
|
}
|
|
2692
2865
|
return (jsxs(DToast$1, { className: classNames({
|
|
2693
2866
|
[`toast-${color}`]: !!color,
|
|
2694
|
-
}, 'show'), children: [jsxs(DToast$1.Header, { children: [icon && (jsx(DIcon, { className: "toast-icon", icon: icon })), jsx("p", { className: "toast-title", children: title }), timestamp && (jsx("small", { className: "toast-timestamp", children: timestamp })), jsx("button", { type: "button", className: "d-close
|
|
2867
|
+
}, 'show'), children: [jsxs(DToast$1.Header, { children: [icon && (jsx(DIcon, { className: "toast-icon", icon: icon })), jsx("p", { className: "toast-title", children: title }), timestamp && (jsx("small", { className: "toast-timestamp", children: timestamp })), jsx("button", { type: "button", className: "d-close", "aria-label": "Close", onClick: () => toast.dismiss(id), children: jsx(DIcon, { icon: closeIcon || xLg }) })] }), jsx(DToast$1.Body, { children: jsx("span", { children: description }) })] }));
|
|
2695
2868
|
}, toastProps);
|
|
2696
2869
|
}, [xLg]);
|
|
2697
2870
|
return {
|
|
@@ -2713,11 +2886,10 @@ async function configureI8n(resources, _a = {}) {
|
|
|
2713
2886
|
.then((t) => t);
|
|
2714
2887
|
}
|
|
2715
2888
|
|
|
2716
|
-
function getQueryString(value, config = {
|
|
2717
|
-
useSearch
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
return urlParams.get(value) || config.default;
|
|
2889
|
+
function getQueryString(value, config = {}) {
|
|
2890
|
+
const { useSearch = true, default: defaultValue } = config;
|
|
2891
|
+
const urlParams = new URLSearchParams(useSearch ? window.location.search : '');
|
|
2892
|
+
return urlParams.get(value) || defaultValue;
|
|
2721
2893
|
}
|
|
2722
2894
|
|
|
2723
2895
|
function changeQueryString(values, { useSearch = true, pushState = false, } = {}) {
|
|
@@ -2840,89 +3012,228 @@ function DCreditCard({ brand = 'visa', name, number, holderText = 'Card Holder',
|
|
|
2840
3012
|
return (jsxs("div", { className: classNames('d-credit-card', isVertical && 'is-vertical', className), children: [jsxs("div", { className: "d-credit-card-header", children: [jsx("img", { src: logoImage || BRAND_LOGOS[brand] || DEFAULT_IMAGE, alt: brand, className: "d-credit-card-logo", width: 100 }), isChipVisible && (jsx("div", { className: "d-credit-card-chip", children: jsx("img", { src: CHIP_IMAGE, alt: "chip", width: 30, className: "d-credit-card-chip-image" }) }))] }), jsxs("div", { className: "d-credit-card-details", children: [jsx("div", { className: "d-credit-card-number", children: number }), jsx("small", { className: "d-credit-card-holder-text", children: holderText }), jsx("span", { className: "d-credit-card-name", children: name })] })] }));
|
|
2841
3013
|
}
|
|
2842
3014
|
|
|
3015
|
+
// Gap between the toggle and the menu.
|
|
3016
|
+
const GAP = 4;
|
|
3017
|
+
// Minimum distance the menu should keep from the viewport edges.
|
|
3018
|
+
const VIEWPORT_PADDING = 8;
|
|
3019
|
+
// Minimum usable height left for the menu before we consider a side unusable.
|
|
3020
|
+
const MIN_USABLE_SPACE = 120;
|
|
2843
3021
|
const getItemClass = (action) => classNames({
|
|
2844
3022
|
'dropdown-item d-flex align-items-center': true,
|
|
2845
3023
|
[`dropdown-item-${action.color}`]: !!action.color,
|
|
2846
3024
|
disabled: action.disabled,
|
|
2847
3025
|
});
|
|
2848
|
-
|
|
3026
|
+
const clamp = (value, min, max) => {
|
|
3027
|
+
if (max < min)
|
|
3028
|
+
return min;
|
|
3029
|
+
return Math.min(Math.max(value, min), max);
|
|
3030
|
+
};
|
|
3031
|
+
/**
|
|
3032
|
+
* `position: fixed` is normally relative to the viewport, but any ancestor
|
|
3033
|
+
* with a `transform`, `perspective`, `filter`, `will-change` (of those) or a
|
|
3034
|
+
* non-trivial `contain` establishes a new containing block, making `fixed`
|
|
3035
|
+
* behave like `absolute` relative to that ancestor instead (e.g. Storybook's
|
|
3036
|
+
* docs canvas wrapper, animated cards, sticky containers, etc.). We detect
|
|
3037
|
+
* that ancestor so the computed coordinates can be adjusted accordingly,
|
|
3038
|
+
* keeping the menu correctly anchored to the toggle in every context.
|
|
3039
|
+
*/
|
|
3040
|
+
const getFixedPositioningOffset = (fromEl) => {
|
|
3041
|
+
var _a;
|
|
3042
|
+
let node = (_a = fromEl === null || fromEl === void 0 ? void 0 : fromEl.parentElement) !== null && _a !== void 0 ? _a : null;
|
|
3043
|
+
while (node && node !== document.body) {
|
|
3044
|
+
const cs = window.getComputedStyle(node);
|
|
3045
|
+
// Treat empty/undefined values as "unset" (jsdom returns "" rather than
|
|
3046
|
+
// the CSS initial value like "none" for some of these properties).
|
|
3047
|
+
const isSet = (value) => !!value && value !== 'none';
|
|
3048
|
+
const establishesContainingBlock = isSet(cs.transform)
|
|
3049
|
+
|| isSet(cs.perspective)
|
|
3050
|
+
|| isSet(cs.filter)
|
|
3051
|
+
|| isSet(cs.backdropFilter)
|
|
3052
|
+
|| ['transform', 'perspective', 'filter'].some((prop) => { var _a; return (_a = cs.willChange) === null || _a === void 0 ? void 0 : _a.includes(prop); })
|
|
3053
|
+
|| cs.contain.split(' ').some((value) => ['layout', 'paint', 'strict', 'content'].includes(value));
|
|
3054
|
+
if (establishesContainingBlock) {
|
|
3055
|
+
const rect = node.getBoundingClientRect();
|
|
3056
|
+
return { top: rect.top, left: rect.left };
|
|
3057
|
+
}
|
|
3058
|
+
node = node.parentElement;
|
|
3059
|
+
}
|
|
3060
|
+
return { top: 0, left: 0 };
|
|
3061
|
+
};
|
|
3062
|
+
/**
|
|
3063
|
+
* Resolves where the menu should render (flip) and where it should sit along
|
|
3064
|
+
* the cross axis (shift), so it always stays fully inside the viewport
|
|
3065
|
+
* regardless of where the toggle is placed or how the viewport is resized.
|
|
3066
|
+
* This mimics floating-ui's `flip` + `shift` middlewares without adding the
|
|
3067
|
+
* dependency, keeping the existing Bootstrap markup/classes.
|
|
3068
|
+
*/
|
|
3069
|
+
const computeCoords = (toggleRect, menuSize, placement, alignment) => {
|
|
3070
|
+
const viewportWidth = window.innerWidth;
|
|
3071
|
+
const viewportHeight = window.innerHeight;
|
|
3072
|
+
const spaceBelow = viewportHeight - toggleRect.bottom;
|
|
3073
|
+
const spaceAbove = toggleRect.top;
|
|
3074
|
+
const spaceLeft = toggleRect.left;
|
|
3075
|
+
const spaceRight = viewportWidth - toggleRect.right;
|
|
3076
|
+
let side = placement === 'auto'
|
|
3077
|
+
? 'down'
|
|
3078
|
+
: placement;
|
|
3079
|
+
if (placement === 'auto') {
|
|
3080
|
+
side = spaceBelow >= menuSize.height + GAP || spaceBelow >= spaceAbove ? 'down' : 'up';
|
|
3081
|
+
}
|
|
3082
|
+
// Flip within the current axis if the chosen side can't fit the menu but
|
|
3083
|
+
// the opposite side (same axis) can.
|
|
3084
|
+
if (side === 'down' && spaceBelow < menuSize.height + GAP && spaceAbove > spaceBelow) {
|
|
3085
|
+
side = 'up';
|
|
3086
|
+
}
|
|
3087
|
+
else if (side === 'up' && spaceAbove < menuSize.height + GAP && spaceBelow > spaceAbove) {
|
|
3088
|
+
side = 'down';
|
|
3089
|
+
}
|
|
3090
|
+
else if (side === 'start' && spaceLeft < menuSize.width + GAP && spaceRight > spaceLeft) {
|
|
3091
|
+
side = 'end';
|
|
3092
|
+
}
|
|
3093
|
+
else if (side === 'end' && spaceRight < menuSize.width + GAP && spaceLeft > spaceRight) {
|
|
3094
|
+
side = 'start';
|
|
3095
|
+
}
|
|
3096
|
+
// Cross-axis fallback: e.g. a horizontally-centered toggle on a narrow
|
|
3097
|
+
// viewport can't open sideways (neither `start` nor `end` fit), but there
|
|
3098
|
+
// is plenty of room above/below — switch the whole axis in that case
|
|
3099
|
+
// (and vice versa for `down`/`up` squeezed against a short viewport).
|
|
3100
|
+
const bestVerticalSpace = Math.max(spaceBelow, spaceAbove);
|
|
3101
|
+
const bestHorizontalSpace = Math.max(spaceLeft, spaceRight);
|
|
3102
|
+
if (side === 'down' || side === 'up') {
|
|
3103
|
+
if (bestVerticalSpace < MIN_USABLE_SPACE && bestHorizontalSpace > bestVerticalSpace) {
|
|
3104
|
+
side = spaceRight >= spaceLeft ? 'end' : 'start';
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
else if (bestHorizontalSpace < menuSize.width + GAP
|
|
3108
|
+
&& bestVerticalSpace > bestHorizontalSpace) {
|
|
3109
|
+
side = spaceBelow >= spaceAbove ? 'down' : 'up';
|
|
3110
|
+
}
|
|
3111
|
+
if (side === 'down' || side === 'up') {
|
|
3112
|
+
const top = side === 'down'
|
|
3113
|
+
? toggleRect.bottom + GAP
|
|
3114
|
+
: toggleRect.top - menuSize.height - GAP;
|
|
3115
|
+
// Align horizontally per the requested `alignment` (start/end/center), then
|
|
3116
|
+
// fall back to the opposite edge if the preferred alignment would overflow
|
|
3117
|
+
// the viewport, and clamp as a final safety net so it never overflows.
|
|
3118
|
+
let left;
|
|
3119
|
+
if (alignment === 'end') {
|
|
3120
|
+
left = toggleRect.right - menuSize.width;
|
|
3121
|
+
}
|
|
3122
|
+
else if (alignment === 'center') {
|
|
3123
|
+
left = toggleRect.left + (toggleRect.width / 2) - (menuSize.width / 2);
|
|
3124
|
+
}
|
|
3125
|
+
else {
|
|
3126
|
+
left = toggleRect.left;
|
|
3127
|
+
}
|
|
3128
|
+
if (left + menuSize.width > viewportWidth - VIEWPORT_PADDING && alignment !== 'end') {
|
|
3129
|
+
const altLeft = toggleRect.right - menuSize.width;
|
|
3130
|
+
if (altLeft >= VIEWPORT_PADDING)
|
|
3131
|
+
left = altLeft;
|
|
3132
|
+
}
|
|
3133
|
+
else if (left < VIEWPORT_PADDING && alignment !== 'start') {
|
|
3134
|
+
const altLeft = toggleRect.left;
|
|
3135
|
+
if (altLeft + menuSize.width <= viewportWidth - VIEWPORT_PADDING)
|
|
3136
|
+
left = altLeft;
|
|
3137
|
+
}
|
|
3138
|
+
left = clamp(left, VIEWPORT_PADDING, Math.max(viewportWidth - menuSize.width - VIEWPORT_PADDING, VIEWPORT_PADDING));
|
|
3139
|
+
const availableHeight = side === 'down' ? spaceBelow : spaceAbove;
|
|
3140
|
+
// Cap strictly to the space actually available on the resolved side —
|
|
3141
|
+
// MIN_USABLE_SPACE is only meant to influence the flip/shift decision
|
|
3142
|
+
// above, not to force a taller box than what physically fits and
|
|
3143
|
+
// overflow the viewport.
|
|
3144
|
+
const maxUsableHeight = Math.max(availableHeight - GAP - VIEWPORT_PADDING, 0);
|
|
3145
|
+
return {
|
|
3146
|
+
top,
|
|
3147
|
+
left,
|
|
3148
|
+
minWidth: Math.max(toggleRect.width, 160),
|
|
3149
|
+
maxHeight: `${maxUsableHeight}px`,
|
|
3150
|
+
resolvedSide: side,
|
|
3151
|
+
};
|
|
3152
|
+
}
|
|
3153
|
+
// side is 'start' or 'end'
|
|
3154
|
+
let left = side === 'start'
|
|
3155
|
+
? toggleRect.left - menuSize.width - GAP
|
|
3156
|
+
: toggleRect.right + GAP;
|
|
3157
|
+
// Safety-net clamp: in rare cases where the toggle is squeezed so tightly
|
|
3158
|
+
// that no side of any axis has enough room, keep the menu inside the
|
|
3159
|
+
// viewport rather than letting it render off-screen.
|
|
3160
|
+
left = clamp(left, VIEWPORT_PADDING, Math.max(viewportWidth - menuSize.width - VIEWPORT_PADDING, VIEWPORT_PADDING));
|
|
3161
|
+
// Shift vertically: align with the toggle's top edge by default, but switch
|
|
3162
|
+
// to the bottom edge (or clamp) if that would overflow the viewport.
|
|
3163
|
+
let { top } = toggleRect;
|
|
3164
|
+
if (top + menuSize.height > viewportHeight - VIEWPORT_PADDING) {
|
|
3165
|
+
top = toggleRect.bottom - menuSize.height;
|
|
3166
|
+
}
|
|
3167
|
+
top = clamp(top, VIEWPORT_PADDING, Math.max(viewportHeight - menuSize.height - VIEWPORT_PADDING, VIEWPORT_PADDING));
|
|
3168
|
+
return {
|
|
3169
|
+
top,
|
|
3170
|
+
left,
|
|
3171
|
+
minWidth: Math.max(toggleRect.width, 160),
|
|
3172
|
+
maxHeight: `${Math.max(viewportHeight - top - VIEWPORT_PADDING, 0)}px`,
|
|
3173
|
+
resolvedSide: side,
|
|
3174
|
+
};
|
|
3175
|
+
};
|
|
3176
|
+
function DDropdown({ actions, dropdownToggle, className, classNameMenu, asPortal = false, placement = 'auto', alignment = 'start', }) {
|
|
2849
3177
|
const [open, setOpen] = useState(false);
|
|
2850
|
-
const [
|
|
2851
|
-
|
|
2852
|
-
//
|
|
2853
|
-
|
|
2854
|
-
//
|
|
3178
|
+
const [coords, setCoords] = useState(null);
|
|
3179
|
+
// Initialize from `placement` (falling back to 'down' for 'auto') so the
|
|
3180
|
+
// wrapper's `drop-*` class reflects the requested side even before the
|
|
3181
|
+
// first open/measurement, matching the previous `drop-${placement}` class
|
|
3182
|
+
// behavior (relevant for Bootstrap caret/orientation styling).
|
|
3183
|
+
const [resolvedSide, setResolvedSide] = useState(placement === 'auto' ? 'down' : placement);
|
|
3184
|
+
// Wraps the toggle in both modes — used to read its viewport-relative rect.
|
|
2855
3185
|
const toggleRef = useRef(null);
|
|
2856
|
-
// Ref on the rendered <ul> — used for outside-click detection
|
|
3186
|
+
// Ref on the rendered <ul> — used to measure its size and for outside-click detection.
|
|
2857
3187
|
const menuRef = useRef(null);
|
|
2858
3188
|
const toggle = () => {
|
|
2859
|
-
|
|
3189
|
+
setCoords(null);
|
|
2860
3190
|
setOpen((prev) => !prev);
|
|
2861
3191
|
};
|
|
2862
|
-
|
|
2863
|
-
//
|
|
2864
|
-
const
|
|
2865
|
-
if (!toggleRef.current)
|
|
3192
|
+
// Measures the toggle + menu and (re)computes fixed, pixel-perfect coords
|
|
3193
|
+
// with collision detection, so the menu never overflows the viewport.
|
|
3194
|
+
const updatePosition = useCallback(() => {
|
|
3195
|
+
if (!toggleRef.current || !menuRef.current)
|
|
3196
|
+
return;
|
|
3197
|
+
const toggleRect = toggleRef.current.getBoundingClientRect();
|
|
3198
|
+
// If the toggle itself has scrolled completely out of the viewport
|
|
3199
|
+
// (regardless of `placement`), close the menu instead of leaving it
|
|
3200
|
+
// rendered detached from the toggle that opened it.
|
|
3201
|
+
const toggleIsMeasured = toggleRect.width > 0 || toggleRect.height > 0;
|
|
3202
|
+
const toggleIsOutOfView = toggleRect.bottom <= 0
|
|
3203
|
+
|| toggleRect.top >= window.innerHeight
|
|
3204
|
+
|| toggleRect.right <= 0
|
|
3205
|
+
|| toggleRect.left >= window.innerWidth;
|
|
3206
|
+
if (toggleIsMeasured && toggleIsOutOfView) {
|
|
3207
|
+
setOpen(false);
|
|
2866
3208
|
return;
|
|
2867
|
-
const rect = toggleRef.current.getBoundingClientRect();
|
|
2868
|
-
const spaceRight = window.innerWidth - rect.right;
|
|
2869
|
-
const spaceBottom = window.innerHeight - rect.bottom;
|
|
2870
|
-
const spaceTop = rect.top;
|
|
2871
|
-
let resolvedPlacement;
|
|
2872
|
-
if (placement === 'auto') {
|
|
2873
|
-
if (spaceBottom < 150) {
|
|
2874
|
-
resolvedPlacement = 'up';
|
|
2875
|
-
}
|
|
2876
|
-
else if (spaceRight < 150) {
|
|
2877
|
-
resolvedPlacement = 'start';
|
|
2878
|
-
}
|
|
2879
|
-
else {
|
|
2880
|
-
resolvedPlacement = 'down';
|
|
2881
|
-
}
|
|
2882
|
-
}
|
|
2883
|
-
else {
|
|
2884
|
-
resolvedPlacement = placement;
|
|
2885
|
-
}
|
|
2886
|
-
const openStart = resolvedPlacement === 'start';
|
|
2887
|
-
const openEnd = resolvedPlacement === 'end';
|
|
2888
|
-
const openUp = resolvedPlacement === 'up';
|
|
2889
|
-
let availableHeight = spaceBottom;
|
|
2890
|
-
if (openUp) {
|
|
2891
|
-
availableHeight = spaceTop;
|
|
2892
|
-
}
|
|
2893
|
-
else if (openStart || openEnd) {
|
|
2894
|
-
availableHeight = window.innerHeight - rect.top;
|
|
2895
|
-
}
|
|
2896
|
-
const verticalCoords = openUp
|
|
2897
|
-
? { bottom: window.innerHeight - rect.top + 4 }
|
|
2898
|
-
: { top: openStart || openEnd ? rect.top : rect.bottom + 4 };
|
|
2899
|
-
let horizontalCoords;
|
|
2900
|
-
if (openStart) {
|
|
2901
|
-
horizontalCoords = { right: window.innerWidth - rect.left + 4 };
|
|
2902
|
-
}
|
|
2903
|
-
else if (openEnd) {
|
|
2904
|
-
horizontalCoords = { left: rect.right + 4 };
|
|
2905
|
-
}
|
|
2906
|
-
else {
|
|
2907
|
-
horizontalCoords = { left: rect.left };
|
|
2908
3209
|
}
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
const
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
};
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
if (
|
|
3210
|
+
const menuRect = menuRef.current.getBoundingClientRect();
|
|
3211
|
+
const _a = computeCoords(toggleRect, { width: menuRect.width, height: menuRect.height }, placement, alignment), { resolvedSide: side } = _a, menuCoords = __rest(_a, ["resolvedSide"]);
|
|
3212
|
+
// In portal mode the menu is appended to document.body, whose containing
|
|
3213
|
+
// block for `fixed` is always the viewport (no adjustment needed). In
|
|
3214
|
+
// inline mode the menu stays next to the toggle, so we must look for a
|
|
3215
|
+
// containing-block-establishing ancestor starting from the toggle.
|
|
3216
|
+
const offset = asPortal
|
|
3217
|
+
? { top: 0, left: 0 }
|
|
3218
|
+
: getFixedPositioningOffset(toggleRef.current);
|
|
3219
|
+
setResolvedSide(side);
|
|
3220
|
+
setCoords(Object.assign(Object.assign({}, menuCoords), { top: menuCoords.top - offset.top, left: menuCoords.left - offset.left }));
|
|
3221
|
+
}, [placement, alignment, asPortal]);
|
|
3222
|
+
// Throttles scroll/resize-triggered updates to at most once per frame,
|
|
3223
|
+
// avoiding repeated forced reflows on busy/high-frequency scroll events.
|
|
3224
|
+
const rafIdRef = useRef(null);
|
|
3225
|
+
const scheduleUpdatePosition = useCallback(() => {
|
|
3226
|
+
if (rafIdRef.current !== null)
|
|
3227
|
+
return;
|
|
3228
|
+
rafIdRef.current = window.requestAnimationFrame(() => {
|
|
3229
|
+
rafIdRef.current = null;
|
|
3230
|
+
updatePosition();
|
|
3231
|
+
});
|
|
3232
|
+
}, [updatePosition]);
|
|
3233
|
+
// Outside-click detection (checks both the toggle and the floating menu,
|
|
3234
|
+
// since in portal mode the menu isn't a DOM descendant of the toggle).
|
|
3235
|
+
useLayoutEffect(() => {
|
|
3236
|
+
if (!open)
|
|
2926
3237
|
return () => { };
|
|
2927
3238
|
const handler = (event) => {
|
|
2928
3239
|
var _a, _b;
|
|
@@ -2933,35 +3244,25 @@ function DDropdown({ actions, dropdownToggle, className, classNameMenu, asPortal
|
|
|
2933
3244
|
};
|
|
2934
3245
|
document.addEventListener('mousedown', handler);
|
|
2935
3246
|
return () => document.removeEventListener('mousedown', handler);
|
|
2936
|
-
}, [
|
|
2937
|
-
//
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
const spaceBottom = window.innerHeight - rect.bottom;
|
|
2943
|
-
const spaceRight = window.innerWidth - rect.right;
|
|
2944
|
-
if (spaceBottom < 150)
|
|
2945
|
-
setPosition('up');
|
|
2946
|
-
else if (spaceRight < 150)
|
|
2947
|
-
setPosition('start');
|
|
2948
|
-
else
|
|
2949
|
-
setPosition('down');
|
|
2950
|
-
}, [asPortal, open, placement]);
|
|
2951
|
-
// Compute + track coords: portal mode
|
|
2952
|
-
// Re-runs whenever the menu opens and keeps coords fresh on scroll/resize
|
|
2953
|
-
useEffect(() => {
|
|
2954
|
-
if (!asPortal || !open)
|
|
3247
|
+
}, [open]);
|
|
3248
|
+
// Computes the initial position synchronously (before paint, to avoid a
|
|
3249
|
+
// flicker) and keeps it fresh while the menu is open and the viewport
|
|
3250
|
+
// scrolls or resizes (throttled to one update per frame, see above).
|
|
3251
|
+
useLayoutEffect(() => {
|
|
3252
|
+
if (!open)
|
|
2955
3253
|
return () => { };
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
window.addEventListener('
|
|
2959
|
-
window.addEventListener('resize', update, { passive: true });
|
|
3254
|
+
updatePosition();
|
|
3255
|
+
window.addEventListener('scroll', scheduleUpdatePosition, { passive: true, capture: true });
|
|
3256
|
+
window.addEventListener('resize', scheduleUpdatePosition, { passive: true });
|
|
2960
3257
|
return () => {
|
|
2961
|
-
window.removeEventListener('scroll',
|
|
2962
|
-
window.removeEventListener('resize',
|
|
3258
|
+
window.removeEventListener('scroll', scheduleUpdatePosition, { capture: true });
|
|
3259
|
+
window.removeEventListener('resize', scheduleUpdatePosition);
|
|
3260
|
+
if (rafIdRef.current !== null) {
|
|
3261
|
+
window.cancelAnimationFrame(rafIdRef.current);
|
|
3262
|
+
rafIdRef.current = null;
|
|
3263
|
+
}
|
|
2963
3264
|
};
|
|
2964
|
-
}, [
|
|
3265
|
+
}, [open, updatePosition, scheduleUpdatePosition]);
|
|
2965
3266
|
let ToggleElement;
|
|
2966
3267
|
if (dropdownToggle) {
|
|
2967
3268
|
ToggleElement = typeof dropdownToggle === 'function'
|
|
@@ -2971,57 +3272,30 @@ function DDropdown({ actions, dropdownToggle, className, classNameMenu, asPortal
|
|
|
2971
3272
|
else {
|
|
2972
3273
|
ToggleElement = (jsx(DButtonIcon, { variant: "link", stopPropagationEnabled: false, "aria-label": "Toggle Dropdown", "aria-haspopup": "menu", "aria-expanded": open, onClick: toggle, icon: "MoreVertical" }));
|
|
2973
3274
|
}
|
|
2974
|
-
|
|
3275
|
+
// Until the first measurement is available, keep the menu off-screen but
|
|
3276
|
+
// measurable (visibility: hidden preserves layout, unlike display: none).
|
|
3277
|
+
const menuStyle = coords
|
|
2975
3278
|
? {
|
|
2976
3279
|
position: 'fixed',
|
|
2977
|
-
top:
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
minWidth: menuCoords.minWidth,
|
|
2982
|
-
maxHeight: menuCoords.maxHeight,
|
|
3280
|
+
top: coords.top,
|
|
3281
|
+
left: coords.left,
|
|
3282
|
+
minWidth: coords.minWidth,
|
|
3283
|
+
maxHeight: coords.maxHeight,
|
|
2983
3284
|
overflowY: 'auto',
|
|
2984
3285
|
zIndex: 1050,
|
|
2985
3286
|
transform: 'none',
|
|
3287
|
+
visibility: 'visible',
|
|
2986
3288
|
}
|
|
2987
|
-
:
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
inlineLeft = 'auto';
|
|
2998
|
-
}
|
|
2999
|
-
else if (resolvedInlinePosition === 'end') {
|
|
3000
|
-
inlineLeft = '100%';
|
|
3001
|
-
}
|
|
3002
|
-
let inlineRight = 'auto';
|
|
3003
|
-
if (resolvedInlinePosition === 'start') {
|
|
3004
|
-
inlineRight = '100%';
|
|
3005
|
-
}
|
|
3006
|
-
let inlineTransform = 'translateY(4px)';
|
|
3007
|
-
if (resolvedInlinePosition === 'up') {
|
|
3008
|
-
inlineTransform = 'translateY(-4px)';
|
|
3009
|
-
}
|
|
3010
|
-
else if (resolvedInlinePosition === 'start') {
|
|
3011
|
-
inlineTransform = 'translateX(-4px)';
|
|
3012
|
-
}
|
|
3013
|
-
else if (resolvedInlinePosition === 'end') {
|
|
3014
|
-
inlineTransform = 'translateX(4px)';
|
|
3015
|
-
}
|
|
3016
|
-
const inlineMenuStyle = {
|
|
3017
|
-
position: 'absolute',
|
|
3018
|
-
top: inlineTop,
|
|
3019
|
-
bottom: resolvedInlinePosition === 'up' ? '100%' : 'auto',
|
|
3020
|
-
left: inlineLeft,
|
|
3021
|
-
right: inlineRight,
|
|
3022
|
-
transform: inlineTransform,
|
|
3023
|
-
};
|
|
3024
|
-
const menuItems = (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) => {
|
|
3289
|
+
: {
|
|
3290
|
+
position: 'fixed',
|
|
3291
|
+
top: 0,
|
|
3292
|
+
left: 0,
|
|
3293
|
+
visibility: 'hidden',
|
|
3294
|
+
pointerEvents: 'none',
|
|
3295
|
+
zIndex: 1050,
|
|
3296
|
+
transform: 'none',
|
|
3297
|
+
};
|
|
3298
|
+
const menuItems = (jsx("ul", { ref: menuRef, role: "menu", className: classNames('dropdown-menu p-2', { show: open }, classNameMenu), style: menuStyle, children: actions.map((action, index) => {
|
|
3025
3299
|
if (action.isDivider) {
|
|
3026
3300
|
return jsx("hr", { className: "dropdown-divider" }, index);
|
|
3027
3301
|
}
|
|
@@ -3040,10 +3314,7 @@ function DDropdown({ actions, dropdownToggle, className, classNameMenu, asPortal
|
|
|
3040
3314
|
}
|
|
3041
3315
|
}, disabled: action.disabled, children: [action.icon && jsx(DIcon, { icon: action.icon, className: "me-2", size: "1rem" }), action.label] })) }, index));
|
|
3042
3316
|
}) }));
|
|
3043
|
-
|
|
3044
|
-
return (jsxs("div", { ref: toggleRef, className: classNames('dropdown', className), children: [ToggleElement, open && menuCoords && createPortal(menuItems, document.body)] }));
|
|
3045
|
-
}
|
|
3046
|
-
return (jsxs("div", { className: classNames(`dropdown drop-${resolvedInlinePosition}`, className), ref: dropdownRef, children: [ToggleElement, menuItems] }));
|
|
3317
|
+
return (jsxs("div", { ref: toggleRef, className: classNames(`dropdown drop-${resolvedSide} d-inline-block`, className), children: [ToggleElement, open && (asPortal ? createPortal(menuItems, document.body) : menuItems)] }));
|
|
3047
3318
|
}
|
|
3048
3319
|
|
|
3049
3320
|
function useScreenshot() {
|
|
@@ -3073,13 +3344,18 @@ function useScreenshot() {
|
|
|
3073
3344
|
|
|
3074
3345
|
function useScreenshotDownload() {
|
|
3075
3346
|
const { clipRef, takeBlob } = useScreenshot();
|
|
3076
|
-
const download = useCallback(async () => {
|
|
3077
|
-
const blob = await takeBlob();
|
|
3347
|
+
const download = useCallback(async (fileName = 'voucher') => {
|
|
3348
|
+
const blob = await takeBlob('image/jpeg');
|
|
3078
3349
|
const url = window.URL.createObjectURL(blob);
|
|
3079
3350
|
const link = window.document.createElement('a');
|
|
3080
3351
|
link.style.display = 'none';
|
|
3081
3352
|
link.href = url;
|
|
3082
|
-
|
|
3353
|
+
// Fall back to the default base name when the caller passes a blank/whitespace value,
|
|
3354
|
+
// so the file is never named just ".jpg".
|
|
3355
|
+
const safeFileName = fileName.trim() || 'voucher';
|
|
3356
|
+
// The extension is always appended by the hook to guarantee a valid image file,
|
|
3357
|
+
// regardless of what fileName the caller passes (e.g. "receipt.png" -> "receipt.png.jpg").
|
|
3358
|
+
link.download = `${safeFileName}.jpg`;
|
|
3083
3359
|
document.body.appendChild(link);
|
|
3084
3360
|
link.click();
|
|
3085
3361
|
document.body.removeChild(link);
|
|
@@ -3093,11 +3369,15 @@ function useScreenshotDownload() {
|
|
|
3093
3369
|
|
|
3094
3370
|
function useScreenshotWebShare() {
|
|
3095
3371
|
const { takeBlob, clipRef } = useScreenshot();
|
|
3096
|
-
const share = useCallback(async () => {
|
|
3097
|
-
const blob = await takeBlob();
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3372
|
+
const share = useCallback(async (fileName = 'voucher') => {
|
|
3373
|
+
const blob = await takeBlob('image/jpeg');
|
|
3374
|
+
// Fall back to the default base name when the caller passes a blank/whitespace value,
|
|
3375
|
+
// so the file is never named just ".jpeg".
|
|
3376
|
+
const safeFileName = fileName.trim() || 'voucher';
|
|
3377
|
+
// The extension is always appended by the hook to guarantee a valid image file,
|
|
3378
|
+
// regardless of what fileName the caller passes (e.g. "receipt.png" -> "receipt.png.jpeg").
|
|
3379
|
+
const image = new File([blob], `${safeFileName}.jpeg`, { type: 'image/jpeg' });
|
|
3380
|
+
if (!navigator.canShare || !navigator.canShare({ files: [image] })) {
|
|
3101
3381
|
window.print();
|
|
3102
3382
|
return;
|
|
3103
3383
|
}
|
|
@@ -3109,11 +3389,11 @@ function useScreenshotWebShare() {
|
|
|
3109
3389
|
};
|
|
3110
3390
|
}
|
|
3111
3391
|
|
|
3112
|
-
function DVoucher({ amount, amountDetails, icon, title, onError, message, downloadText = 'Download', shareText = 'Share', className, children, }) {
|
|
3392
|
+
function DVoucher({ amount, amountDetails, icon, title, onError, message, downloadText = 'Download', shareText = 'Share', fileName = 'voucher', hideActions = false, className, children, }) {
|
|
3113
3393
|
const { shareRef, share } = useScreenshotWebShare();
|
|
3114
3394
|
const { downloadRef, download } = useScreenshotDownload();
|
|
3115
3395
|
const handleShare = () => {
|
|
3116
|
-
share()
|
|
3396
|
+
share(fileName)
|
|
3117
3397
|
.catch(async (err) => {
|
|
3118
3398
|
if (onError) {
|
|
3119
3399
|
await onError(err);
|
|
@@ -3124,7 +3404,7 @@ function DVoucher({ amount, amountDetails, icon, title, onError, message, downlo
|
|
|
3124
3404
|
});
|
|
3125
3405
|
};
|
|
3126
3406
|
const handleDownload = () => {
|
|
3127
|
-
download()
|
|
3407
|
+
download(fileName)
|
|
3128
3408
|
.catch(async (err) => {
|
|
3129
3409
|
if (onError) {
|
|
3130
3410
|
await onError(err);
|
|
@@ -3152,80 +3432,36 @@ function DVoucher({ amount, amountDetails, icon, title, onError, message, downlo
|
|
|
3152
3432
|
return (jsx("div", { className: classNames('d-voucher', className), ref: (el) => {
|
|
3153
3433
|
shareRef.current = el;
|
|
3154
3434
|
downloadRef.current = el;
|
|
3155
|
-
}, children: jsxs("div", { children: [jsxs("div", { className: "d-voucher-header", children: [resolvedIconProps && (jsx(DIcon, Object.assign({}, resolvedIconProps))), jsxs("div", { className: "
|
|
3156
|
-
}
|
|
3157
|
-
|
|
3158
|
-
function useCountdown(seconds) {
|
|
3159
|
-
const [secondsLeft, setSecondsLeft] = useState(seconds);
|
|
3160
|
-
const [isActive, setIsActive] = useState(true);
|
|
3161
|
-
const resetCountdown = useCallback((newSeconds = seconds) => {
|
|
3162
|
-
setIsActive(false);
|
|
3163
|
-
setSecondsLeft(newSeconds);
|
|
3164
|
-
}, [seconds]);
|
|
3165
|
-
const restartCountdown = useCallback(() => {
|
|
3166
|
-
resetCountdown(seconds);
|
|
3167
|
-
setIsActive(true);
|
|
3168
|
-
}, [resetCountdown, seconds]);
|
|
3169
|
-
useEffect(() => {
|
|
3170
|
-
if (!isActive) {
|
|
3171
|
-
return () => { };
|
|
3172
|
-
}
|
|
3173
|
-
const interval = setInterval(() => {
|
|
3174
|
-
setSecondsLeft((prevSeconds) => {
|
|
3175
|
-
const newSeconds = prevSeconds - 1;
|
|
3176
|
-
if (newSeconds <= 0) {
|
|
3177
|
-
clearInterval(interval);
|
|
3178
|
-
setIsActive(false);
|
|
3179
|
-
return 0;
|
|
3180
|
-
}
|
|
3181
|
-
return newSeconds;
|
|
3182
|
-
});
|
|
3183
|
-
}, 1000);
|
|
3184
|
-
return () => clearInterval(interval);
|
|
3185
|
-
}, [isActive]);
|
|
3186
|
-
return { secondsLeft, restartCountdown };
|
|
3435
|
+
}, children: jsxs("div", { children: [jsxs("div", { className: "d-voucher-header", children: [resolvedIconProps && (jsx(DIcon, Object.assign({}, resolvedIconProps))), jsxs("div", { className: "d-voucher-title-group", children: [jsx("h3", { className: "d-voucher-title", children: title }), jsx("p", { className: "d-voucher-message", children: message })] })] }), amount && (jsxs("div", { className: "d-voucher-amount", children: [jsx("div", { className: classNames('d-voucher-amount-value', { 'd-voucher-amount-value-with-details': !!amountDetails }), children: amount }), amountDetails] })), jsx("hr", { className: "d-voucher-divider" }), children, !hideActions && (jsxs(Fragment, { children: [jsx("hr", { className: "my-4" }), jsxs("div", { className: "d-voucher-footer", children: [jsx(DButton, { onClick: handleShare, iconStart: "Share2", text: shareText, variant: "outline", size: "sm" }), jsx(DButton, { onClick: handleDownload, iconStart: "Download", text: downloadText, variant: "outline", size: "sm" })] })] }))] }) }));
|
|
3187
3436
|
}
|
|
3188
3437
|
|
|
3189
3438
|
const defaultMessage = (secs) => (secs > 0
|
|
3190
3439
|
? `Didn't get any code? Resend in: ${secs}s`
|
|
3191
3440
|
: "Didn't get any code?");
|
|
3192
|
-
function OtpCountdown({
|
|
3193
|
-
|
|
3194
|
-
return (jsxs("div", { className: "d-flex gap-2 align-items-center", children: [jsx("p", { className: "mb-0 flex-1", children: message ? message(secondsLeft) : defaultMessage(secondsLeft) }), jsx(DButton, { text: resendText, variant: "link", className: "text-nowrap", disabled: secondsLeft > 0, onClick: restartCountdown })] }));
|
|
3441
|
+
function OtpCountdown({ secondsLeft, restartCountdown, resendText, message, }) {
|
|
3442
|
+
return (jsxs("div", { className: "d-otp-countdown", children: [jsx("p", { className: "d-otp-countdown-text", children: message ? message(secondsLeft) : defaultMessage(secondsLeft) }), jsx(DButton, { text: resendText, variant: "link", className: "text-nowrap", disabled: secondsLeft > 0, onClick: restartCountdown })] }));
|
|
3195
3443
|
}
|
|
3196
3444
|
|
|
3197
3445
|
const TEXT_PROPS = {
|
|
3198
3446
|
resend: 'Resend',
|
|
3199
|
-
resendText: 'Resend
|
|
3447
|
+
resendText: 'Resend',
|
|
3200
3448
|
submit: 'Authorize and continue',
|
|
3201
3449
|
title: 'We will send you a 6-digit code to your associated phone number so you can continue with your request.',
|
|
3450
|
+
invalidCode: 'Invalid code, please try again.',
|
|
3202
3451
|
contact: (jsxs(Fragment, { children: [jsx("span", { children: "Problems with your digital token? Contact us" }), ' ', jsx("a", { href: "https://www.modyo.com", className: "link-primary text-nowrap", target: "_blank", rel: "noreferrer", children: "Contact us" })] })),
|
|
3203
3452
|
};
|
|
3204
3453
|
function DOtp({ className, action, isLoading, otpSize = 6, texts = TEXT_PROPS, seconds = 15, }) {
|
|
3205
|
-
const
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
if (otp.length < otpSize) {
|
|
3209
|
-
setInvalid(true);
|
|
3210
|
-
return;
|
|
3211
|
-
}
|
|
3212
|
-
setInvalid(false);
|
|
3213
|
-
await action();
|
|
3214
|
-
}, [
|
|
3215
|
-
otp.length,
|
|
3216
|
-
action,
|
|
3217
|
-
otpSize,
|
|
3218
|
-
]);
|
|
3219
|
-
return (jsxs("div", { className: className, children: [jsx("p", { children: texts.title }), jsxs("div", { className: "d-flex flex-column gap-6 pb-4 px-3", children: [jsxs("div", { className: "d-flex flex-column gap-6", children: [jsx(DInputPin, { className: "modal-otp-pin", characters: otpSize, onChange: (e) => setOtp(e), invalid: invalid && otp.length < otpSize, placeholder: "0" }), jsx(OtpCountdown, { seconds: seconds, resendText: texts.resend })] }), jsx("hr", { className: "m-0" }), jsxs("div", { className: "d-flex flex-column flex-lg-row gap-4 align-items-center", children: [jsx(DButton, { text: texts.submit, onClick: () => {
|
|
3220
|
-
handler().catch((err) => {
|
|
3454
|
+
const { setOtp, invalid, submit, isLoading: isSubmitting, secondsLeft, restartCountdown, } = useOtp({ action, otpSize, seconds });
|
|
3455
|
+
return (jsxs("div", { className: className, children: [jsx("p", { children: texts.title }), jsxs("div", { className: "d-otp d-otp-content", children: [jsxs("div", { className: "d-otp-fields", children: [jsx(DInputPin, { className: "modal-otp-pin", characters: otpSize, onChange: (e) => setOtp(e), invalid: invalid, hint: invalid ? texts.invalidCode : undefined, placeholder: "0" }), jsx(OtpCountdown, { secondsLeft: secondsLeft, restartCountdown: restartCountdown, resendText: texts.resendText })] }), jsx("hr", { className: "d-otp-divider" }), jsxs("div", { className: "d-otp-footer", children: [jsx(DButton, { text: texts.submit, onClick: () => {
|
|
3456
|
+
submit().catch((err) => {
|
|
3221
3457
|
// eslint-disable-next-line no-console
|
|
3222
3458
|
console.error('Error in DOtp action:', err);
|
|
3223
3459
|
});
|
|
3224
|
-
}, loading: isLoading }), jsx("p", { className: "
|
|
3460
|
+
}, loading: isLoading || isSubmitting }), jsx("p", { className: "d-otp-contact", children: texts.contact })] })] })] }));
|
|
3225
3461
|
}
|
|
3226
3462
|
|
|
3227
3463
|
function DefaultErrorBoundary({ resetErrorBoundary }) {
|
|
3228
|
-
return (jsx(DAlert, { color: "danger", showClose: false, children: jsxs("div", { className: "d-
|
|
3464
|
+
return (jsx(DAlert, { color: "danger", showClose: false, children: jsxs("div", { className: "d-error-boundary-content", children: [jsx("span", { children: "An unexpected error occurred." }), jsx(DButton, { color: "secondary", variant: "outline", size: "sm", onClick: resetErrorBoundary, children: "Retry" })] }) }));
|
|
3229
3465
|
}
|
|
3230
3466
|
|
|
3231
3467
|
function DErrorBoundary({ name, fallback, resetKeys, onReset, onError, children, }) {
|
|
@@ -3243,15 +3479,15 @@ function DErrorBoundary({ name, fallback, resetKeys, onReset, onError, children,
|
|
|
3243
3479
|
}
|
|
3244
3480
|
|
|
3245
3481
|
function ErrorState({ message, onRetry, retryMessage = 'Retry', color = 'danger', }) {
|
|
3246
|
-
return (jsxs(DAlert, { color: color, className: "d-
|
|
3482
|
+
return (jsxs(DAlert, { color: color, className: "d-error-state", children: [jsx("div", { className: "d-error-state-content", children: jsx("p", { className: "d-error-state-message", children: message !== null && message !== void 0 ? message : 'An unexpected error occurred.' }) }), onRetry && (jsx(DButton, { onClick: onRetry, text: retryMessage, variant: "outline", iconStart: "RefreshCw" }))] }));
|
|
3247
3483
|
}
|
|
3248
3484
|
|
|
3249
3485
|
function EmptyState({ message, icon = 'FileText', actionText, onAction, }) {
|
|
3250
|
-
return (jsxs("div", { className: "d-
|
|
3486
|
+
return (jsxs("div", { className: "d-empty-state", children: [jsx(DIcon, { icon: icon, size: "3rem", className: "d-empty-state-icon" }), jsx("p", { className: "d-empty-state-message", children: message !== null && message !== void 0 ? message : 'No data available.' }), actionText && onAction && (jsx(DButton, { onClick: onAction, text: actionText, variant: "outline" }))] }));
|
|
3251
3487
|
}
|
|
3252
3488
|
|
|
3253
3489
|
function LoadingState({ ariaLabel = 'Loading...', className }) {
|
|
3254
|
-
return (jsx("div", { className: `d-
|
|
3490
|
+
return (jsx("div", { className: `d-loading-state ${className || ''}`.trim(), "aria-busy": "true", "aria-live": "polite", children: jsx("span", { className: "spinner-border", role: "status", "aria-label": ariaLabel }) }));
|
|
3255
3491
|
}
|
|
3256
3492
|
|
|
3257
3493
|
function render(renderable) {
|
|
@@ -3310,7 +3546,7 @@ function DConfirmModalUI({ entry }) {
|
|
|
3310
3546
|
}
|
|
3311
3547
|
});
|
|
3312
3548
|
}, [onConfirmAction]);
|
|
3313
|
-
return (jsxs(DModal$1, { name: entry.id, size: "lg", className: `confirm-modal ${critical ? 'critical-modal' : ''}`, children: [jsx(DModal$1.Header, { onClose: handleClose, showCloseButton: true, children: jsx("h5", { className: "fw-bold", children: title }) }), jsxs(DModal$1.Body, { className: "
|
|
3549
|
+
return (jsxs(DModal$1, { name: entry.id, size: "lg", className: `confirm-modal ${critical ? 'critical-modal' : ''}`, children: [jsx(DModal$1.Header, { onClose: handleClose, showCloseButton: true, children: jsx("h5", { className: "fw-bold", children: title }) }), jsxs(DModal$1.Body, { className: "d-confirm-modal-body", children: [jsx("p", { className: "d-confirm-modal-message", children: message }), critical && (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 }))] }), jsxs(DModal$1.Footer, { children: [jsx(DButton, { text: cancelLabel, variant: "outline", color: "secondary", onClick: handleClose, disabled: internalLoading }), jsx(DButton, { text: confirmLabel, color: confirmColor, onClick: handleConfirmClick, disabled: isConfirmDisabled, loading: internalLoading })] })] }));
|
|
3314
3550
|
}
|
|
3315
3551
|
|
|
3316
3552
|
/**
|
|
@@ -3363,5 +3599,5 @@ function DConfirmModalContainer({ nodeId }) {
|
|
|
3363
3599
|
return createPortal(jsx(AnimatePresence, { children: entries.map((entry) => (jsxs(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0, transition: { delay: 0.3 } }, transition: { duration: 0.15, ease: 'linear' }, children: [jsx("div", { className: "backdrop backdrop-confirm-modal", onClick: entry.onCloseAction, role: "presentation" }), jsx(DConfirmModalUI, { entry: entry })] }, entry.id))) }), portalNode);
|
|
3364
3600
|
}
|
|
3365
3601
|
|
|
3366
|
-
export { DAlert, DAvatar, DBadge, DBox, DBoxFile, DButton, DButtonIcon, DCard$1 as DCard, DCardBody, DCardFooter, DCardHeader, DCarousel$1 as DCarousel, DCarouselSlide, DChip, DCollapse, DConfirmModalContainer, DContext, DContextProvider, DCreditCard, DCurrencyText, DDataStateWrapper, DDatePicker, DDropdown, DErrorBoundary, DIcon, DIconBase, ForwardedDInput as DInput, DInputCheck, ForwardedDInputCounter as DInputCounter, ForwardedDInputCurrency as DInputCurrency, ForwardedDInputMask as DInputMask, ForwardedDInputPassword as DInputPassword, ForwardedDInputPhone as DInputPhone, DInputPin, ForwardedDInputRange as DInputRange, ForwardedDInputSearch as DInputSearch, DInputSelect, DInputSwitch, DLayout$1 as DLayout, DLayoutPane, DListGroup$1 as DListGroup, DListGroupItem, DModal$1 as DModal, DModalBody, DModalFooter, DModalHeader, DOffcanvas$1 as DOffcanvas, DOffcanvasBody, DOffcanvasFooter, DOffcanvasHeader, DOtp, DPaginator, DPasswordStrengthMeter, DPopover, DProgress, DSelect$1 as DSelect, DStepper, DStepper$2 as DStepperDesktop, DStepper$1 as DStepperMobile, DTabContent, DTabs$1 as DTabs, DTimeline, DToast$1 as DToast, DToastContainer, DTooltip, DVoucher, EmptyState, ErrorState, LoadingState, changeQueryString, checkMediaQuery, configureI8n as configureI18n, formatCurrency, getCssVariable, getQueryString, subscribeToMediaQuery, useConfirmModal, useDContext, useDPortalContext, useDToast, useDisableBodyScrollEffect, useDisableInputWheel, useFormatCurrency, useInputCurrency, useItemSelection, useMediaBreakpointUpLg, useMediaBreakpointUpMd, useMediaBreakpointUpSm, useMediaBreakpointUpXl, useMediaBreakpointUpXs, useMediaBreakpointUpXxl, useMediaQuery, useProvidedRefOrCreate, useStackState, useTabContext, validatePhoneNumber };
|
|
3602
|
+
export { DAlert, DAvatar, DBadge, DBox, DBoxFile, DButton, DButtonIcon, DCard$1 as DCard, DCardBody, DCardFooter, DCardHeader, DCarousel$1 as DCarousel, DCarouselSlide, DChip, DCollapse, DConfirmModalContainer, DContext, DContextProvider, DCreditCard, DCurrencyText, DDataStateWrapper, DDatePicker, DDropdown, DErrorBoundary, DIcon, DIconBase, ForwardedDInput as DInput, DInputCheck, ForwardedDInputCounter as DInputCounter, ForwardedDInputCurrency as DInputCurrency, ForwardedDInputMask as DInputMask, ForwardedDInputPassword as DInputPassword, ForwardedDInputPhone as DInputPhone, DInputPin, ForwardedDInputRange as DInputRange, ForwardedDInputSearch as DInputSearch, DInputSelect, DInputSwitch, DLayout$1 as DLayout, DLayoutPane, DListGroup$1 as DListGroup, DListGroupItem, DModal$1 as DModal, DModalBody, DModalFooter, DModalHeader, DOffcanvas$1 as DOffcanvas, DOffcanvasBody, DOffcanvasFooter, DOffcanvasHeader, DOtp, DPaginator, DPasswordStrengthMeter, DPopover, DProgress, DSelect$1 as DSelect, DStepper, DStepper$2 as DStepperDesktop, DStepper$1 as DStepperMobile, DTabContent, DTabs$1 as DTabs, DTimeline, DToast$1 as DToast, DToastContainer, DTooltip, DVoucher, EmptyState, ErrorState, LoadingState, changeQueryString, checkMediaQuery, configureI8n as configureI18n, formatCurrency, getCssVariable, getQueryString, subscribeToMediaQuery, useConfirmModal, useCountdown, useDContext, useDPortalContext, useDToast, useDisableBodyScrollEffect, useDisableInputWheel, useFormatCurrency, useInputCurrency, useItemSelection, useMediaBreakpointUpLg, useMediaBreakpointUpMd, useMediaBreakpointUpSm, useMediaBreakpointUpXl, useMediaBreakpointUpXs, useMediaBreakpointUpXxl, useMediaQuery, useOtp, useProvidedRefOrCreate, useScreenshot, useScreenshotDownload, useScreenshotWebShare, useStackState, useTabContext, validatePhoneNumber };
|
|
3367
3603
|
//# sourceMappingURL=index.esm.js.map
|