@dynamic-framework/ui-react 2.6.0 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/css/dynamic-ui.css +278 -3
  2. package/dist/css/dynamic-ui.min.css +2 -2
  3. package/dist/index.esm.js +575 -284
  4. package/dist/index.esm.js.map +1 -1
  5. package/dist/index.js +577 -281
  6. package/dist/index.js.map +1 -1
  7. package/dist/types/components/DCarousel/DCarousel.d.ts +9 -2
  8. package/dist/types/components/DDropdown/DDropdown.d.ts +10 -2
  9. package/dist/types/components/DIcon/DIcon.d.ts +4 -4
  10. package/dist/types/components/DIconBase/DIconBase.d.ts +3 -2
  11. package/dist/types/components/DIconBase/index.d.ts +1 -0
  12. package/dist/types/components/DOffcanvas/DOffcanvas.d.ts +15 -3
  13. package/dist/types/components/DOtp/DOtp.d.ts +1 -0
  14. package/dist/types/components/DOtp/components/OtpCountdown.d.ts +3 -2
  15. package/dist/types/components/DVoucher/DVoucher.d.ts +3 -1
  16. package/dist/types/components/DVoucher/hooks/useScreenshotDownload.d.ts +1 -1
  17. package/dist/types/components/DVoucher/hooks/useScreenshotDownload.spec.d.ts +1 -0
  18. package/dist/types/components/DVoucher/hooks/useScreenshotWebShare.d.ts +1 -1
  19. package/dist/types/components/DVoucher/hooks/useScreenshotWebShare.spec.d.ts +1 -0
  20. package/dist/types/components/DVoucher/index.d.ts +3 -0
  21. package/dist/types/components/index.d.ts +1 -1
  22. package/dist/types/components/interface.d.ts +5 -3
  23. package/dist/types/contexts/DContext.d.ts +3 -2
  24. package/dist/types/hooks/index.d.ts +3 -0
  25. package/dist/types/hooks/tests/useCountdown.spec.d.ts +1 -0
  26. package/dist/types/hooks/useCountdown.d.ts +14 -0
  27. package/dist/types/hooks/useInputCurrency.d.ts +1 -6
  28. package/dist/types/hooks/useOtp.d.ts +98 -0
  29. package/package.json +3 -1
  30. package/src/style/abstracts/variables/_+import.scss +1 -0
  31. package/src/style/abstracts/variables/_input-currency.scss +1 -0
  32. package/src/style/abstracts/variables/_offcanvas.scss +8 -0
  33. package/src/style/components/_+import.scss +7 -0
  34. package/src/style/components/_d-box-file.scss +5 -0
  35. package/src/style/components/_d-carousel.scss +19 -1
  36. package/src/style/components/_d-close.scss +1 -0
  37. package/src/style/components/_d-confirm-modal.scss +17 -0
  38. package/src/style/components/_d-data-state-wrapper.scss +47 -0
  39. package/src/style/components/_d-datepicker.scss +11 -0
  40. package/src/style/components/_d-error-boundary.scss +7 -0
  41. package/src/style/components/_d-input-currency.scss +5 -0
  42. package/src/style/components/_d-otp.scss +54 -0
  43. package/src/style/components/_d-password-strength-meter.scss +76 -0
  44. package/src/style/components/_d-popover.scss +4 -0
  45. package/src/style/components/_d-stepper.scss +8 -0
  46. package/src/style/components/_d-tabs.scss +18 -0
  47. package/src/style/components/_d-voucher.scss +30 -0
  48. package/dist/types/components/DOtp/DOtpspec.d.ts +0 -0
  49. package/dist/types/components/DOtp/hooks/useCountdown.d.ts +0 -4
package/dist/index.esm.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
2
  import classNames from 'classnames';
3
- import React, { useEffect, useState, useCallback, useMemo, useContext, createContext, useLayoutEffect, useSyncExternalStore, forwardRef, useId, useRef, isValidElement, cloneElement, createRef } from 'react';
3
+ import React, { useEffect, useState, useCallback, useMemo, useContext, createContext, useLayoutEffect, useSyncExternalStore, createElement, forwardRef, useId, useRef, isValidElement, cloneElement, createRef } from 'react';
4
4
  import { __rest } from 'tslib';
5
5
  import * as LucideIcons from 'lucide-react';
6
+ import { isValidElementType } from 'react-is';
6
7
  import { createPortal } from 'react-dom';
7
8
  import { AnimatePresence, motion } from 'framer-motion';
8
9
  import { fromEvent } from 'file-selector';
9
- import { SplideSlide, Splide } from '@splidejs/react-splide';
10
+ import { SplideSlide, Splide, SplideTrack } from '@splidejs/react-splide';
10
11
  import currency from 'currency.js';
11
12
  import DatePicker from 'react-datepicker';
12
13
  import { getYear, format, getMonth } from 'date-fns';
@@ -309,6 +310,7 @@ const DEFAULT_STATE = {
309
310
  familyPrefix: 'bi-',
310
311
  materialStyle: false,
311
312
  },
313
+ iconRegistry: undefined,
312
314
  iconMap: {
313
315
  x: 'X',
314
316
  xLg: 'X',
@@ -374,11 +376,12 @@ const DContext = createContext(DEFAULT_STATE);
374
376
  * @template T - Map of portal name → payload shape (e.g. `{ modal: { title: string } }`).
375
377
  * Pass it once at the top level: `<DContextProvider<MyPortals> ...>`.
376
378
  */
377
- function DContextProvider({ language = DEFAULT_STATE.language, currency = DEFAULT_STATE.currency, icon = DEFAULT_STATE.icon, iconMap = DEFAULT_STATE.iconMap, portalName = 'd-portal', availablePortals, children, }) {
379
+ function DContextProvider({ language = DEFAULT_STATE.language, currency = DEFAULT_STATE.currency, icon = DEFAULT_STATE.icon, iconRegistry = DEFAULT_STATE.iconRegistry, iconMap = DEFAULT_STATE.iconMap, portalName = 'd-portal', availablePortals, children, }) {
378
380
  const [internalContext, setInternalContext,] = useState({
379
381
  language,
380
382
  currency,
381
383
  icon,
384
+ iconRegistry,
382
385
  iconMap,
383
386
  breakpoints: DEFAULT_STATE.breakpoints,
384
387
  });
@@ -495,17 +498,21 @@ function useResponsiveProp(useListener = false) {
495
498
  return { responsivePropValue };
496
499
  }
497
500
 
501
+ function isIconComponent(value) {
502
+ return typeof value !== 'string' && isValidElementType(value);
503
+ }
498
504
  function DIconBase({ icon, color, style, className, size, useListenerSize = false, hasCircle = false, materialStyle = false, familyClass, familyPrefix, strokeWidth = 2, dataAttributes, }) {
499
505
  // If materialStyle is true, use Material Design icons (legacy)
500
- const useMaterialIcons = materialStyle;
506
+ const isStringIcon = typeof icon === 'string';
507
+ const useMaterialIcons = materialStyle && isStringIcon;
501
508
  // Get Lucide icon component
502
509
  const LucideIcon = useMemo(() => {
503
- if (useMaterialIcons)
510
+ if (!isStringIcon || useMaterialIcons)
504
511
  return null;
505
512
  // Try to find the icon in Lucide (expects PascalCase)
506
513
  const icons = LucideIcons;
507
514
  return icons[icon] || null;
508
- }, [icon, useMaterialIcons]);
515
+ }, [icon, isStringIcon, useMaterialIcons]);
509
516
  const { responsivePropValue } = useResponsiveProp(useListenerSize);
510
517
  const resolvedSize = useMemo(() => {
511
518
  if (!size)
@@ -525,24 +532,35 @@ function DIconBase({ icon, color, style, className, size, useListenerSize = fals
525
532
  }, [resolvedSize]);
526
533
  // Render Material Design icon (legacy support)
527
534
  if (useMaterialIcons) {
528
- return (jsx("i", Object.assign({ className: classNames(generateClasses, familyClass), style: generateStyleVariables }, dataAttributes, { children: icon })));
535
+ return (jsx("i", Object.assign({ className: classNames(generateClasses, familyClass), style: generateStyleVariables }, dataAttributes, { children: isStringIcon ? icon : null })));
536
+ }
537
+ if (isIconComponent(icon)) {
538
+ return (jsx("span", Object.assign({ className: classNames(generateClasses), style: generateStyleVariables }, dataAttributes, { children: createElement(icon, {
539
+ width: resolvedSize || 24,
540
+ height: resolvedSize || 24,
541
+ strokeWidth,
542
+ }) })));
529
543
  }
530
544
  // Render Lucide icon
531
545
  if (!LucideIcon) {
532
- if (familyClass && familyPrefix) {
546
+ if (isStringIcon && familyClass && familyPrefix) {
533
547
  return (jsx("i", Object.assign({ className: classNames(generateClasses, familyClass, `${familyPrefix}${icon}`), style: generateStyleVariables }, dataAttributes)));
534
548
  }
535
549
  // eslint-disable-next-line no-console
536
- console.warn(`Icon "${icon}" not found in Lucide. Make sure to use PascalCase names (e.g., "Home", "User", "Settings")`);
550
+ console.warn(`Icon "${String(icon)}" not found in Lucide. Make sure to use PascalCase names (e.g., "Home", "User", "Settings")`);
537
551
  return (jsx("span", Object.assign({ className: classNames(generateClasses), style: generateStyleVariables }, dataAttributes, { children: "?" })));
538
552
  }
539
553
  return (jsx("span", Object.assign({ className: classNames(generateClasses), style: generateStyleVariables }, dataAttributes, { children: jsx(LucideIcon, { size: iconSize || 24, strokeWidth: strokeWidth }) })));
540
554
  }
541
555
 
542
556
  function DIcon(_a) {
543
- var { familyClass: propFamilyClass, familyPrefix: propFamilyPrefix, materialStyle: propMaterialStyle } = _a, props = __rest(_a, ["familyClass", "familyPrefix", "materialStyle"]);
544
- const { icon: { familyClass, familyPrefix, materialStyle, }, } = useDContext();
545
- return (jsx(DIconBase, Object.assign({ familyClass: propFamilyClass || familyClass, familyPrefix: propFamilyPrefix || familyPrefix, materialStyle: propMaterialStyle || materialStyle }, props)));
557
+ var { icon, familyClass: propFamilyClass, familyPrefix: propFamilyPrefix, materialStyle: propMaterialStyle } = _a, props = __rest(_a, ["icon", "familyClass", "familyPrefix", "materialStyle"]);
558
+ const { icon: { familyClass, familyPrefix, materialStyle, }, iconRegistry, } = useDContext();
559
+ const registryIcon = typeof icon === 'string'
560
+ ? iconRegistry === null || iconRegistry === void 0 ? void 0 : iconRegistry[icon]
561
+ : undefined;
562
+ const resolvedIcon = registryIcon || icon;
563
+ return (jsx(DIconBase, Object.assign({ icon: resolvedIcon, familyClass: propFamilyClass !== null && propFamilyClass !== void 0 ? propFamilyClass : familyClass, familyPrefix: propFamilyPrefix !== null && propFamilyPrefix !== void 0 ? propFamilyPrefix : familyPrefix, materialStyle: propMaterialStyle !== null && propMaterialStyle !== void 0 ? propMaterialStyle : materialStyle }, props)));
546
564
  }
547
565
 
548
566
  function DAlert({ color = 'success', icon: iconProp, iconFamilyClass, iconFamilyPrefix, iconMaterialStyle = false, iconClose: iconCloseProp, iconCloseFamilyClass, iconCloseFamilyPrefix, iconCloseMaterialStyle = false, showClose, onClose, children, id, className, style, dataAttributes, }) {
@@ -1088,7 +1106,7 @@ function DBoxFile(_a) {
1088
1106
  'd-box-file-invalid': isDragInvalid,
1089
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'
1090
1108
  ? children(openFileDialog)
1091
- : children || (jsx("p", { className: "text-center m-0", 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}`))) }))] }));
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}`))) }))] }));
1092
1110
  }
1093
1111
 
1094
1112
  const DButton = forwardRef((props, ref) => {
@@ -1222,8 +1240,14 @@ function DCarouselSlide(_a) {
1222
1240
  return (jsx(SplideSlide, Object.assign({ className: classNames('d-carousel-slide', className) }, props)));
1223
1241
  }
1224
1242
 
1243
+ const DEFAULT_ARROW_PATH = 'm15.5 0.932-4.3 4.38 14.5 14.6-14.5 14.5 4.3 4.4 14.6-14.6 4.4-4.3-4.4-4.4-14.6-14.6z';
1244
+ function DefaultArrowIcon() {
1245
+ return (jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 40 40", width: 40, height: 40, focusable: "false", "aria-hidden": "true", children: jsx("path", { d: DEFAULT_ARROW_PATH }) }));
1246
+ }
1225
1247
  function DCarousel(_a, ref) {
1226
- var { children, className, style, options, dataAttributes } = _a, props = __rest(_a, ["children", "className", "style", "options", "dataAttributes"]);
1248
+ var { children, className, style, options, dataAttributes, iconArrowLeft, iconArrowRight, hasTrack: propsHasTrack } = _a, props = __rest(_a, ["children", "className", "style", "options", "dataAttributes", "iconArrowLeft", "iconArrowRight", "hasTrack"]);
1249
+ // Explicit `options.arrows === false` always wins, even when icon props are set.
1250
+ const hasCustomArrows = Boolean((iconArrowLeft || iconArrowRight) && (options === null || options === void 0 ? void 0 : options.arrows) !== false);
1227
1251
  return (jsx(Splide, Object.assign({ className: classNames('d-carousel', className), style: style, ref: ref, options: Object.assign(Object.assign({}, options), { classes: {
1228
1252
  // Arrows
1229
1253
  arrows: 'splide__arrows d-carousel-arrows',
@@ -1233,7 +1257,11 @@ function DCarousel(_a, ref) {
1233
1257
  // Paginator
1234
1258
  pagination: 'splide__pagination d-carousel-pagination',
1235
1259
  page: 'splide__pagination__page d-carousel-pagination-page',
1236
- } }) }, dataAttributes, props, { children: children })));
1260
+ } }) }, dataAttributes, props, {
1261
+ // Rendering our own arrows requires taking over the track wrapper, so this
1262
+ // must be applied after `...props` to prevent a consumer-provided `hasTrack`
1263
+ // from re-enabling Splide's automatic track when custom arrows are active.
1264
+ hasTrack: hasCustomArrows ? false : propsHasTrack, children: hasCustomArrows ? (jsxs(Fragment, { children: [jsx(SplideTrack, { children: children }), jsxs("div", { className: "splide__arrows d-carousel-arrows", children: [jsx("button", { type: "button", className: "splide__arrow splide__arrow--prev d-carousel-arrow d-carousel-arrow-prev", children: iconArrowLeft ? jsx(DIcon, Object.assign({}, iconArrowLeft)) : jsx(DefaultArrowIcon, {}) }), jsx("button", { type: "button", className: "splide__arrow splide__arrow--next d-carousel-arrow d-carousel-arrow-next", children: iconArrowRight ? jsx(DIcon, Object.assign({}, iconArrowRight)) : jsx(DefaultArrowIcon, {}) })] })] })) : children })));
1237
1265
  }
1238
1266
  const ForwardedDCarousel = forwardRef(DCarousel);
1239
1267
  ForwardedDCarousel.displayName = 'DCarousel';
@@ -1454,9 +1482,9 @@ function DDatePickerHeaderSelector({ date, changeYear, changeMonth, decreaseMont
1454
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" })] }));
1455
1483
  }
1456
1484
  if (pickerType === PickerType.Quarter || pickerType === PickerType.Month) {
1457
- 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: "d-flex justify-content-center flex-grow-1", 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' } })] }));
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' } })] }));
1458
1486
  }
1459
- 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: "mb-0 fw-normal", 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' } })] })] }));
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' } })] })] }));
1460
1488
  }
1461
1489
 
1462
1490
  function DDatePicker(_a) {
@@ -1591,11 +1619,24 @@ function formatValue(value, currencyOptions) {
1591
1619
  }
1592
1620
  return currency(value, Object.assign(Object.assign({}, currencyOptions), { symbol: '' })).format();
1593
1621
  }
1594
- function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref) {
1622
+ function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref, minValue, maxValue) {
1595
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]);
1596
1637
  const [innerType, setInnerType] = useState('text');
1597
- const [innerNumber, setInnerNumber] = useState(value);
1598
- const [innerString, setInnerString] = useState(formatValue(value, currencyOptions));
1638
+ const [innerNumber, setInnerNumber] = useState(clampValue(value));
1639
+ const [innerString, setInnerString] = useState(formatValue(clampValue(value), currencyOptions));
1599
1640
  const handleOnFocus = useCallback((event) => {
1600
1641
  event.stopPropagation();
1601
1642
  setInnerType('number');
@@ -1604,15 +1645,14 @@ function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref
1604
1645
  const handleOnBlur = useCallback((event) => {
1605
1646
  event.stopPropagation();
1606
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
+ }
1607
1654
  onBlur === null || onBlur === void 0 ? void 0 : onBlur(event);
1608
- }, [onBlur]);
1609
- const generateStyleVariables = useMemo(() => ({
1610
- [`--${PREFIX_BS}input-currency-component-symbol-color`]: `var(--${PREFIX_BS}secondary)`,
1611
- [`--${PREFIX_BS}input-currency-symbol-color`]: `var(--${PREFIX_BS}input-currency-component-symbol-color)`,
1612
- }), []);
1613
- const generateSymbolStyleVariables = useMemo(() => ({
1614
- color: `var(--${PREFIX_BS}input-currency-symbol-color)`,
1615
- }), []);
1655
+ }, [onBlur, innerNumber, clampValue, currencyOptions, onChange]);
1616
1656
  const handleOnChange = useCallback((newValue) => {
1617
1657
  const newNumber = (newValue === undefined || newValue === '') ? undefined : Number(newValue);
1618
1658
  if (newNumber !== innerNumber) {
@@ -1621,7 +1661,12 @@ function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref
1621
1661
  onChange === null || onChange === void 0 ? void 0 : onChange(newNumber);
1622
1662
  }
1623
1663
  }, [currencyOptions, onChange, innerNumber]);
1664
+ const isMountedRef = useRef(false);
1624
1665
  useEffect(() => {
1666
+ if (!isMountedRef.current) {
1667
+ isMountedRef.current = true;
1668
+ return;
1669
+ }
1625
1670
  if (value !== innerNumber) {
1626
1671
  setInnerNumber(value);
1627
1672
  setInnerString(formatValue(value, currencyOptions));
@@ -1635,8 +1680,6 @@ function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref
1635
1680
  handleOnFocus,
1636
1681
  handleOnChange,
1637
1682
  handleOnBlur,
1638
- generateStyleVariables,
1639
- generateSymbolStyleVariables,
1640
1683
  };
1641
1684
  }
1642
1685
 
@@ -1741,6 +1784,149 @@ function useConfirmModal(config) {
1741
1784
  return { open };
1742
1785
  }
1743
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
+
1744
1930
  function DInputCounter(_a, ref) {
1745
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"]);
1746
1932
  const { handleOnWheel, } = useDisableInputWheel(ref);
@@ -1783,8 +1969,8 @@ function DInputCurrency(_a, ref) {
1783
1969
  var { value, minValue, maxValue, currencyCode, onFocus, onBlur, onChange } = _a, props = __rest(_a, ["value", "minValue", "maxValue", "currencyCode", "onFocus", "onBlur", "onChange"]);
1784
1970
  const { currency: currencyOptions } = useDContext();
1785
1971
  const { handleOnWheel, } = useDisableInputWheel(ref);
1786
- const { inputRef, innerValue, innerType, handleOnFocus, handleOnChange, handleOnBlur, generateStyleVariables, generateSymbolStyleVariables, } = useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref);
1787
- return (jsx(ForwardedDInput, Object.assign({ ref: inputRef, value: innerValue, onChange: handleOnChange, style: generateStyleVariables, inputMode: "decimal", type: innerType, onFocus: handleOnFocus, onBlur: handleOnBlur, onWheel: handleOnWheel, inputStart: (jsx("span", { slot: "input-start", style: generateSymbolStyleVariables, children: currencyCode || currencyOptions.symbol })) }, props)));
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)));
1788
1974
  }
1789
1975
  const ForwardedDInputCurrency = forwardRef(DInputCurrency);
1790
1976
  ForwardedDInputCurrency.displayName = 'DInputCurrency';
@@ -1806,26 +1992,25 @@ ForwardedDInputPassword.displayName = 'DInputPassword';
1806
1992
 
1807
1993
  function PasswordCheckItem({ password, regex, text, }) {
1808
1994
  const isValid = regex.test(password);
1809
- return (jsxs("li", { className: "d-flex gap-2 align-items-start small text-gray-600", children: [jsx(DIcon, { className: classNames('flex-shrink-0', isValid ? 'text-success' : 'text-gray-300'), icon: isValid ? 'CircleCheck' : 'Circle', size: "16px" }), jsx("span", { className: classNames({ 'text-success': isValid }), children: text })] }));
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 })] }));
1810
1996
  }
1811
1997
 
1812
- const getColorClass = (strength, total) => {
1998
+ const getStrengthModifier = (strength, total) => {
1813
1999
  const percentage = total > 0 ? strength / total : 0;
1814
2000
  if (percentage === 0)
1815
- return 'bg-gray-200';
2001
+ return 'is-empty';
1816
2002
  if (percentage <= 0.25)
1817
- return 'bg-danger';
2003
+ return 'is-weak';
1818
2004
  if (percentage <= 0.5)
1819
- return 'bg-warning';
2005
+ return 'is-fair';
1820
2006
  if (percentage <= 0.75)
1821
- return 'bg-info';
1822
- return 'bg-success';
2007
+ return 'is-good';
2008
+ return 'is-strong';
1823
2009
  };
1824
2010
  function PasswordStrengthBar({ strength, total }) {
1825
2011
  const percentage = total > 0 ? (strength / total) * 100 : 0;
1826
- return (jsx("div", { className: "w-100 rounded-3 overflow-hidden bg-gray-100 mb-2", style: { height: '8px' }, children: jsx("div", { className: `h-100 ${getColorClass(strength, total)}`, style: {
2012
+ return (jsx("div", { className: "d-password-strength-bar", children: jsx("div", { className: `d-password-strength-bar-fill ${getStrengthModifier(strength, total)}`, style: {
1827
2013
  width: `${percentage}%`,
1828
- transition: 'width 0.3s ease-in-out',
1829
2014
  } }) }));
1830
2015
  }
1831
2016
 
@@ -1861,7 +2046,7 @@ function PasswordChecksList({ password, validationMessages, enabledChecks, }) {
1861
2046
  const passwordChecks = allChecks.filter((check) => enabledChecks.includes(check.key));
1862
2047
  const passed = passwordChecks.filter((r) => r.regex.test(password)).length;
1863
2048
  const total = passwordChecks.length;
1864
- return (jsxs("div", { className: "mt-2", children: [jsx(PasswordStrengthBar, { strength: passed, total: total }), jsx("ul", { className: "list-unstyled m-0 d-flex flex-column gap-2", children: passwordChecks.map(({ key, regex, text }) => (jsx(PasswordCheckItem, { password: password, regex: regex, text: text }, key))) })] }));
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))) })] }));
1865
2050
  }
1866
2051
 
1867
2052
  const DEFAULT_VALIDATION_MESSAGES = {
@@ -2211,7 +2396,7 @@ var DListGroup$1 = Object.assign(DListGroup, {
2211
2396
  function DModalHeader({ showCloseButton, onClose, children, className, style, iconFamilyClass, iconFamilyPrefix, icon: iconProp, materialStyle = false, }) {
2212
2397
  const { iconMap: { xLg, }, } = useDContext();
2213
2398
  const icon = useMemo(() => iconProp || xLg, [iconProp, xLg]);
2214
- 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 align-self-center", "aria-label": "Close", onClick: onClose, children: jsx(DIcon, { icon: icon, familyClass: iconFamilyClass, familyPrefix: iconFamilyPrefix, materialStyle: materialStyle }) }))] }), jsx("div", { className: "d-modal-separator" })] }));
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" })] }));
2215
2400
  }
2216
2401
 
2217
2402
  function DModalBody({ children, className, style, }) {
@@ -2255,7 +2440,7 @@ var DModal$1 = Object.assign(DModal, {
2255
2440
  function DOffcanvasHeader({ showCloseButton, onClose, children, className, style, iconFamilyClass, iconFamilyPrefix, icon: iconProp, materialStyle = false, }) {
2256
2441
  const { iconMap: { xLg, }, } = useDContext();
2257
2442
  const icon = useMemo(() => iconProp || xLg, [iconProp, xLg]);
2258
- 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 align-self-center", "aria-label": "Close", onClick: onClose, children: jsx(DIcon, { icon: icon, familyClass: iconFamilyClass, familyPrefix: iconFamilyPrefix, materialStyle: materialStyle }) }))] }), jsx("div", { className: "d-offcanvas-separator" })] }));
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" })] }));
2259
2444
  }
2260
2445
 
2261
2446
  function DOffcanvasBody({ children, className, style, }) {
@@ -2296,10 +2481,37 @@ const defaultTransition = {
2296
2481
  ease: 'easeInOut',
2297
2482
  duration: 0.3,
2298
2483
  };
2299
- function DOffcanvas({ name, className, style, staticBackdrop, scrollable, openFrom = 'end', transition, children, dataAttributes, }) {
2484
+ function DOffcanvas({ name, className, style, staticBackdrop, scrollable, openFrom = 'end', width, height, transition, children, dataAttributes, }) {
2485
+ // Only subscribe to breakpoint-change listeners when a responsive object is
2486
+ // actually provided, avoiding unnecessary matchMedia subscriptions for the
2487
+ // common case of plain string values.
2488
+ const hasResponsiveProp = typeof openFrom === 'object'
2489
+ || typeof width === 'object'
2490
+ || typeof height === 'object';
2491
+ const { responsivePropValue } = useResponsiveProp(hasResponsiveProp);
2492
+ const resolvedOpenFrom = useMemo(() => {
2493
+ var _a;
2494
+ if (typeof openFrom === 'string')
2495
+ return openFrom;
2496
+ return (_a = responsivePropValue(openFrom)) !== null && _a !== void 0 ? _a : 'end';
2497
+ }, [responsivePropValue, openFrom]);
2498
+ const resolvedWidth = useMemo(() => {
2499
+ if (!width)
2500
+ return undefined;
2501
+ if (typeof width === 'string')
2502
+ return width;
2503
+ return responsivePropValue(width);
2504
+ }, [responsivePropValue, width]);
2505
+ const resolvedHeight = useMemo(() => {
2506
+ if (!height)
2507
+ return undefined;
2508
+ if (typeof height === 'string')
2509
+ return height;
2510
+ return responsivePropValue(height);
2511
+ }, [responsivePropValue, height]);
2300
2512
  return (jsx(motion.div, Object.assign({ className: classNames('offcanvas portal show', {
2301
- [`offcanvas-${openFrom}`]: openFrom,
2302
- }, className), style: Object.assign(Object.assign({}, style), { transition: 'none' }), id: name, tabIndex: -1, "aria-labelledby": `${name}Label`, "aria-hidden": "false", custom: openFrom, variants: variants, initial: "hidden", animate: "visible", exit: "hidden", transition: Object.assign(Object.assign({}, (transition !== null && transition !== void 0 ? transition : defaultTransition)), { delay: 0.15 }) }, staticBackdrop && ({
2513
+ [`offcanvas-${resolvedOpenFrom}`]: resolvedOpenFrom,
2514
+ }, className), style: Object.assign(Object.assign(Object.assign(Object.assign({}, style), { transition: 'none' }), (resolvedWidth && { [`--${PREFIX_BS}offcanvas-width`]: resolvedWidth })), (resolvedHeight && { [`--${PREFIX_BS}offcanvas-height`]: resolvedHeight })), id: name, tabIndex: -1, "aria-labelledby": `${name}Label`, "aria-hidden": "false", custom: resolvedOpenFrom, variants: variants, initial: "hidden", animate: "visible", exit: "hidden", transition: Object.assign(Object.assign({}, (transition !== null && transition !== void 0 ? transition : defaultTransition)), { delay: 0.15 }) }, staticBackdrop && ({
2303
2515
  [`data-${PREFIX_BS}backdrop`]: 'static',
2304
2516
  [`data-${PREFIX_BS}keyboard`]: 'false',
2305
2517
  }), scrollable && ({
@@ -2355,7 +2567,7 @@ function DPopover({ children, renderComponent, open, setOpen, adjustContentToRen
2355
2567
  }
2356
2568
  const reference = cloneElement(triggerElement, Object.assign({ ref: refs.setReference }, getReferenceProps(triggerElement.props)));
2357
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', {
2358
- 'w-100': adjustContentToRender,
2570
+ 'd-popover-content-fill': adjustContentToRender,
2359
2571
  }), ref: refs.setFloating, style: floatingStyles, "aria-labelledby": headingId }, getFloatingProps(), { children: children })) }))] })));
2360
2572
  }
2361
2573
 
@@ -2419,13 +2631,13 @@ function DStepper$1({ options, currentStep, className, style, }) {
2419
2631
  }
2420
2632
 
2421
2633
  function DStepper({ options, currentStep, iconSuccess, iconSuccessFamilyClass, iconSuccessFamilyPrefix, iconSuccessMaterialStyle = false, vertical = false, breakpoint = 'lg', className, completed = false, style, dataAttributes, }) {
2422
- return (jsxs("div", Object.assign({ className: className, style: style }, dataAttributes, { children: [jsx("div", { className: `d-block d-${breakpoint}-none`, children: jsx(DStepper$1, { options: options, currentStep: currentStep }) }), jsx("div", { className: `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 }) })] })));
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 }) })] })));
2423
2635
  }
2424
2636
 
2425
2637
  const ARROW_WIDTH = 8;
2426
2638
  const ARROW_HEIGHT = 4;
2427
- const GAP = 2;
2428
- 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, }) {
2429
2641
  const [isOpen, setIsOpen] = useState(open);
2430
2642
  const arrowRef = useRef(null);
2431
2643
  const { refs, context, floatingStyles, } = useFloating({
@@ -2494,8 +2706,16 @@ function DTabs({ children, defaultSelected, onChange, options, className, classN
2494
2706
  useEffect(() => {
2495
2707
  setSelected(defaultSelected);
2496
2708
  }, [defaultSelected]);
2497
- const generateClasses = useMemo(() => (Object.assign({ nav: true, 'flex-column align-items-center': vertical && variant !== 'tabs', [`nav-${variant}`]: true }, className && { [className]: true })), [vertical, variant, className]);
2709
+ const generateClasses = useMemo(() => (Object.assign({ nav: true, 'd-tabs-nav-vertical': vertical && variant !== 'tabs', [`nav-${variant}`]: true }, className && { [className]: true })), [vertical, variant, className]);
2498
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;
2499
2719
  useEffect(() => {
2500
2720
  tabRefs.current = options.map((_, i) => tabRefs.current[i] || createRef());
2501
2721
  }, [options]);
@@ -2510,20 +2730,28 @@ function DTabs({ children, defaultSelected, onChange, options, className, classN
2510
2730
  setSelected(firstEnabled.tab);
2511
2731
  }
2512
2732
  }, [options, selected]);
2513
- // Declarative focus management
2514
- const focusTab = (idx) => {
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) => {
2515
2738
  var _a;
2516
2739
  if ((_a = tabRefs.current[idx]) === null || _a === void 0 ? void 0 : _a.current) {
2517
2740
  tabRefs.current[idx].current.focus();
2518
2741
  }
2519
- };
2520
- // 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).
2521
2749
  useEffect(() => {
2522
- const idx = options.findIndex((opt) => opt.tab === selected && !opt.disabled);
2750
+ const idx = optionsRef.current.findIndex((opt) => opt.tab === selected && !opt.disabled);
2523
2751
  if (idx !== -1) {
2524
2752
  focusTab(idx);
2525
2753
  }
2526
- }, [selected, options]);
2754
+ }, [selected, focusTab]);
2527
2755
  const handleKeyDown = useCallback((idx, e) => {
2528
2756
  const count = options.length;
2529
2757
  if (count === 0)
@@ -2552,7 +2780,7 @@ function DTabs({ children, defaultSelected, onChange, options, className, classN
2552
2780
  }
2553
2781
  }
2554
2782
  }
2555
- }, [options, vertical]);
2783
+ }, [options, vertical, focusTab]);
2556
2784
  let tablistProps = {};
2557
2785
  if (ariaLabelledBy) {
2558
2786
  tablistProps = { 'aria-labelledby': ariaLabelledBy };
@@ -2565,12 +2793,12 @@ function DTabs({ children, defaultSelected, onChange, options, className, classN
2565
2793
  isSelected,
2566
2794
  }), [isSelected]);
2567
2795
  return (jsx(TabContext.Provider, { value: value, children: jsxs("div", Object.assign({ className: classNames({
2568
- 'd-flex w-100': true,
2569
- 'flex-column': !vertical || variant === 'tabs',
2796
+ 'd-tabs': true,
2797
+ 'd-tabs-column': !vertical || variant === 'tabs',
2570
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) => {
2571
2799
  const isTabSelected = !!option.tab && option.tab === selected;
2572
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));
2573
- }) })), jsx("div", { className: "tab-content w-100", children: children })] })) }));
2801
+ }) })), jsx("div", { className: "d-tabs-content tab-content", children: children })] })) }));
2574
2802
  }
2575
2803
  var DTabs$1 = Object.assign(DTabs, {
2576
2804
  Tab: DTabContent,
@@ -2632,11 +2860,11 @@ function useDToast() {
2632
2860
  if (!description) {
2633
2861
  return (jsx(DToast$1, { className: classNames({
2634
2862
  [`toast-${color}`]: !!color,
2635
- }, '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 align-self-center", "aria-label": "Close", onClick: () => toast.dismiss(id), children: jsx(DIcon, { icon: closeIcon || xLg }) })] }) }));
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 }) })] }) }));
2636
2864
  }
2637
2865
  return (jsxs(DToast$1, { className: classNames({
2638
2866
  [`toast-${color}`]: !!color,
2639
- }, '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 align-self-center", "aria-label": "Close", onClick: () => toast.dismiss(id), children: jsx(DIcon, { icon: closeIcon || xLg }) })] }), jsx(DToast$1.Body, { children: jsx("span", { children: description }) })] }));
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 }) })] }));
2640
2868
  }, toastProps);
2641
2869
  }, [xLg]);
2642
2870
  return {
@@ -2658,11 +2886,10 @@ async function configureI8n(resources, _a = {}) {
2658
2886
  .then((t) => t);
2659
2887
  }
2660
2888
 
2661
- function getQueryString(value, config = {
2662
- useSearch: true,
2663
- }) {
2664
- const urlParams = new URLSearchParams(config.useSearch ? window.location.search : '');
2665
- 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;
2666
2893
  }
2667
2894
 
2668
2895
  function changeQueryString(values, { useSearch = true, pushState = false, } = {}) {
@@ -2785,89 +3012,228 @@ function DCreditCard({ brand = 'visa', name, number, holderText = 'Card Holder',
2785
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 })] })] }));
2786
3013
  }
2787
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;
2788
3021
  const getItemClass = (action) => classNames({
2789
3022
  'dropdown-item d-flex align-items-center': true,
2790
3023
  [`dropdown-item-${action.color}`]: !!action.color,
2791
3024
  disabled: action.disabled,
2792
3025
  });
2793
- function DDropdown({ actions, dropdownToggle, className, classNameMenu, asPortal = false, placement = 'auto', }) {
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', }) {
2794
3177
  const [open, setOpen] = useState(false);
2795
- const [position, setPosition] = useState('down');
2796
- const [menuCoords, setMenuCoords] = useState(null);
2797
- // Used in default (non-portal) mode: wraps the entire dropdown
2798
- const dropdownRef = useRef(null);
2799
- // Used in portal mode: ref on the toggle wrapper to read its viewport coords
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.
2800
3185
  const toggleRef = useRef(null);
2801
- // Ref on the rendered <ul> — used for outside-click detection in portal mode
3186
+ // Ref on the rendered <ul> — used to measure its size and for outside-click detection.
2802
3187
  const menuRef = useRef(null);
2803
3188
  const toggle = () => {
2804
- setMenuCoords(null);
3189
+ setCoords(null);
2805
3190
  setOpen((prev) => !prev);
2806
3191
  };
2807
- const resolvedInlinePosition = placement === 'auto' ? position : placement;
2808
- // Compute fixed coords from the toggle's bounding rect (portal mode only)
2809
- const computePortalCoords = useCallback(() => {
2810
- 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);
2811
3208
  return;
2812
- const rect = toggleRef.current.getBoundingClientRect();
2813
- const spaceRight = window.innerWidth - rect.right;
2814
- const spaceBottom = window.innerHeight - rect.bottom;
2815
- const spaceTop = rect.top;
2816
- let resolvedPlacement;
2817
- if (placement === 'auto') {
2818
- if (spaceBottom < 150) {
2819
- resolvedPlacement = 'up';
2820
- }
2821
- else if (spaceRight < 150) {
2822
- resolvedPlacement = 'start';
2823
- }
2824
- else {
2825
- resolvedPlacement = 'down';
2826
- }
2827
- }
2828
- else {
2829
- resolvedPlacement = placement;
2830
- }
2831
- const openStart = resolvedPlacement === 'start';
2832
- const openEnd = resolvedPlacement === 'end';
2833
- const openUp = resolvedPlacement === 'up';
2834
- let availableHeight = spaceBottom;
2835
- if (openUp) {
2836
- availableHeight = spaceTop;
2837
- }
2838
- else if (openStart || openEnd) {
2839
- availableHeight = window.innerHeight - rect.top;
2840
- }
2841
- const verticalCoords = openUp
2842
- ? { bottom: window.innerHeight - rect.top + 4 }
2843
- : { top: openStart || openEnd ? rect.top : rect.bottom + 4 };
2844
- let horizontalCoords;
2845
- if (openStart) {
2846
- horizontalCoords = { right: window.innerWidth - rect.left + 4 };
2847
- }
2848
- else if (openEnd) {
2849
- horizontalCoords = { left: rect.right + 4 };
2850
- }
2851
- else {
2852
- horizontalCoords = { left: rect.left };
2853
3209
  }
2854
- setMenuCoords(Object.assign(Object.assign(Object.assign({}, verticalCoords), horizontalCoords), { minWidth: Math.max(rect.width, 160), maxHeight: `${Math.max(availableHeight - 12, 120)}px` }));
2855
- }, [placement]);
2856
- // Outside-click: default mode
2857
- useEffect(() => {
2858
- if (asPortal)
2859
- return () => { };
2860
- const handler = (event) => {
2861
- if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
2862
- setOpen(false);
2863
- }
2864
- };
2865
- document.addEventListener('mousedown', handler);
2866
- return () => document.removeEventListener('mousedown', handler);
2867
- }, [asPortal]);
2868
- // Outside-click: portal mode (must check both toggle and floating menu)
2869
- useEffect(() => {
2870
- if (!asPortal || !open)
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)
2871
3237
  return () => { };
2872
3238
  const handler = (event) => {
2873
3239
  var _a, _b;
@@ -2878,35 +3244,25 @@ function DDropdown({ actions, dropdownToggle, className, classNameMenu, asPortal
2878
3244
  };
2879
3245
  document.addEventListener('mousedown', handler);
2880
3246
  return () => document.removeEventListener('mousedown', handler);
2881
- }, [asPortal, open]);
2882
- // Auto-position: default mode
2883
- useEffect(() => {
2884
- if (asPortal || !open || !dropdownRef.current || placement !== 'auto')
2885
- return;
2886
- const rect = dropdownRef.current.getBoundingClientRect();
2887
- const spaceBottom = window.innerHeight - rect.bottom;
2888
- const spaceRight = window.innerWidth - rect.right;
2889
- if (spaceBottom < 150)
2890
- setPosition('up');
2891
- else if (spaceRight < 150)
2892
- setPosition('start');
2893
- else
2894
- setPosition('down');
2895
- }, [asPortal, open, placement]);
2896
- // Compute + track coords: portal mode
2897
- // Re-runs whenever the menu opens and keeps coords fresh on scroll/resize
2898
- useEffect(() => {
2899
- 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)
2900
3253
  return () => { };
2901
- computePortalCoords();
2902
- const update = () => computePortalCoords();
2903
- window.addEventListener('scroll', update, { passive: true, capture: true });
2904
- window.addEventListener('resize', update, { passive: true });
3254
+ updatePosition();
3255
+ window.addEventListener('scroll', scheduleUpdatePosition, { passive: true, capture: true });
3256
+ window.addEventListener('resize', scheduleUpdatePosition, { passive: true });
2905
3257
  return () => {
2906
- window.removeEventListener('scroll', update, { capture: true });
2907
- window.removeEventListener('resize', update);
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
+ }
2908
3264
  };
2909
- }, [asPortal, open, computePortalCoords]);
3265
+ }, [open, updatePosition, scheduleUpdatePosition]);
2910
3266
  let ToggleElement;
2911
3267
  if (dropdownToggle) {
2912
3268
  ToggleElement = typeof dropdownToggle === 'function'
@@ -2916,57 +3272,30 @@ function DDropdown({ actions, dropdownToggle, className, classNameMenu, asPortal
2916
3272
  else {
2917
3273
  ToggleElement = (jsx(DButtonIcon, { variant: "link", stopPropagationEnabled: false, "aria-label": "Toggle Dropdown", "aria-haspopup": "menu", "aria-expanded": open, onClick: toggle, icon: "MoreVertical" }));
2918
3274
  }
2919
- const portalMenuStyle = asPortal && menuCoords
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
2920
3278
  ? {
2921
3279
  position: 'fixed',
2922
- top: menuCoords.top,
2923
- bottom: menuCoords.bottom,
2924
- left: menuCoords.left,
2925
- right: menuCoords.right,
2926
- minWidth: menuCoords.minWidth,
2927
- maxHeight: menuCoords.maxHeight,
3280
+ top: coords.top,
3281
+ left: coords.left,
3282
+ minWidth: coords.minWidth,
3283
+ maxHeight: coords.maxHeight,
2928
3284
  overflowY: 'auto',
2929
3285
  zIndex: 1050,
2930
3286
  transform: 'none',
3287
+ visibility: 'visible',
2931
3288
  }
2932
- : undefined;
2933
- let inlineTop = '100%';
2934
- if (resolvedInlinePosition === 'up') {
2935
- inlineTop = 'auto';
2936
- }
2937
- else if (resolvedInlinePosition === 'start' || resolvedInlinePosition === 'end') {
2938
- inlineTop = 0;
2939
- }
2940
- let inlineLeft = 0;
2941
- if (resolvedInlinePosition === 'start') {
2942
- inlineLeft = 'auto';
2943
- }
2944
- else if (resolvedInlinePosition === 'end') {
2945
- inlineLeft = '100%';
2946
- }
2947
- let inlineRight = 'auto';
2948
- if (resolvedInlinePosition === 'start') {
2949
- inlineRight = '100%';
2950
- }
2951
- let inlineTransform = 'translateY(4px)';
2952
- if (resolvedInlinePosition === 'up') {
2953
- inlineTransform = 'translateY(-4px)';
2954
- }
2955
- else if (resolvedInlinePosition === 'start') {
2956
- inlineTransform = 'translateX(-4px)';
2957
- }
2958
- else if (resolvedInlinePosition === 'end') {
2959
- inlineTransform = 'translateX(4px)';
2960
- }
2961
- const inlineMenuStyle = {
2962
- position: 'absolute',
2963
- top: inlineTop,
2964
- bottom: resolvedInlinePosition === 'up' ? '100%' : 'auto',
2965
- left: inlineLeft,
2966
- right: inlineRight,
2967
- transform: inlineTransform,
2968
- };
2969
- 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) => {
2970
3299
  if (action.isDivider) {
2971
3300
  return jsx("hr", { className: "dropdown-divider" }, index);
2972
3301
  }
@@ -2985,10 +3314,7 @@ function DDropdown({ actions, dropdownToggle, className, classNameMenu, asPortal
2985
3314
  }
2986
3315
  }, disabled: action.disabled, children: [action.icon && jsx(DIcon, { icon: action.icon, className: "me-2", size: "1rem" }), action.label] })) }, index));
2987
3316
  }) }));
2988
- if (asPortal) {
2989
- return (jsxs("div", { ref: toggleRef, className: classNames('dropdown', className), children: [ToggleElement, open && menuCoords && createPortal(menuItems, document.body)] }));
2990
- }
2991
- 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)] }));
2992
3318
  }
2993
3319
 
2994
3320
  function useScreenshot() {
@@ -3018,13 +3344,18 @@ function useScreenshot() {
3018
3344
 
3019
3345
  function useScreenshotDownload() {
3020
3346
  const { clipRef, takeBlob } = useScreenshot();
3021
- const download = useCallback(async () => {
3022
- const blob = await takeBlob();
3347
+ const download = useCallback(async (fileName = 'voucher') => {
3348
+ const blob = await takeBlob('image/jpeg');
3023
3349
  const url = window.URL.createObjectURL(blob);
3024
3350
  const link = window.document.createElement('a');
3025
3351
  link.style.display = 'none';
3026
3352
  link.href = url;
3027
- link.download = 'voucher.jpg';
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`;
3028
3359
  document.body.appendChild(link);
3029
3360
  link.click();
3030
3361
  document.body.removeChild(link);
@@ -3038,11 +3369,15 @@ function useScreenshotDownload() {
3038
3369
 
3039
3370
  function useScreenshotWebShare() {
3040
3371
  const { takeBlob, clipRef } = useScreenshot();
3041
- const share = useCallback(async () => {
3042
- const blob = await takeBlob();
3043
- const image = new File([blob], 'voucher.jpeg', { type: 'image/jpeg' });
3044
- if (!navigator.canShare
3045
- && (navigator.canShare && !navigator.canShare({ files: [image] }))) {
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] })) {
3046
3381
  window.print();
3047
3382
  return;
3048
3383
  }
@@ -3054,11 +3389,11 @@ function useScreenshotWebShare() {
3054
3389
  };
3055
3390
  }
3056
3391
 
3057
- 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, }) {
3058
3393
  const { shareRef, share } = useScreenshotWebShare();
3059
3394
  const { downloadRef, download } = useScreenshotDownload();
3060
3395
  const handleShare = () => {
3061
- share()
3396
+ share(fileName)
3062
3397
  .catch(async (err) => {
3063
3398
  if (onError) {
3064
3399
  await onError(err);
@@ -3069,7 +3404,7 @@ function DVoucher({ amount, amountDetails, icon, title, onError, message, downlo
3069
3404
  });
3070
3405
  };
3071
3406
  const handleDownload = () => {
3072
- download()
3407
+ download(fileName)
3073
3408
  .catch(async (err) => {
3074
3409
  if (onError) {
3075
3410
  await onError(err);
@@ -3097,80 +3432,36 @@ function DVoucher({ amount, amountDetails, icon, title, onError, message, downlo
3097
3432
  return (jsx("div", { className: classNames('d-voucher', className), ref: (el) => {
3098
3433
  shareRef.current = el;
3099
3434
  downloadRef.current = el;
3100
- }, children: jsxs("div", { children: [jsxs("div", { className: "d-voucher-header", children: [resolvedIconProps && (jsx(DIcon, Object.assign({}, resolvedIconProps))), jsxs("div", { className: "text-center", children: [jsx("h3", { className: "mb-2", children: title }), jsx("p", { className: "m-0", children: message })] })] }), amount && (jsxs("div", { className: "d-voucher-amount", children: [jsx("div", { className: classNames('text-center fw-bold fs-3', amountDetails ? 'mb-1' : 'm-0'), children: amount }), amountDetails] })), jsx("hr", { className: "my-4" }), 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" })] })] }) }));
3101
- }
3102
-
3103
- function useCountdown(seconds) {
3104
- const [secondsLeft, setSecondsLeft] = useState(seconds);
3105
- const [isActive, setIsActive] = useState(true);
3106
- const resetCountdown = useCallback((newSeconds = seconds) => {
3107
- setIsActive(false);
3108
- setSecondsLeft(newSeconds);
3109
- }, [seconds]);
3110
- const restartCountdown = useCallback(() => {
3111
- resetCountdown(seconds);
3112
- setIsActive(true);
3113
- }, [resetCountdown, seconds]);
3114
- useEffect(() => {
3115
- if (!isActive) {
3116
- return () => { };
3117
- }
3118
- const interval = setInterval(() => {
3119
- setSecondsLeft((prevSeconds) => {
3120
- const newSeconds = prevSeconds - 1;
3121
- if (newSeconds <= 0) {
3122
- clearInterval(interval);
3123
- setIsActive(false);
3124
- return 0;
3125
- }
3126
- return newSeconds;
3127
- });
3128
- }, 1000);
3129
- return () => clearInterval(interval);
3130
- }, [isActive]);
3131
- 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" })] })] }))] }) }));
3132
3436
  }
3133
3437
 
3134
3438
  const defaultMessage = (secs) => (secs > 0
3135
3439
  ? `Didn't get any code? Resend in: ${secs}s`
3136
3440
  : "Didn't get any code?");
3137
- function OtpCountdown({ seconds, resendText, message, }) {
3138
- const { secondsLeft, restartCountdown } = useCountdown(seconds);
3139
- 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 })] }));
3140
3443
  }
3141
3444
 
3142
3445
  const TEXT_PROPS = {
3143
3446
  resend: 'Resend',
3144
- resendText: 'Resend code',
3447
+ resendText: 'Resend',
3145
3448
  submit: 'Authorize and continue',
3146
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.',
3147
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" })] })),
3148
3452
  };
3149
3453
  function DOtp({ className, action, isLoading, otpSize = 6, texts = TEXT_PROPS, seconds = 15, }) {
3150
- const [otp, setOtp] = useState('');
3151
- const [invalid, setInvalid] = useState(false);
3152
- const handler = useCallback(async () => {
3153
- if (otp.length < otpSize) {
3154
- setInvalid(true);
3155
- return;
3156
- }
3157
- setInvalid(false);
3158
- await action();
3159
- }, [
3160
- otp.length,
3161
- action,
3162
- otpSize,
3163
- ]);
3164
- 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: () => {
3165
- 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) => {
3166
3457
  // eslint-disable-next-line no-console
3167
3458
  console.error('Error in DOtp action:', err);
3168
3459
  });
3169
- }, loading: isLoading }), jsx("p", { className: "small ms-lg-auto mb-0", children: texts.contact })] })] })] }));
3460
+ }, loading: isLoading || isSubmitting }), jsx("p", { className: "d-otp-contact", children: texts.contact })] })] })] }));
3170
3461
  }
3171
3462
 
3172
3463
  function DefaultErrorBoundary({ resetErrorBoundary }) {
3173
- return (jsx(DAlert, { color: "danger", showClose: false, children: jsxs("div", { className: "d-flex align-items-center gap-2", children: [jsx("span", { children: "An unexpected error occurred." }), jsx(DButton, { color: "secondary", variant: "outline", size: "sm", onClick: resetErrorBoundary, children: "Retry" })] }) }));
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" })] }) }));
3174
3465
  }
3175
3466
 
3176
3467
  function DErrorBoundary({ name, fallback, resetKeys, onReset, onError, children, }) {
@@ -3188,15 +3479,15 @@ function DErrorBoundary({ name, fallback, resetKeys, onReset, onError, children,
3188
3479
  }
3189
3480
 
3190
3481
  function ErrorState({ message, onRetry, retryMessage = 'Retry', color = 'danger', }) {
3191
- return (jsxs(DAlert, { color: color, className: "d-flex align-items-center gap-3", children: [jsx("div", { className: "flex-grow-1", children: jsx("p", { className: "mb-0", children: message !== null && message !== void 0 ? message : 'An unexpected error occurred.' }) }), onRetry && (jsx(DButton, { onClick: onRetry, text: retryMessage, variant: "outline", iconStart: "RefreshCw" }))] }));
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" }))] }));
3192
3483
  }
3193
3484
 
3194
3485
  function EmptyState({ message, icon = 'FileText', actionText, onAction, }) {
3195
- return (jsxs("div", { className: "d-flex flex-column align-items-center justify-content-center p-5 text-center", children: [jsx(DIcon, { icon: icon, size: "3rem", className: "text-secondary mb-3" }), jsx("p", { className: "text-secondary mb-3", children: message !== null && message !== void 0 ? message : 'No data available.' }), actionText && onAction && (jsx(DButton, { onClick: onAction, text: actionText, variant: "outline" }))] }));
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" }))] }));
3196
3487
  }
3197
3488
 
3198
3489
  function LoadingState({ ariaLabel = 'Loading...', className }) {
3199
- return (jsx("div", { className: `d-flex align-items-center justify-content-center p-4 ${className || ''}`.trim(), "aria-busy": "true", "aria-live": "polite", children: jsx("span", { className: "spinner-border", role: "status", "aria-label": ariaLabel }) }));
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 }) }));
3200
3491
  }
3201
3492
 
3202
3493
  function render(renderable) {
@@ -3255,7 +3546,7 @@ function DConfirmModalUI({ entry }) {
3255
3546
  }
3256
3547
  });
3257
3548
  }, [onConfirmAction]);
3258
- 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: "py-3 px-5", children: [jsx("p", { className: "mb-4", children: message }), critical && (jsx(ForwardedDInput, { type: "text", label: critical.codeLabel || 'Confirmation code', placeholder: critical.inputPlaceholder, value: confirmationCode, onChange: (value) => setConfirmationCode(value), className: "mb-4", 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 })] })] }));
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 })] })] }));
3259
3550
  }
3260
3551
 
3261
3552
  /**
@@ -3308,5 +3599,5 @@ function DConfirmModalContainer({ nodeId }) {
3308
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);
3309
3600
  }
3310
3601
 
3311
- 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 };
3312
3603
  //# sourceMappingURL=index.esm.js.map