@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.js
CHANGED
|
@@ -1126,7 +1126,7 @@ function DBoxFile(_a) {
|
|
|
1126
1126
|
'd-box-file-invalid': isDragInvalid,
|
|
1127
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'
|
|
1128
1128
|
? children(openFileDialog)
|
|
1129
|
-
: 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}`))) }))] }));
|
|
1130
1130
|
}
|
|
1131
1131
|
|
|
1132
1132
|
const DButton = React.forwardRef((props, ref) => {
|
|
@@ -1502,9 +1502,9 @@ function DDatePickerHeaderSelector({ date, changeYear, changeMonth, decreaseMont
|
|
|
1502
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" })] }));
|
|
1503
1503
|
}
|
|
1504
1504
|
if (pickerType === PickerType.Quarter || pickerType === PickerType.Month) {
|
|
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: "
|
|
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' } })] }));
|
|
1506
1506
|
}
|
|
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: "
|
|
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' } })] })] }));
|
|
1508
1508
|
}
|
|
1509
1509
|
|
|
1510
1510
|
function DDatePicker(_a) {
|
|
@@ -1639,11 +1639,24 @@ function formatValue(value, currencyOptions) {
|
|
|
1639
1639
|
}
|
|
1640
1640
|
return currency(value, Object.assign(Object.assign({}, currencyOptions), { symbol: '' })).format();
|
|
1641
1641
|
}
|
|
1642
|
-
function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref) {
|
|
1642
|
+
function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref, minValue, maxValue) {
|
|
1643
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]);
|
|
1644
1657
|
const [innerType, setInnerType] = React.useState('text');
|
|
1645
|
-
const [innerNumber, setInnerNumber] = React.useState(value);
|
|
1646
|
-
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));
|
|
1647
1660
|
const handleOnFocus = React.useCallback((event) => {
|
|
1648
1661
|
event.stopPropagation();
|
|
1649
1662
|
setInnerType('number');
|
|
@@ -1652,15 +1665,14 @@ function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref
|
|
|
1652
1665
|
const handleOnBlur = React.useCallback((event) => {
|
|
1653
1666
|
event.stopPropagation();
|
|
1654
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
|
+
}
|
|
1655
1674
|
onBlur === null || onBlur === void 0 ? void 0 : onBlur(event);
|
|
1656
|
-
}, [onBlur]);
|
|
1657
|
-
const generateStyleVariables = React.useMemo(() => ({
|
|
1658
|
-
[`--${PREFIX_BS}input-currency-component-symbol-color`]: `var(--${PREFIX_BS}secondary)`,
|
|
1659
|
-
[`--${PREFIX_BS}input-currency-symbol-color`]: `var(--${PREFIX_BS}input-currency-component-symbol-color)`,
|
|
1660
|
-
}), []);
|
|
1661
|
-
const generateSymbolStyleVariables = React.useMemo(() => ({
|
|
1662
|
-
color: `var(--${PREFIX_BS}input-currency-symbol-color)`,
|
|
1663
|
-
}), []);
|
|
1675
|
+
}, [onBlur, innerNumber, clampValue, currencyOptions, onChange]);
|
|
1664
1676
|
const handleOnChange = React.useCallback((newValue) => {
|
|
1665
1677
|
const newNumber = (newValue === undefined || newValue === '') ? undefined : Number(newValue);
|
|
1666
1678
|
if (newNumber !== innerNumber) {
|
|
@@ -1669,7 +1681,12 @@ function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref
|
|
|
1669
1681
|
onChange === null || onChange === void 0 ? void 0 : onChange(newNumber);
|
|
1670
1682
|
}
|
|
1671
1683
|
}, [currencyOptions, onChange, innerNumber]);
|
|
1684
|
+
const isMountedRef = React.useRef(false);
|
|
1672
1685
|
React.useEffect(() => {
|
|
1686
|
+
if (!isMountedRef.current) {
|
|
1687
|
+
isMountedRef.current = true;
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1673
1690
|
if (value !== innerNumber) {
|
|
1674
1691
|
setInnerNumber(value);
|
|
1675
1692
|
setInnerString(formatValue(value, currencyOptions));
|
|
@@ -1683,8 +1700,6 @@ function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref
|
|
|
1683
1700
|
handleOnFocus,
|
|
1684
1701
|
handleOnChange,
|
|
1685
1702
|
handleOnBlur,
|
|
1686
|
-
generateStyleVariables,
|
|
1687
|
-
generateSymbolStyleVariables,
|
|
1688
1703
|
};
|
|
1689
1704
|
}
|
|
1690
1705
|
|
|
@@ -1789,6 +1804,149 @@ function useConfirmModal(config) {
|
|
|
1789
1804
|
return { open };
|
|
1790
1805
|
}
|
|
1791
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
|
+
|
|
1792
1950
|
function DInputCounter(_a, ref) {
|
|
1793
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"]);
|
|
1794
1952
|
const { handleOnWheel, } = useDisableInputWheel(ref);
|
|
@@ -1831,8 +1989,8 @@ function DInputCurrency(_a, ref) {
|
|
|
1831
1989
|
var { value, minValue, maxValue, currencyCode, onFocus, onBlur, onChange } = _a, props = tslib.__rest(_a, ["value", "minValue", "maxValue", "currencyCode", "onFocus", "onBlur", "onChange"]);
|
|
1832
1990
|
const { currency: currencyOptions } = useDContext();
|
|
1833
1991
|
const { handleOnWheel, } = useDisableInputWheel(ref);
|
|
1834
|
-
const { inputRef, innerValue, innerType, handleOnFocus, handleOnChange, handleOnBlur,
|
|
1835
|
-
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)));
|
|
1836
1994
|
}
|
|
1837
1995
|
const ForwardedDInputCurrency = React.forwardRef(DInputCurrency);
|
|
1838
1996
|
ForwardedDInputCurrency.displayName = 'DInputCurrency';
|
|
@@ -1854,26 +2012,25 @@ ForwardedDInputPassword.displayName = 'DInputPassword';
|
|
|
1854
2012
|
|
|
1855
2013
|
function PasswordCheckItem({ password, regex, text, }) {
|
|
1856
2014
|
const isValid = regex.test(password);
|
|
1857
|
-
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 })] }));
|
|
1858
2016
|
}
|
|
1859
2017
|
|
|
1860
|
-
const
|
|
2018
|
+
const getStrengthModifier = (strength, total) => {
|
|
1861
2019
|
const percentage = total > 0 ? strength / total : 0;
|
|
1862
2020
|
if (percentage === 0)
|
|
1863
|
-
return '
|
|
2021
|
+
return 'is-empty';
|
|
1864
2022
|
if (percentage <= 0.25)
|
|
1865
|
-
return '
|
|
2023
|
+
return 'is-weak';
|
|
1866
2024
|
if (percentage <= 0.5)
|
|
1867
|
-
return '
|
|
2025
|
+
return 'is-fair';
|
|
1868
2026
|
if (percentage <= 0.75)
|
|
1869
|
-
return '
|
|
1870
|
-
return '
|
|
2027
|
+
return 'is-good';
|
|
2028
|
+
return 'is-strong';
|
|
1871
2029
|
};
|
|
1872
2030
|
function PasswordStrengthBar({ strength, total }) {
|
|
1873
2031
|
const percentage = total > 0 ? (strength / total) * 100 : 0;
|
|
1874
|
-
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: {
|
|
1875
2033
|
width: `${percentage}%`,
|
|
1876
|
-
transition: 'width 0.3s ease-in-out',
|
|
1877
2034
|
} }) }));
|
|
1878
2035
|
}
|
|
1879
2036
|
|
|
@@ -1909,7 +2066,7 @@ function PasswordChecksList({ password, validationMessages, enabledChecks, }) {
|
|
|
1909
2066
|
const passwordChecks = allChecks.filter((check) => enabledChecks.includes(check.key));
|
|
1910
2067
|
const passed = passwordChecks.filter((r) => r.regex.test(password)).length;
|
|
1911
2068
|
const total = passwordChecks.length;
|
|
1912
|
-
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))) })] }));
|
|
1913
2070
|
}
|
|
1914
2071
|
|
|
1915
2072
|
const DEFAULT_VALIDATION_MESSAGES = {
|
|
@@ -2259,7 +2416,7 @@ var DListGroup = Object.assign(DListGroup$1, {
|
|
|
2259
2416
|
function DModalHeader({ showCloseButton, onClose, children, className, style, iconFamilyClass, iconFamilyPrefix, icon: iconProp, materialStyle = false, }) {
|
|
2260
2417
|
const { iconMap: { xLg, }, } = useDContext();
|
|
2261
2418
|
const icon = React.useMemo(() => iconProp || xLg, [iconProp, xLg]);
|
|
2262
|
-
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" })] }));
|
|
2263
2420
|
}
|
|
2264
2421
|
|
|
2265
2422
|
function DModalBody({ children, className, style, }) {
|
|
@@ -2303,7 +2460,7 @@ var DModal = Object.assign(DModal$1, {
|
|
|
2303
2460
|
function DOffcanvasHeader({ showCloseButton, onClose, children, className, style, iconFamilyClass, iconFamilyPrefix, icon: iconProp, materialStyle = false, }) {
|
|
2304
2461
|
const { iconMap: { xLg, }, } = useDContext();
|
|
2305
2462
|
const icon = React.useMemo(() => iconProp || xLg, [iconProp, xLg]);
|
|
2306
|
-
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" })] }));
|
|
2307
2464
|
}
|
|
2308
2465
|
|
|
2309
2466
|
function DOffcanvasBody({ children, className, style, }) {
|
|
@@ -2430,7 +2587,7 @@ function DPopover({ children, renderComponent, open, setOpen, adjustContentToRen
|
|
|
2430
2587
|
}
|
|
2431
2588
|
const reference = React.cloneElement(triggerElement, Object.assign({ ref: refs.setReference }, getReferenceProps(triggerElement.props)));
|
|
2432
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', {
|
|
2433
|
-
'
|
|
2590
|
+
'd-popover-content-fill': adjustContentToRender,
|
|
2434
2591
|
}), ref: refs.setFloating, style: floatingStyles, "aria-labelledby": headingId }, getFloatingProps(), { children: children })) }))] })));
|
|
2435
2592
|
}
|
|
2436
2593
|
|
|
@@ -2494,13 +2651,13 @@ function DStepper$1({ options, currentStep, className, style, }) {
|
|
|
2494
2651
|
}
|
|
2495
2652
|
|
|
2496
2653
|
function DStepper({ options, currentStep, iconSuccess, iconSuccessFamilyClass, iconSuccessFamilyPrefix, iconSuccessMaterialStyle = false, vertical = false, breakpoint = 'lg', className, completed = false, style, dataAttributes, }) {
|
|
2497
|
-
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 }) })] })));
|
|
2498
2655
|
}
|
|
2499
2656
|
|
|
2500
2657
|
const ARROW_WIDTH = 8;
|
|
2501
2658
|
const ARROW_HEIGHT = 4;
|
|
2502
|
-
const GAP = 2;
|
|
2503
|
-
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, }) {
|
|
2504
2661
|
const [isOpen, setIsOpen] = React.useState(open);
|
|
2505
2662
|
const arrowRef = React.useRef(null);
|
|
2506
2663
|
const { refs, context, floatingStyles, } = react.useFloating({
|
|
@@ -2569,8 +2726,16 @@ function DTabs$1({ children, defaultSelected, onChange, options, className, clas
|
|
|
2569
2726
|
React.useEffect(() => {
|
|
2570
2727
|
setSelected(defaultSelected);
|
|
2571
2728
|
}, [defaultSelected]);
|
|
2572
|
-
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]);
|
|
2573
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;
|
|
2574
2739
|
React.useEffect(() => {
|
|
2575
2740
|
tabRefs.current = options.map((_, i) => tabRefs.current[i] || React.createRef());
|
|
2576
2741
|
}, [options]);
|
|
@@ -2585,20 +2750,28 @@ function DTabs$1({ children, defaultSelected, onChange, options, className, clas
|
|
|
2585
2750
|
setSelected(firstEnabled.tab);
|
|
2586
2751
|
}
|
|
2587
2752
|
}, [options, selected]);
|
|
2588
|
-
// Declarative focus management
|
|
2589
|
-
|
|
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) => {
|
|
2590
2758
|
var _a;
|
|
2591
2759
|
if ((_a = tabRefs.current[idx]) === null || _a === void 0 ? void 0 : _a.current) {
|
|
2592
2760
|
tabRefs.current[idx].current.focus();
|
|
2593
2761
|
}
|
|
2594
|
-
};
|
|
2595
|
-
// 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).
|
|
2596
2769
|
React.useEffect(() => {
|
|
2597
|
-
const idx =
|
|
2770
|
+
const idx = optionsRef.current.findIndex((opt) => opt.tab === selected && !opt.disabled);
|
|
2598
2771
|
if (idx !== -1) {
|
|
2599
2772
|
focusTab(idx);
|
|
2600
2773
|
}
|
|
2601
|
-
}, [selected,
|
|
2774
|
+
}, [selected, focusTab]);
|
|
2602
2775
|
const handleKeyDown = React.useCallback((idx, e) => {
|
|
2603
2776
|
const count = options.length;
|
|
2604
2777
|
if (count === 0)
|
|
@@ -2627,7 +2800,7 @@ function DTabs$1({ children, defaultSelected, onChange, options, className, clas
|
|
|
2627
2800
|
}
|
|
2628
2801
|
}
|
|
2629
2802
|
}
|
|
2630
|
-
}, [options, vertical]);
|
|
2803
|
+
}, [options, vertical, focusTab]);
|
|
2631
2804
|
let tablistProps = {};
|
|
2632
2805
|
if (ariaLabelledBy) {
|
|
2633
2806
|
tablistProps = { 'aria-labelledby': ariaLabelledBy };
|
|
@@ -2640,12 +2813,12 @@ function DTabs$1({ children, defaultSelected, onChange, options, className, clas
|
|
|
2640
2813
|
isSelected,
|
|
2641
2814
|
}), [isSelected]);
|
|
2642
2815
|
return (jsxRuntime.jsx(TabContext.Provider, { value: value, children: jsxRuntime.jsxs("div", Object.assign({ className: classNames({
|
|
2643
|
-
'd-
|
|
2644
|
-
'
|
|
2816
|
+
'd-tabs': true,
|
|
2817
|
+
'd-tabs-column': !vertical || variant === 'tabs',
|
|
2645
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) => {
|
|
2646
2819
|
const isTabSelected = !!option.tab && option.tab === selected;
|
|
2647
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));
|
|
2648
|
-
}) })), jsxRuntime.jsx("div", { className: "
|
|
2821
|
+
}) })), jsxRuntime.jsx("div", { className: "d-tabs-content tab-content", children: children })] })) }));
|
|
2649
2822
|
}
|
|
2650
2823
|
var DTabs = Object.assign(DTabs$1, {
|
|
2651
2824
|
Tab: DTabContent,
|
|
@@ -2707,11 +2880,11 @@ function useDToast() {
|
|
|
2707
2880
|
if (!description) {
|
|
2708
2881
|
return (jsxRuntime.jsx(DToast, { className: classNames({
|
|
2709
2882
|
[`toast-${color}`]: !!color,
|
|
2710
|
-
}, '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 }) })] }) }));
|
|
2711
2884
|
}
|
|
2712
2885
|
return (jsxRuntime.jsxs(DToast, { className: classNames({
|
|
2713
2886
|
[`toast-${color}`]: !!color,
|
|
2714
|
-
}, '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 }) })] }));
|
|
2715
2888
|
}, toastProps);
|
|
2716
2889
|
}, [xLg]);
|
|
2717
2890
|
return {
|
|
@@ -2733,11 +2906,10 @@ async function configureI8n(resources, _a = {}) {
|
|
|
2733
2906
|
.then((t) => t);
|
|
2734
2907
|
}
|
|
2735
2908
|
|
|
2736
|
-
function getQueryString(value, config = {
|
|
2737
|
-
useSearch
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
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;
|
|
2741
2913
|
}
|
|
2742
2914
|
|
|
2743
2915
|
function changeQueryString(values, { useSearch = true, pushState = false, } = {}) {
|
|
@@ -2860,89 +3032,228 @@ function DCreditCard({ brand = 'visa', name, number, holderText = 'Card Holder',
|
|
|
2860
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 })] })] }));
|
|
2861
3033
|
}
|
|
2862
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;
|
|
2863
3041
|
const getItemClass = (action) => classNames({
|
|
2864
3042
|
'dropdown-item d-flex align-items-center': true,
|
|
2865
3043
|
[`dropdown-item-${action.color}`]: !!action.color,
|
|
2866
3044
|
disabled: action.disabled,
|
|
2867
3045
|
});
|
|
2868
|
-
|
|
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', }) {
|
|
2869
3197
|
const [open, setOpen] = React.useState(false);
|
|
2870
|
-
const [
|
|
2871
|
-
|
|
2872
|
-
//
|
|
2873
|
-
|
|
2874
|
-
//
|
|
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.
|
|
2875
3205
|
const toggleRef = React.useRef(null);
|
|
2876
|
-
// 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.
|
|
2877
3207
|
const menuRef = React.useRef(null);
|
|
2878
3208
|
const toggle = () => {
|
|
2879
|
-
|
|
3209
|
+
setCoords(null);
|
|
2880
3210
|
setOpen((prev) => !prev);
|
|
2881
3211
|
};
|
|
2882
|
-
|
|
2883
|
-
//
|
|
2884
|
-
const
|
|
2885
|
-
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);
|
|
2886
3228
|
return;
|
|
2887
|
-
const rect = toggleRef.current.getBoundingClientRect();
|
|
2888
|
-
const spaceRight = window.innerWidth - rect.right;
|
|
2889
|
-
const spaceBottom = window.innerHeight - rect.bottom;
|
|
2890
|
-
const spaceTop = rect.top;
|
|
2891
|
-
let resolvedPlacement;
|
|
2892
|
-
if (placement === 'auto') {
|
|
2893
|
-
if (spaceBottom < 150) {
|
|
2894
|
-
resolvedPlacement = 'up';
|
|
2895
|
-
}
|
|
2896
|
-
else if (spaceRight < 150) {
|
|
2897
|
-
resolvedPlacement = 'start';
|
|
2898
|
-
}
|
|
2899
|
-
else {
|
|
2900
|
-
resolvedPlacement = 'down';
|
|
2901
|
-
}
|
|
2902
|
-
}
|
|
2903
|
-
else {
|
|
2904
|
-
resolvedPlacement = placement;
|
|
2905
|
-
}
|
|
2906
|
-
const openStart = resolvedPlacement === 'start';
|
|
2907
|
-
const openEnd = resolvedPlacement === 'end';
|
|
2908
|
-
const openUp = resolvedPlacement === 'up';
|
|
2909
|
-
let availableHeight = spaceBottom;
|
|
2910
|
-
if (openUp) {
|
|
2911
|
-
availableHeight = spaceTop;
|
|
2912
|
-
}
|
|
2913
|
-
else if (openStart || openEnd) {
|
|
2914
|
-
availableHeight = window.innerHeight - rect.top;
|
|
2915
|
-
}
|
|
2916
|
-
const verticalCoords = openUp
|
|
2917
|
-
? { bottom: window.innerHeight - rect.top + 4 }
|
|
2918
|
-
: { top: openStart || openEnd ? rect.top : rect.bottom + 4 };
|
|
2919
|
-
let horizontalCoords;
|
|
2920
|
-
if (openStart) {
|
|
2921
|
-
horizontalCoords = { right: window.innerWidth - rect.left + 4 };
|
|
2922
|
-
}
|
|
2923
|
-
else if (openEnd) {
|
|
2924
|
-
horizontalCoords = { left: rect.right + 4 };
|
|
2925
|
-
}
|
|
2926
|
-
else {
|
|
2927
|
-
horizontalCoords = { left: rect.left };
|
|
2928
3229
|
}
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
const
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
};
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
React.
|
|
2945
|
-
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)
|
|
2946
3257
|
return () => { };
|
|
2947
3258
|
const handler = (event) => {
|
|
2948
3259
|
var _a, _b;
|
|
@@ -2953,35 +3264,25 @@ function DDropdown({ actions, dropdownToggle, className, classNameMenu, asPortal
|
|
|
2953
3264
|
};
|
|
2954
3265
|
document.addEventListener('mousedown', handler);
|
|
2955
3266
|
return () => document.removeEventListener('mousedown', handler);
|
|
2956
|
-
}, [
|
|
2957
|
-
//
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
const spaceBottom = window.innerHeight - rect.bottom;
|
|
2963
|
-
const spaceRight = window.innerWidth - rect.right;
|
|
2964
|
-
if (spaceBottom < 150)
|
|
2965
|
-
setPosition('up');
|
|
2966
|
-
else if (spaceRight < 150)
|
|
2967
|
-
setPosition('start');
|
|
2968
|
-
else
|
|
2969
|
-
setPosition('down');
|
|
2970
|
-
}, [asPortal, open, placement]);
|
|
2971
|
-
// Compute + track coords: portal mode
|
|
2972
|
-
// Re-runs whenever the menu opens and keeps coords fresh on scroll/resize
|
|
2973
|
-
React.useEffect(() => {
|
|
2974
|
-
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)
|
|
2975
3273
|
return () => { };
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
window.addEventListener('
|
|
2979
|
-
window.addEventListener('resize', update, { passive: true });
|
|
3274
|
+
updatePosition();
|
|
3275
|
+
window.addEventListener('scroll', scheduleUpdatePosition, { passive: true, capture: true });
|
|
3276
|
+
window.addEventListener('resize', scheduleUpdatePosition, { passive: true });
|
|
2980
3277
|
return () => {
|
|
2981
|
-
window.removeEventListener('scroll',
|
|
2982
|
-
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
|
+
}
|
|
2983
3284
|
};
|
|
2984
|
-
}, [
|
|
3285
|
+
}, [open, updatePosition, scheduleUpdatePosition]);
|
|
2985
3286
|
let ToggleElement;
|
|
2986
3287
|
if (dropdownToggle) {
|
|
2987
3288
|
ToggleElement = typeof dropdownToggle === 'function'
|
|
@@ -2991,57 +3292,30 @@ function DDropdown({ actions, dropdownToggle, className, classNameMenu, asPortal
|
|
|
2991
3292
|
else {
|
|
2992
3293
|
ToggleElement = (jsxRuntime.jsx(DButtonIcon, { variant: "link", stopPropagationEnabled: false, "aria-label": "Toggle Dropdown", "aria-haspopup": "menu", "aria-expanded": open, onClick: toggle, icon: "MoreVertical" }));
|
|
2993
3294
|
}
|
|
2994
|
-
|
|
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
|
|
2995
3298
|
? {
|
|
2996
3299
|
position: 'fixed',
|
|
2997
|
-
top:
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
minWidth: menuCoords.minWidth,
|
|
3002
|
-
maxHeight: menuCoords.maxHeight,
|
|
3300
|
+
top: coords.top,
|
|
3301
|
+
left: coords.left,
|
|
3302
|
+
minWidth: coords.minWidth,
|
|
3303
|
+
maxHeight: coords.maxHeight,
|
|
3003
3304
|
overflowY: 'auto',
|
|
3004
3305
|
zIndex: 1050,
|
|
3005
3306
|
transform: 'none',
|
|
3307
|
+
visibility: 'visible',
|
|
3006
3308
|
}
|
|
3007
|
-
:
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
inlineLeft = 'auto';
|
|
3018
|
-
}
|
|
3019
|
-
else if (resolvedInlinePosition === 'end') {
|
|
3020
|
-
inlineLeft = '100%';
|
|
3021
|
-
}
|
|
3022
|
-
let inlineRight = 'auto';
|
|
3023
|
-
if (resolvedInlinePosition === 'start') {
|
|
3024
|
-
inlineRight = '100%';
|
|
3025
|
-
}
|
|
3026
|
-
let inlineTransform = 'translateY(4px)';
|
|
3027
|
-
if (resolvedInlinePosition === 'up') {
|
|
3028
|
-
inlineTransform = 'translateY(-4px)';
|
|
3029
|
-
}
|
|
3030
|
-
else if (resolvedInlinePosition === 'start') {
|
|
3031
|
-
inlineTransform = 'translateX(-4px)';
|
|
3032
|
-
}
|
|
3033
|
-
else if (resolvedInlinePosition === 'end') {
|
|
3034
|
-
inlineTransform = 'translateX(4px)';
|
|
3035
|
-
}
|
|
3036
|
-
const inlineMenuStyle = {
|
|
3037
|
-
position: 'absolute',
|
|
3038
|
-
top: inlineTop,
|
|
3039
|
-
bottom: resolvedInlinePosition === 'up' ? '100%' : 'auto',
|
|
3040
|
-
left: inlineLeft,
|
|
3041
|
-
right: inlineRight,
|
|
3042
|
-
transform: inlineTransform,
|
|
3043
|
-
};
|
|
3044
|
-
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) => {
|
|
3045
3319
|
if (action.isDivider) {
|
|
3046
3320
|
return jsxRuntime.jsx("hr", { className: "dropdown-divider" }, index);
|
|
3047
3321
|
}
|
|
@@ -3060,10 +3334,7 @@ function DDropdown({ actions, dropdownToggle, className, classNameMenu, asPortal
|
|
|
3060
3334
|
}
|
|
3061
3335
|
}, disabled: action.disabled, children: [action.icon && jsxRuntime.jsx(DIcon, { icon: action.icon, className: "me-2", size: "1rem" }), action.label] })) }, index));
|
|
3062
3336
|
}) }));
|
|
3063
|
-
|
|
3064
|
-
return (jsxRuntime.jsxs("div", { ref: toggleRef, className: classNames('dropdown', className), children: [ToggleElement, open && menuCoords && reactDom.createPortal(menuItems, document.body)] }));
|
|
3065
|
-
}
|
|
3066
|
-
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)] }));
|
|
3067
3338
|
}
|
|
3068
3339
|
|
|
3069
3340
|
function useScreenshot() {
|
|
@@ -3093,13 +3364,18 @@ function useScreenshot() {
|
|
|
3093
3364
|
|
|
3094
3365
|
function useScreenshotDownload() {
|
|
3095
3366
|
const { clipRef, takeBlob } = useScreenshot();
|
|
3096
|
-
const download = React.useCallback(async () => {
|
|
3097
|
-
const blob = await takeBlob();
|
|
3367
|
+
const download = React.useCallback(async (fileName = 'voucher') => {
|
|
3368
|
+
const blob = await takeBlob('image/jpeg');
|
|
3098
3369
|
const url = window.URL.createObjectURL(blob);
|
|
3099
3370
|
const link = window.document.createElement('a');
|
|
3100
3371
|
link.style.display = 'none';
|
|
3101
3372
|
link.href = url;
|
|
3102
|
-
|
|
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`;
|
|
3103
3379
|
document.body.appendChild(link);
|
|
3104
3380
|
link.click();
|
|
3105
3381
|
document.body.removeChild(link);
|
|
@@ -3113,11 +3389,15 @@ function useScreenshotDownload() {
|
|
|
3113
3389
|
|
|
3114
3390
|
function useScreenshotWebShare() {
|
|
3115
3391
|
const { takeBlob, clipRef } = useScreenshot();
|
|
3116
|
-
const share = React.useCallback(async () => {
|
|
3117
|
-
const blob = await takeBlob();
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
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] })) {
|
|
3121
3401
|
window.print();
|
|
3122
3402
|
return;
|
|
3123
3403
|
}
|
|
@@ -3129,11 +3409,11 @@ function useScreenshotWebShare() {
|
|
|
3129
3409
|
};
|
|
3130
3410
|
}
|
|
3131
3411
|
|
|
3132
|
-
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, }) {
|
|
3133
3413
|
const { shareRef, share } = useScreenshotWebShare();
|
|
3134
3414
|
const { downloadRef, download } = useScreenshotDownload();
|
|
3135
3415
|
const handleShare = () => {
|
|
3136
|
-
share()
|
|
3416
|
+
share(fileName)
|
|
3137
3417
|
.catch(async (err) => {
|
|
3138
3418
|
if (onError) {
|
|
3139
3419
|
await onError(err);
|
|
@@ -3144,7 +3424,7 @@ function DVoucher({ amount, amountDetails, icon, title, onError, message, downlo
|
|
|
3144
3424
|
});
|
|
3145
3425
|
};
|
|
3146
3426
|
const handleDownload = () => {
|
|
3147
|
-
download()
|
|
3427
|
+
download(fileName)
|
|
3148
3428
|
.catch(async (err) => {
|
|
3149
3429
|
if (onError) {
|
|
3150
3430
|
await onError(err);
|
|
@@ -3172,80 +3452,36 @@ function DVoucher({ amount, amountDetails, icon, title, onError, message, downlo
|
|
|
3172
3452
|
return (jsxRuntime.jsx("div", { className: classNames('d-voucher', className), ref: (el) => {
|
|
3173
3453
|
shareRef.current = el;
|
|
3174
3454
|
downloadRef.current = el;
|
|
3175
|
-
}, children: jsxRuntime.jsxs("div", { children: [jsxRuntime.jsxs("div", { className: "d-voucher-header", children: [resolvedIconProps && (jsxRuntime.jsx(DIcon, Object.assign({}, resolvedIconProps))), jsxRuntime.jsxs("div", { className: "
|
|
3176
|
-
}
|
|
3177
|
-
|
|
3178
|
-
function useCountdown(seconds) {
|
|
3179
|
-
const [secondsLeft, setSecondsLeft] = React.useState(seconds);
|
|
3180
|
-
const [isActive, setIsActive] = React.useState(true);
|
|
3181
|
-
const resetCountdown = React.useCallback((newSeconds = seconds) => {
|
|
3182
|
-
setIsActive(false);
|
|
3183
|
-
setSecondsLeft(newSeconds);
|
|
3184
|
-
}, [seconds]);
|
|
3185
|
-
const restartCountdown = React.useCallback(() => {
|
|
3186
|
-
resetCountdown(seconds);
|
|
3187
|
-
setIsActive(true);
|
|
3188
|
-
}, [resetCountdown, seconds]);
|
|
3189
|
-
React.useEffect(() => {
|
|
3190
|
-
if (!isActive) {
|
|
3191
|
-
return () => { };
|
|
3192
|
-
}
|
|
3193
|
-
const interval = setInterval(() => {
|
|
3194
|
-
setSecondsLeft((prevSeconds) => {
|
|
3195
|
-
const newSeconds = prevSeconds - 1;
|
|
3196
|
-
if (newSeconds <= 0) {
|
|
3197
|
-
clearInterval(interval);
|
|
3198
|
-
setIsActive(false);
|
|
3199
|
-
return 0;
|
|
3200
|
-
}
|
|
3201
|
-
return newSeconds;
|
|
3202
|
-
});
|
|
3203
|
-
}, 1000);
|
|
3204
|
-
return () => clearInterval(interval);
|
|
3205
|
-
}, [isActive]);
|
|
3206
|
-
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" })] })] }))] }) }));
|
|
3207
3456
|
}
|
|
3208
3457
|
|
|
3209
3458
|
const defaultMessage = (secs) => (secs > 0
|
|
3210
3459
|
? `Didn't get any code? Resend in: ${secs}s`
|
|
3211
3460
|
: "Didn't get any code?");
|
|
3212
|
-
function OtpCountdown({
|
|
3213
|
-
|
|
3214
|
-
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 })] }));
|
|
3215
3463
|
}
|
|
3216
3464
|
|
|
3217
3465
|
const TEXT_PROPS = {
|
|
3218
3466
|
resend: 'Resend',
|
|
3219
|
-
resendText: 'Resend
|
|
3467
|
+
resendText: 'Resend',
|
|
3220
3468
|
submit: 'Authorize and continue',
|
|
3221
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.',
|
|
3222
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" })] })),
|
|
3223
3472
|
};
|
|
3224
3473
|
function DOtp({ className, action, isLoading, otpSize = 6, texts = TEXT_PROPS, seconds = 15, }) {
|
|
3225
|
-
const
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
if (otp.length < otpSize) {
|
|
3229
|
-
setInvalid(true);
|
|
3230
|
-
return;
|
|
3231
|
-
}
|
|
3232
|
-
setInvalid(false);
|
|
3233
|
-
await action();
|
|
3234
|
-
}, [
|
|
3235
|
-
otp.length,
|
|
3236
|
-
action,
|
|
3237
|
-
otpSize,
|
|
3238
|
-
]);
|
|
3239
|
-
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: () => {
|
|
3240
|
-
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) => {
|
|
3241
3477
|
// eslint-disable-next-line no-console
|
|
3242
3478
|
console.error('Error in DOtp action:', err);
|
|
3243
3479
|
});
|
|
3244
|
-
}, loading: isLoading }), jsxRuntime.jsx("p", { className: "
|
|
3480
|
+
}, loading: isLoading || isSubmitting }), jsxRuntime.jsx("p", { className: "d-otp-contact", children: texts.contact })] })] })] }));
|
|
3245
3481
|
}
|
|
3246
3482
|
|
|
3247
3483
|
function DefaultErrorBoundary({ resetErrorBoundary }) {
|
|
3248
|
-
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" })] }) }));
|
|
3249
3485
|
}
|
|
3250
3486
|
|
|
3251
3487
|
function DErrorBoundary({ name, fallback, resetKeys, onReset, onError, children, }) {
|
|
@@ -3263,15 +3499,15 @@ function DErrorBoundary({ name, fallback, resetKeys, onReset, onError, children,
|
|
|
3263
3499
|
}
|
|
3264
3500
|
|
|
3265
3501
|
function ErrorState({ message, onRetry, retryMessage = 'Retry', color = 'danger', }) {
|
|
3266
|
-
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" }))] }));
|
|
3267
3503
|
}
|
|
3268
3504
|
|
|
3269
3505
|
function EmptyState({ message, icon = 'FileText', actionText, onAction, }) {
|
|
3270
|
-
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" }))] }));
|
|
3271
3507
|
}
|
|
3272
3508
|
|
|
3273
3509
|
function LoadingState({ ariaLabel = 'Loading...', className }) {
|
|
3274
|
-
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 }) }));
|
|
3275
3511
|
}
|
|
3276
3512
|
|
|
3277
3513
|
function render(renderable) {
|
|
@@ -3330,7 +3566,7 @@ function DConfirmModalUI({ entry }) {
|
|
|
3330
3566
|
}
|
|
3331
3567
|
});
|
|
3332
3568
|
}, [onConfirmAction]);
|
|
3333
|
-
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 })] })] }));
|
|
3334
3570
|
}
|
|
3335
3571
|
|
|
3336
3572
|
/**
|
|
@@ -3468,6 +3704,7 @@ exports.getCssVariable = getCssVariable;
|
|
|
3468
3704
|
exports.getQueryString = getQueryString;
|
|
3469
3705
|
exports.subscribeToMediaQuery = subscribeToMediaQuery;
|
|
3470
3706
|
exports.useConfirmModal = useConfirmModal;
|
|
3707
|
+
exports.useCountdown = useCountdown;
|
|
3471
3708
|
exports.useDContext = useDContext;
|
|
3472
3709
|
exports.useDPortalContext = useDPortalContext;
|
|
3473
3710
|
exports.useDToast = useDToast;
|
|
@@ -3483,7 +3720,11 @@ exports.useMediaBreakpointUpXl = useMediaBreakpointUpXl;
|
|
|
3483
3720
|
exports.useMediaBreakpointUpXs = useMediaBreakpointUpXs;
|
|
3484
3721
|
exports.useMediaBreakpointUpXxl = useMediaBreakpointUpXxl;
|
|
3485
3722
|
exports.useMediaQuery = useMediaQuery;
|
|
3723
|
+
exports.useOtp = useOtp;
|
|
3486
3724
|
exports.useProvidedRefOrCreate = useProvidedRefOrCreate;
|
|
3725
|
+
exports.useScreenshot = useScreenshot;
|
|
3726
|
+
exports.useScreenshotDownload = useScreenshotDownload;
|
|
3727
|
+
exports.useScreenshotWebShare = useScreenshotWebShare;
|
|
3487
3728
|
exports.useStackState = useStackState;
|
|
3488
3729
|
exports.useTabContext = useTabContext;
|
|
3489
3730
|
exports.validatePhoneNumber = validatePhoneNumber;
|