@transferwise/components 0.0.0-experimental-08d336e → 0.0.0-experimental-b069e0c

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 (36) hide show
  1. package/build/index.esm.js +1308 -1237
  2. package/build/index.esm.js.map +1 -1
  3. package/build/index.js +1306 -1236
  4. package/build/index.js.map +1 -1
  5. package/build/main.css +1 -1
  6. package/build/styles/main.css +1 -1
  7. package/build/styles/moneyInput/MoneyInput.css +1 -1
  8. package/build/types/common/hooks/useLayout/useLayout.d.ts +6 -9
  9. package/build/types/common/hooks/useLayout/useLayout.d.ts.map +1 -1
  10. package/build/types/index.d.ts +0 -1
  11. package/build/types/index.d.ts.map +1 -1
  12. package/build/types/inputs/SelectInput.d.ts +2 -1
  13. package/build/types/inputs/SelectInput.d.ts.map +1 -1
  14. package/build/types/moneyInput/MoneyInput.d.ts.map +1 -1
  15. package/package.json +1 -1
  16. package/src/common/hooks/useLayout/useLayout.ts +49 -21
  17. package/src/common/responsivePanel/ResponsivePanel.spec.js +1 -3
  18. package/src/dateLookup/DateLookup.testingLibrary.spec.js +19 -1
  19. package/src/dateLookup/dateHeader/DateHeader.spec.js +0 -2
  20. package/src/drawer/Drawer.rtl.spec.js +1 -3
  21. package/src/drawer/Drawer.spec.js +0 -2
  22. package/src/index.ts +0 -1
  23. package/src/info/Info.spec.js +11 -7
  24. package/src/inputs/SelectInput.tsx +8 -0
  25. package/src/main.css +1 -1
  26. package/src/modal/Modal.rtl.spec.js +1 -3
  27. package/src/modal/Modal.spec.js +0 -2
  28. package/src/modal/Modal.tsx +2 -2
  29. package/src/moneyInput/MoneyInput.css +1 -1
  30. package/src/moneyInput/MoneyInput.js +85 -44
  31. package/src/moneyInput/MoneyInput.less +35 -161
  32. package/src/moneyInput/MoneyInput.rtl.spec.js +71 -0
  33. package/src/moneyInput/MoneyInput.spec.js +245 -117
  34. package/src/moneyInput/MoneyInput.story.tsx +78 -1
  35. package/src/ssr.spec.js +0 -1
  36. package/src/uploadInput/UploadInput.spec.tsx +1 -3
@@ -20,7 +20,7 @@ import { usePopper } from 'react-popper';
20
20
  import throttle from 'lodash.throttle';
21
21
  import { createPortal } from 'react-dom';
22
22
  import { isUndefined, isKey, isNumber, isEmpty, isNull, isArray } from '@transferwise/neptune-validation';
23
- import { Illustration } from '@wise/art';
23
+ import { Flag, Illustration } from '@wise/art';
24
24
  import clamp$2 from 'lodash.clamp';
25
25
  import debounce from 'lodash.debounce';
26
26
  import requiredIf from 'react-required-if';
@@ -1847,36 +1847,43 @@ const useHasIntersected = ({
1847
1847
  return [hasIntersected];
1848
1848
  };
1849
1849
 
1850
- function useMedia(query) {
1851
- return useSyncExternalStore(onStoreChange => {
1852
- const mediaQueryList = window.matchMedia(query);
1853
- mediaQueryList.addEventListener('change', onStoreChange);
1854
- return () => {
1855
- mediaQueryList.removeEventListener('change', onStoreChange);
1856
- };
1857
- }, () => typeof window !== 'undefined' ? window.matchMedia(query).matches : undefined, () => undefined);
1858
- }
1859
-
1860
- function useScreenSize(size) {
1861
- return useMedia(`(min-width: ${size}px)`);
1862
- }
1863
-
1864
- /**
1865
- * @deprecated Prefer `useScreenSize` instead.
1866
- */
1867
1850
  const useLayout = () => {
1868
- const screenXs = useScreenSize(Breakpoint.EXTRA_SMALL);
1869
- const screenSm = useScreenSize(Breakpoint.SMALL);
1870
- const screenMd = useScreenSize(Breakpoint.MEDIUM);
1871
- const screenLg = useScreenSize(Breakpoint.LARGE);
1872
- const screenXl = useScreenSize(Breakpoint.EXTRA_LARGE);
1851
+ const windowReference = typeof window === 'undefined' ? undefined : window;
1852
+ const [breakpoint, setBreakpoint] = useState();
1853
+ const [clientWidth] = useClientWidth({
1854
+ ref: windowReference
1855
+ });
1856
+ useEffect(() => {
1857
+ if (!clientWidth) {
1858
+ return;
1859
+ }
1860
+ if (clientWidth <= Breakpoint.EXTRA_SMALL) {
1861
+ setBreakpoint(Breakpoint.EXTRA_SMALL);
1862
+ return;
1863
+ }
1864
+ if (Breakpoint.EXTRA_SMALL < clientWidth && clientWidth <= Breakpoint.SMALL) {
1865
+ setBreakpoint(Breakpoint.SMALL);
1866
+ return;
1867
+ }
1868
+ if (Breakpoint.SMALL < clientWidth && clientWidth <= Breakpoint.MEDIUM) {
1869
+ setBreakpoint(Breakpoint.MEDIUM);
1870
+ return;
1871
+ }
1872
+ if (Breakpoint.MEDIUM < clientWidth && clientWidth <= Breakpoint.LARGE) {
1873
+ setBreakpoint(Breakpoint.LARGE);
1874
+ return;
1875
+ }
1876
+ if (Breakpoint.LARGE < clientWidth) {
1877
+ setBreakpoint(Breakpoint.EXTRA_LARGE);
1878
+ }
1879
+ }, [clientWidth]);
1873
1880
  return {
1874
- isMobile: screenSm != null ? !screenSm : undefined,
1875
- isExtraSmall: screenXs,
1876
- isSmall: screenSm,
1877
- isMedium: screenMd,
1878
- isLarge: screenLg,
1879
- isExtraLarge: screenXl
1881
+ isMobile: !!breakpoint && [Breakpoint.EXTRA_SMALL, Breakpoint.SMALL].includes(breakpoint),
1882
+ isExtraSmall: breakpoint === Breakpoint.EXTRA_SMALL,
1883
+ isSmall: breakpoint === Breakpoint.SMALL,
1884
+ isMedium: breakpoint === Breakpoint.MEDIUM,
1885
+ isLarge: breakpoint === Breakpoint.LARGE,
1886
+ isExtraLarge: breakpoint === Breakpoint.EXTRA_LARGE
1880
1887
  };
1881
1888
  };
1882
1889
 
@@ -2001,6 +2008,16 @@ Drawer.defaultProps = {
2001
2008
  };
2002
2009
  var Drawer$1 = Drawer;
2003
2010
 
2011
+ function useMedia(query) {
2012
+ return useSyncExternalStore(onStoreChange => {
2013
+ const mediaQueryList = window.matchMedia(query);
2014
+ mediaQueryList.addEventListener('change', onStoreChange);
2015
+ return () => {
2016
+ mediaQueryList.removeEventListener('change', onStoreChange);
2017
+ };
2018
+ }, () => typeof window !== 'undefined' ? window.matchMedia(query).matches : undefined, () => undefined);
2019
+ }
2020
+
2004
2021
  const INITIAL_Y_POSITION = 0;
2005
2022
  const CONTENT_SCROLL_THRESHOLD = 1;
2006
2023
  const MOVE_OFFSET_THRESHOLD = 50;
@@ -6009,13 +6026,14 @@ const Modal = ({
6009
6026
  }) => {
6010
6027
  const checkSpecialClasses = classToCheck => className?.split(' ').includes(classToCheck);
6011
6028
  const {
6012
- isMedium
6029
+ isMobile,
6030
+ isMedium: isTablet
6013
6031
  } = useLayout();
6014
6032
  // These should be replaced with props in breaking change.
6015
6033
  const isCompact = checkSpecialClasses('compact');
6016
6034
  const noDivider = checkSpecialClasses('no-divider');
6017
6035
  const contentReference = useRef(null);
6018
- return !isMedium ? /*#__PURE__*/jsx(Drawer$1, {
6036
+ return isMobile || isTablet ? /*#__PURE__*/jsx(Drawer$1, {
6019
6037
  open: open,
6020
6038
  headerTitle: title,
6021
6039
  footerContent: footer,
@@ -6449,6 +6467,10 @@ const SearchInput = /*#__PURE__*/forwardRef(function SearchInput({
6449
6467
  });
6450
6468
  });
6451
6469
 
6470
+ function useScreenSize(size) {
6471
+ return useMedia(`(min-width: ${size}px)`);
6472
+ }
6473
+
6452
6474
  const PolymorphicWithOverrides = /*#__PURE__*/forwardRef(function PolymorphicWithOverrides({
6453
6475
  __overrides: {
6454
6476
  as: Element,
@@ -6838,6 +6860,7 @@ function SelectInput({
6838
6860
  size = 'md',
6839
6861
  className,
6840
6862
  onChange,
6863
+ onSearchChange,
6841
6864
  onClear
6842
6865
  }) {
6843
6866
  const [open, setOpen] = useState(false);
@@ -6908,7 +6931,8 @@ function SelectInput({
6908
6931
  filterable: filterable,
6909
6932
  filterPlaceholder: filterPlaceholder,
6910
6933
  searchInputRef: searchInputRef,
6911
- listboxRef: listboxRef
6934
+ listboxRef: listboxRef,
6935
+ onSearchChange: onSearchChange
6912
6936
  })
6913
6937
  })
6914
6938
  });
@@ -6976,7 +7000,8 @@ function SelectInputOptions({
6976
7000
  filterable = false,
6977
7001
  filterPlaceholder,
6978
7002
  searchInputRef,
6979
- listboxRef
7003
+ listboxRef,
7004
+ onSearchChange
6980
7005
  }) {
6981
7006
  const intl = useIntl();
6982
7007
  const controllerRef = filterable ? searchInputRef : listboxRef;
@@ -7028,6 +7053,9 @@ function SelectInputOptions({
7028
7053
  },
7029
7054
  onChange: event => {
7030
7055
  setQuery(event.currentTarget.value);
7056
+ if (onSearchChange) {
7057
+ onSearchChange(event.currentTarget.value);
7058
+ }
7031
7059
  }
7032
7060
  })
7033
7061
  }) : null, /*#__PURE__*/jsxs("section", {
@@ -7757,1105 +7785,491 @@ Money.propTypes = {
7757
7785
  var Money$1 = Money;
7758
7786
 
7759
7787
  var messages$3 = defineMessages({
7760
- searchPlaceholder: {
7761
- id: "neptune.Select.searchPlaceholder"
7788
+ selectPlaceholder: {
7789
+ id: "neptune.MoneyInput.Select.placeholder"
7762
7790
  }
7763
7791
  });
7764
7792
 
7765
- // Option.tsx NEW
7766
- function Option$1({
7767
- label,
7768
- currency = '',
7769
- note = '',
7770
- secondary = '',
7771
- icon,
7772
- classNames = {},
7773
- selected = false
7774
- }) {
7775
- const {
7776
- isModern
7777
- } = useTheme();
7778
- const style = classes => classes.map(className => classNames[className] || className).join(' ');
7779
- const currencyClassNames = currency ? style(['currency-flag', `currency-flag-${currency.toLowerCase()}`]) : undefined;
7780
- const iconElement = icon ? /*#__PURE__*/cloneElement(icon, {
7781
- size: 24,
7782
- className: 'tw-icon'
7783
- }) : currency && /*#__PURE__*/jsx("i", {
7784
- className: currencyClassNames
7785
- });
7786
- const titleAndNoteElement = /*#__PURE__*/jsxs(Body, {
7787
- as: "span",
7788
- type: Typography.BODY_LARGE,
7789
- className: selected ? 'text-ellipsis' : undefined,
7790
- children: [label, note && /*#__PURE__*/jsx(Body, {
7791
- as: "span",
7792
- className: isModern ? 'm-l-1' : 'm-l-1 body-2',
7793
- children: note
7794
- })]
7795
- });
7796
- const secondaryElementClassNames = () => {
7797
- let classes = undefined;
7798
- if (selected) {
7799
- classes = 'text-ellipsis';
7800
- }
7801
- if (isModern) {
7802
- return classes;
7803
- }
7804
- return `${classes ? classes + ' ' : ''}body-2`;
7805
- };
7806
- const secondaryElement = secondary && /*#__PURE__*/jsx(Body, {
7807
- className: secondaryElementClassNames(),
7808
- children: secondary
7809
- });
7810
- return iconElement ? /*#__PURE__*/jsxs("div", {
7811
- className: "d-flex np-option-content",
7812
- children: [/*#__PURE__*/jsx("div", {
7813
- className: `d-flex flex-column${selected ? ' justify-content-center' : ''}`,
7814
- children: iconElement
7815
- }), /*#__PURE__*/jsxs("div", {
7816
- className: "d-flex flex-column justify-content-center",
7817
- children: [titleAndNoteElement, secondaryElement]
7818
- })]
7819
- }) : /*#__PURE__*/jsxs(Fragment, {
7820
- children: [iconElement, titleAndNoteElement, secondaryElement]
7821
- });
7793
+ // TODO: do not duplicate this between formatting and components
7794
+ const currencyDecimals = {
7795
+ BIF: 0,
7796
+ BYR: 0,
7797
+ CLP: 0,
7798
+ DJF: 0,
7799
+ GNF: 0,
7800
+ JPY: 0,
7801
+ KMF: 0,
7802
+ KRW: 0,
7803
+ MGA: 0,
7804
+ PYG: 0,
7805
+ RWF: 0,
7806
+ VND: 0,
7807
+ VUV: 0,
7808
+ XAF: 0,
7809
+ XOF: 0,
7810
+ XPF: 0,
7811
+ // technically HUF does have decimals, but due to the exchange rate banks
7812
+ // do not accept decimal amounts
7813
+ HUF: 0,
7814
+ BHD: 3,
7815
+ JOD: 3,
7816
+ KWD: 3,
7817
+ OMR: 3,
7818
+ TND: 3
7819
+ };
7820
+ const DEFAULT_CURRENCY_DECIMALS = 2;
7821
+ function isNumberLocaleSupported() {
7822
+ const number = 1234;
7823
+ const numberString = number.toLocaleString && number.toLocaleString(DEFAULT_LOCALE);
7824
+ return numberString === '1,234';
7822
7825
  }
7823
-
7824
- const SearchBox = /*#__PURE__*/forwardRef(({
7825
- id,
7826
- classNames: classNames$1 = {},
7827
- focusedOptionId,
7828
- onChange,
7829
- onClick,
7830
- placeholder = undefined,
7831
- value = ''
7832
- }, reference) => {
7833
- const style = className => classNames$1[className] || className;
7834
- return /*#__PURE__*/jsx("li", {
7835
- className: style('border-bottom'),
7836
- children: /*#__PURE__*/jsx("a", {
7837
- className: `${style('np-select-filter-link')} ${style('p-a-0')}`,
7838
- children: /*#__PURE__*/jsxs("div", {
7839
- className: style('input-group'),
7840
- children: [/*#__PURE__*/jsx("span", {
7841
- className: classNames('input-group-addon', 'input-group-addon--search'),
7842
- children: /*#__PURE__*/jsx(Search, {
7843
- className: classNames(style('tw-icon'), style('tw-icon-search')),
7844
- size: 24
7845
- })
7846
- }), /*#__PURE__*/jsx(Input, {
7847
- ref: reference,
7848
- id: id,
7849
- role: "searchbox",
7850
- inputMode: "search",
7851
- className: classNames(style('np-select-filter')),
7852
- placeholder: placeholder,
7853
- value: value,
7854
- spellCheck: "false",
7855
- "aria-activedescendant": focusedOptionId,
7856
- onChange: onChange,
7857
- onClick: onClick
7858
- })]
7859
- })
7860
- })
7861
- });
7862
- });
7863
- var SearchBox$1 = SearchBox;
7864
-
7865
- const DEFAULT_SEARCH_VALUE = '';
7866
- const DEFAULT_OPTIONS_PAGE_SIZE = 1000;
7867
- const includesString = (string1, string2) => string1.toLowerCase().includes(string2.toLowerCase());
7868
- function defaultFilterFunction(option, searchValue) {
7869
- if (isPlaceholderOption(option)) {
7870
- return true;
7826
+ function getValidLocale(locale) {
7827
+ try {
7828
+ const noUnderscoreLocale = locale.replace(/_/, '-');
7829
+ Intl.NumberFormat(noUnderscoreLocale);
7830
+ return noUnderscoreLocale;
7831
+ } catch {
7832
+ return 'en-GB';
7871
7833
  }
7872
- const {
7873
- label,
7874
- note,
7875
- secondary,
7876
- currency,
7877
- searchStrings
7878
- } = option;
7879
- return !!label && includesString(label, searchValue) || !!note && includesString(note, searchValue) || !!secondary && includesString(secondary, searchValue) || !!currency && includesString(currency, searchValue) || !!searchStrings && searchStrings.some(string => includesString(string, searchValue));
7880
- }
7881
- function isActionableOption(option) {
7882
- return !option.header && !option.separator && !option.disabled;
7883
7834
  }
7884
- function isHeaderOption(option) {
7885
- return option != null && 'header' in option;
7835
+ function getCurrencyDecimals(currency = '') {
7836
+ const upperCaseCurrency = currency.toUpperCase();
7837
+ if (Object.prototype.hasOwnProperty.call(currencyDecimals, upperCaseCurrency)) {
7838
+ return currencyDecimals[upperCaseCurrency];
7839
+ }
7840
+ return DEFAULT_CURRENCY_DECIMALS;
7886
7841
  }
7887
- function isSeparatorOption(option) {
7888
- return option != null && 'separator' in option;
7842
+ function getDecimalSeparator(locale) {
7843
+ return isNumberLocaleSupported() ? 1.1.toLocaleString(locale)[1] : '.';
7889
7844
  }
7890
- function clamp(from, to, value) {
7891
- return Math.max(Math.min(to, value), from);
7845
+ function parseAmount(number, currency, locale) {
7846
+ const validLocale = getValidLocale(locale);
7847
+ const precision = getCurrencyDecimals(currency);
7848
+ const groupSeparator = isNumberLocaleSupported() ? 10000 .toLocaleString(validLocale)[2] : ',';
7849
+ const decimalSeparator = getDecimalSeparator(validLocale);
7850
+ const numberWithStandardDecimalSeparator = (number ? `${number}` : '').replace(new RegExp(`\\${groupSeparator}`, 'g'), '').replace(new RegExp(`\\${decimalSeparator}`, 'g'), '.').replace(/[^0-9.]/g, '');
7851
+ const parsedAmount = parseFloat(parseFloat(numberWithStandardDecimalSeparator).toFixed(precision));
7852
+ return Math.abs(parsedAmount);
7892
7853
  }
7893
7854
 
7894
- /**
7895
- * No option or placeholder option is selected
7896
- */
7897
- const DEFAULT_SELECTED_OPTION = null;
7898
- function isPlaceholderOption(option) {
7899
- return option === DEFAULT_SELECTED_OPTION || 'placeholder' in option;
7900
- }
7901
- function isSearchableOption(option) {
7902
- return !isHeaderOption(option) && !isSeparatorOption(option) && !isPlaceholderOption(option);
7903
- }
7904
- const getUniqueIdForOption = (parentId = '', option) => {
7905
- if (option == null) {
7906
- return undefined;
7855
+ const Currency = PropTypes.shape({
7856
+ header: PropTypes.string,
7857
+ value: PropTypes.string,
7858
+ label: PropTypes.string,
7859
+ currency: PropTypes.string,
7860
+ note: PropTypes.string,
7861
+ searchable: PropTypes.string
7862
+ });
7863
+ const isNumberOrNull = v => isNumber(v) || isNull(v);
7864
+ const formatAmountIfSet = (amount, currency, locale, maxLengthOverride) => {
7865
+ if (maxLengthOverride) {
7866
+ return amount || '';
7867
+ } else {
7868
+ return typeof amount === 'number' ? formatAmount(amount, currency, locale) : '';
7907
7869
  }
7908
- const uniqueOptionId = option.value || (option.label?.replace(/\s/g, '') ?? '');
7909
- return `option-${parentId}-${uniqueOptionId}`;
7910
7870
  };
7911
-
7912
- /**
7913
- * @deprecated Use `SelectInput` instead (https://neptune.wise.design/blog/2023-11-28-adopting-our-new-selectinput)
7914
- */
7915
- function Select({
7916
- placeholder,
7917
- id,
7918
- required,
7919
- disabled,
7920
- inverse,
7921
- dropdownWidth,
7922
- size,
7923
- block,
7924
- selected,
7925
- search,
7926
- onChange,
7927
- onFocus,
7928
- onBlur,
7929
- options: defaultOptions,
7930
- onSearchChange,
7931
- searchValue: initSearchValue,
7932
- searchPlaceholder,
7933
- // eslint-disable-next-line unicorn/prevent-abbreviations
7934
- classNames: classNamesProp,
7935
- dropdownUp,
7936
- dropdownProps,
7937
- buttonProps
7938
- }) {
7939
- const {
7940
- formatMessage
7941
- } = useIntl();
7942
- const {
7943
- isModern
7944
- } = useTheme();
7945
- const s = className => classNamesProp[className] || className;
7946
- const [open, setOpen] = useState(false);
7947
- const [searchValue, setSearchValue] = useState(DEFAULT_SEARCH_VALUE);
7948
- const [keyboardFocusedOptionIndex, setKeyboardFocusedOptionIndex] = useState(null);
7949
- const keyboardFocusedReference = useRef();
7950
- const previousKeyboardFocusedOptionIndex = useRef();
7951
- const [numberOfOptionsShown, setNumberOfOptionsShown] = useState(DEFAULT_OPTIONS_PAGE_SIZE);
7952
- const searchBoxReference = useRef(null);
7953
- const selectReference = useRef(null);
7954
- const dropdownButtonReference = useRef(null);
7955
- const optionsListReference = useRef(null);
7956
- const isSearchEnabled = !!onSearchChange || !!search;
7957
- const isDropdownAutoWidth = dropdownWidth == null;
7958
- const fallbackButtonId = useMemo(() => getSimpleRandomId('np-select-'), []);
7959
- const options = useMemo(() => {
7960
- if (!search || !searchValue) {
7961
- return defaultOptions;
7962
- }
7963
- return defaultOptions.filter(isSearchableOption).filter(option => {
7964
- if (typeof search === 'function') {
7965
- return search(option, searchValue);
7966
- } else {
7967
- return defaultFilterFunction(option, searchValue);
7968
- }
7871
+ const parseNumber = (amount, currency, locale, maxLengthOverride) => {
7872
+ if (!maxLengthOverride) {
7873
+ return parseAmount(amount, currency, locale);
7874
+ }
7875
+ if (maxLengthOverride && amount.length > maxLengthOverride) {
7876
+ return 0;
7877
+ }
7878
+ return +amount;
7879
+ };
7880
+ const inputKeyCodeAllowlist = new Set([KeyCodes.BACKSPACE, KeyCodes.DELETE, KeyCodes.COMMA, KeyCodes.PERIOD, KeyCodes.DOWN, KeyCodes.UP, KeyCodes.LEFT, KeyCodes.RIGHT, KeyCodes.ENTER, KeyCodes.ESCAPE, KeyCodes.TAB]);
7881
+ const inputKeyAllowlist = new Set([Key.PERIOD, Key.COMMA]);
7882
+ class MoneyInput extends Component {
7883
+ constructor(props) {
7884
+ super(props);
7885
+ const {
7886
+ locale
7887
+ } = this.props.intl;
7888
+ this.formatMessage = this.props.intl.formatMessage;
7889
+ this.state = {
7890
+ searchQuery: '',
7891
+ selectedOption: this.props.selectedCurrency,
7892
+ formattedAmount: formatAmountIfSet(props.amount, props.selectedCurrency.currency, locale, props.maxLengthOverride),
7893
+ locale
7894
+ };
7895
+ }
7896
+ UNSAFE_componentWillReceiveProps(nextProps) {
7897
+ this.setState({
7898
+ locale: nextProps?.intl?.locale
7969
7899
  });
7970
- }, [defaultOptions, search, searchValue]);
7971
- const selectableOptions = useMemo(() => options.filter(isActionableOption), [options]);
7972
- const focusedOption = selectableOptions[keyboardFocusedOptionIndex];
7973
- const computedId = id || fallbackButtonId;
7974
- const listboxId = `${computedId}-listbox`;
7975
- const searchBoxId = `${computedId}-searchbox`;
7976
- const {
7977
- isMobile
7978
- } = useLayout();
7979
- useEffect(() => {
7980
- let cancelled;
7981
- if (keyboardFocusedOptionIndex >= 0) {
7982
- requestAnimationFrame(() => {
7983
- if (!cancelled) {
7984
- if (isSearchEnabled) {
7985
- keyboardFocusedReference.current?.scrollIntoView?.({
7986
- block: 'center'
7987
- });
7988
- } else {
7989
- keyboardFocusedReference.current?.focus();
7990
- }
7991
- }
7900
+ if (!this.amountFocused) {
7901
+ this.setState({
7902
+ formattedAmount: formatAmountIfSet(nextProps.amount, nextProps.selectedCurrency.currency, nextProps?.intl?.locale, nextProps.maxLengthOverride)
7992
7903
  });
7993
- return () => {
7994
- cancelled = true;
7995
- };
7996
7904
  }
7997
- }, [keyboardFocusedOptionIndex, isSearchEnabled]);
7998
- const handleOnClick = () => {
7999
- setOpen(true);
7905
+ }
7906
+ isInputAllowedForKeyEvent = event => {
7907
+ const {
7908
+ keyCode,
7909
+ metaKey,
7910
+ key,
7911
+ ctrlKey
7912
+ } = event;
7913
+ const isNumberKey = isNumber(parseInt(key, 10));
7914
+ return isNumberKey || metaKey || ctrlKey || inputKeyCodeAllowlist.has(keyCode) || inputKeyAllowlist.has(key);
8000
7915
  };
8001
- const handleTouchStart = event => {
8002
- if (event.currentTarget === event.target && open) {
8003
- handleCloseOptions();
7916
+ handleKeyDown = event => {
7917
+ if (!this.isInputAllowedForKeyEvent(event)) {
7918
+ event.preventDefault();
8004
7919
  }
8005
7920
  };
8006
- const handleOnFocus = event => {
8007
- if (onFocus) {
8008
- onFocus(event);
7921
+ handlePaste = event => {
7922
+ const paste = (event.clipboardData || window.clipboardData).getData('text');
7923
+ const {
7924
+ locale
7925
+ } = this.state;
7926
+ const parsed = isEmpty(paste) ? null : parseNumber(paste, this.props.selectedCurrency.currency, locale, this.props.maxLengthOverride);
7927
+ if (isNumberOrNull(parsed)) {
7928
+ this.setState({
7929
+ formattedAmount: formatAmountIfSet(parsed, this.props.selectedCurrency.currency, locale, this.props.maxLengthOverride)
7930
+ });
7931
+ this.props.onAmountChange(parsed);
8009
7932
  }
7933
+ event.preventDefault();
8010
7934
  };
8011
- const handleOnBlur = event => {
7935
+ onAmountChange = event => {
8012
7936
  const {
8013
- nativeEvent
8014
- } = event;
8015
- if (nativeEvent) {
8016
- const elementReceivingFocus = nativeEvent.relatedTarget;
8017
- const select = event.currentTarget;
8018
- if (select && elementReceivingFocus && select.contains(elementReceivingFocus)) {
8019
- return;
8020
- }
8021
- }
8022
- if (onBlur) {
8023
- onBlur(event);
7937
+ value
7938
+ } = event.target;
7939
+ this.setState({
7940
+ formattedAmount: value
7941
+ });
7942
+ const parsed = isEmpty(value) ? null : parseNumber(value, this.props.selectedCurrency.currency, this.state.locale, this.props.maxLengthOverride);
7943
+ if (isNumberOrNull(parsed)) {
7944
+ this.props.onAmountChange(parsed);
8024
7945
  }
8025
7946
  };
8026
- const handleSearchChange = event => {
8027
- setNumberOfOptionsShown(DEFAULT_OPTIONS_PAGE_SIZE);
8028
- setSearchValue(event.target.value);
8029
- if (onSearchChange) {
8030
- onSearchChange(event.target.value);
8031
- }
7947
+ onAmountBlur = () => {
7948
+ this.amountFocused = false;
7949
+ this.setAmount();
8032
7950
  };
8033
- const handleKeyDown = event => {
8034
- switch (event.keyCode) {
8035
- case KeyCodes.UP:
8036
- case KeyCodes.DOWN:
8037
- if (open) {
8038
- moveFocusWithDifference(event.keyCode === KeyCodes.UP ? -1 : 1);
8039
- } else {
8040
- setOpen(true);
8041
- }
8042
- stopPropagation$1(event);
8043
- break;
8044
- case KeyCodes.SPACE:
8045
- if (event.target !== searchBoxReference.current) {
8046
- if (open) {
8047
- selectKeyboardFocusedOption();
8048
- } else {
8049
- setOpen(true);
8050
- }
8051
- stopPropagation$1(event);
8052
- }
8053
- break;
8054
- case KeyCodes.ENTER:
8055
- if (open) {
8056
- selectKeyboardFocusedOption();
8057
- } else {
8058
- setOpen(true);
8059
- }
8060
- stopPropagation$1(event);
8061
- break;
8062
- case KeyCodes.ESCAPE:
8063
- handleCloseOptions();
8064
- stopPropagation$1(event);
8065
- break;
8066
- case KeyCodes.TAB:
8067
- if (open) {
8068
- selectKeyboardFocusedOption();
8069
- }
8070
- break;
8071
- }
7951
+ onAmountFocus = () => {
7952
+ this.amountFocused = true;
8072
7953
  };
8073
- function selectKeyboardFocusedOption() {
8074
- if (keyboardFocusedOptionIndex != null) {
8075
- selectableOptions.length > 0 && selectOption(selectableOptions[keyboardFocusedOptionIndex]);
8076
- }
8077
- }
8078
- function moveFocusWithDifference(difference) {
8079
- const selectedOptionIndex = selectableOptions.reduce((optionIndex, current, index) => {
8080
- if (optionIndex != null) {
8081
- return optionIndex;
8082
- }
8083
- if (isOptionSelected(selected, current)) {
8084
- return index;
8085
- }
8086
- return null;
8087
- }, null);
8088
- const previousFocusedIndex = previousKeyboardFocusedOptionIndex.current ?? -1;
8089
- let indexToStartMovingFrom = previousFocusedIndex;
8090
- if (previousFocusedIndex === -1) {
8091
- if (selectedOptionIndex == null) {
8092
- setKeyboardFocusedOptionIndex(0);
7954
+ mapOption = item => {
7955
+ return {
7956
+ type: 'option',
7957
+ value: item,
7958
+ filterMatchers: [item.value, item.label, item.note, item.searchable]
7959
+ };
7960
+ };
7961
+ getSelectOptions() {
7962
+ const selectOptions = [...filterOptionsForQuery(this.props.currencies, this.state.searchQuery)];
7963
+ let formattedOptions = [];
7964
+ let groupIndex = null;
7965
+ selectOptions.forEach(item => {
7966
+ if (item.header) {
7967
+ formattedOptions.push({
7968
+ type: 'group',
7969
+ label: item.header,
7970
+ options: []
7971
+ });
7972
+ groupIndex = formattedOptions.length - 1;
8093
7973
  } else {
8094
- indexToStartMovingFrom = selectedOptionIndex;
7974
+ if (groupIndex === null) {
7975
+ formattedOptions.push(this.mapOption(item));
7976
+ } else {
7977
+ formattedOptions[groupIndex]?.options.push(this.mapOption(item));
7978
+ }
8095
7979
  }
8096
- }
8097
- const unClampedNewIndex = indexToStartMovingFrom + difference;
8098
- const newIndex = clamp(0, selectableOptions.length - 1, unClampedNewIndex);
8099
- setKeyboardFocusedOptionIndex(newIndex);
7980
+ });
7981
+ return formattedOptions;
8100
7982
  }
8101
- useEffect(() => {
8102
- if (open) {
8103
- if (!isMobile || searchValue) {
8104
- if (isSearchEnabled && !!searchBoxReference.current) {
8105
- searchBoxReference.current.focus();
8106
- }
8107
- if (!isSearchEnabled && optionsListReference.current && (previousKeyboardFocusedOptionIndex.current == null || Number.isNaN(previousKeyboardFocusedOptionIndex.current))) {
8108
- optionsListReference.current.focus();
8109
- }
7983
+ setAmount() {
7984
+ this.setState(previousState => {
7985
+ const parsed = parseNumber(previousState.formattedAmount, this.props.selectedCurrency.currency, previousState.locale, this.props.maxLengthOverride);
7986
+ if (!isNumberOrNull(parsed)) {
7987
+ return {
7988
+ formattedAmount: previousState.formattedAmount
7989
+ };
8110
7990
  }
8111
- previousKeyboardFocusedOptionIndex.current = keyboardFocusedOptionIndex;
8112
- } else {
8113
- previousKeyboardFocusedOptionIndex.current = null;
8114
- }
8115
- }, [open, searchValue, isSearchEnabled, isMobile, keyboardFocusedOptionIndex]);
8116
- const handleCloseOptions = () => {
8117
- setOpen(false);
8118
- setKeyboardFocusedOptionIndex(null);
8119
- if (dropdownButtonReference.current) {
8120
- dropdownButtonReference.current.focus();
7991
+ return {
7992
+ formattedAmount: formatAmountIfSet(parsed, this.props.selectedCurrency.currency, previousState.locale, this.props.maxLengthOverride)
7993
+ };
7994
+ });
7995
+ }
7996
+ handleSelectChange = value => {
7997
+ this.handleSearchChange('');
7998
+ this.setState({
7999
+ selectedOption: value
8000
+ });
8001
+ this.props.onCurrencyChange(value);
8002
+ };
8003
+ handleCustomAction = () => {
8004
+ this.handleSearchChange('');
8005
+ if (this.props.onCustomAction) {
8006
+ this.props.onCustomAction();
8121
8007
  }
8122
8008
  };
8123
- function createSelectHandlerForOption(option) {
8124
- return event => {
8125
- stopPropagation$1(event);
8126
- selectOption(option);
8127
- };
8128
- }
8129
- function selectOption(option) {
8130
- onChange(isPlaceholderOption(option) ? DEFAULT_SELECTED_OPTION : option);
8131
- handleCloseOptions();
8132
- }
8133
- function renderOptionsList({
8134
- className
8135
- } = {}) {
8136
- const dropdownClass = classNames(s('np-dropdown-menu'), {
8137
- [s('np-dropdown-menu-desktop')]: !isMobile,
8138
- [s(`np-dropdown-menu-${dropdownWidth}`)]: !isMobile && !isDropdownAutoWidth
8139
- }, s(className));
8140
- const showPlaceholder = !required && !isSearchEnabled && Boolean(placeholder);
8141
- return /*#__PURE__*/jsxs("ul", {
8142
- ref: optionsListReference,
8143
- id: listboxId,
8144
- role: "listbox",
8145
- "aria-orientation": "vertical",
8146
- "aria-activedescendant": getUniqueIdForOption(id, selected),
8147
- tabIndex: "-1",
8148
- className: dropdownClass,
8149
- ...dropdownProps,
8150
- children: [showPlaceholder && /*#__PURE__*/jsx(PlaceHolderOption, {}), isSearchEnabled && /*#__PURE__*/jsx(SearchBox$1, {
8151
- ref: searchBoxReference,
8152
- id: searchBoxId,
8153
- classNames: classNamesProp,
8154
- value: initSearchValue || searchValue,
8155
- placeholder: searchPlaceholder || formatMessage(messages$3.searchPlaceholder),
8156
- focusedOptionId: getUniqueIdForOption(id, focusedOption),
8157
- onChange: handleSearchChange,
8158
- onClick: stopPropagation$1
8159
- }), options.slice(0, numberOfOptionsShown).map(renderOption), numberOfOptionsShown < options.length && /*#__PURE__*/jsx(ShowMoreOption, {})]
8009
+ handleSearchChange = searchQuery => {
8010
+ this.setState({
8011
+ searchQuery
8160
8012
  });
8161
- }
8162
- function ShowMoreOption() {
8163
- function handleOnClick(event) {
8164
- stopPropagation$1(event);
8165
- setNumberOfOptionsShown(numberOfOptionsShown + DEFAULT_OPTIONS_PAGE_SIZE);
8013
+ if (this.props.onSearchChange) {
8014
+ this.props.onSearchChange({
8015
+ searchQuery,
8016
+ filteredOptions: filterOptionsForQuery(this.props.currencies, searchQuery)
8017
+ });
8166
8018
  }
8167
- return (
8168
- /*#__PURE__*/
8169
- /* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */
8170
- jsx("li", {
8171
- className: classNames(s('clickable'), s('border-bottom'), s('show-more')),
8172
- onClick: handleOnClick,
8173
- onKeyPress: handleOnClick,
8174
- children: /*#__PURE__*/jsx("a", {
8175
- children: "..."
8176
- })
8177
- })
8178
- );
8179
- }
8180
- function PlaceHolderOption() {
8181
- const placeholderOption = {
8182
- placeholder
8019
+ };
8020
+ style = className => this.props.classNames[className] || className;
8021
+ render() {
8022
+ const {
8023
+ selectedCurrency,
8024
+ onCurrencyChange,
8025
+ size,
8026
+ addon,
8027
+ id,
8028
+ selectProps,
8029
+ maxLengthOverride
8030
+ } = this.props;
8031
+ const selectOptions = this.getSelectOptions();
8032
+ const firstOptionValue = () => {
8033
+ if (selectOptions.length !== 0) {
8034
+ const firstOption = selectOptions[0];
8035
+ if (firstOption.type === 'option') {
8036
+ return firstOption.value.value;
8037
+ }
8038
+ if (firstOption.type === 'group' && firstOption.options.length !== 0) {
8039
+ return firstOption.options[0].value.value;
8040
+ }
8041
+ }
8042
+ return null;
8183
8043
  };
8184
- return (
8185
- /*#__PURE__*/
8186
- /* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */
8187
- jsx("li", {
8188
- className: classNames(s('clickable'), s('border-bottom')),
8189
- onClick: createSelectHandlerForOption(placeholderOption),
8190
- onKeyPress: createSelectHandlerForOption(placeholderOption),
8191
- children: /*#__PURE__*/jsx("a", {
8192
- children: placeholder
8044
+ const firstOption = firstOptionValue();
8045
+ const isFixedCurrency = selectOptions.length === 1 && firstOption === selectedCurrency.currency || !onCurrencyChange;
8046
+ const disabled = !this.props.onAmountChange;
8047
+ return /*#__PURE__*/jsxs("div", {
8048
+ className: classNames(this.style('tw-money-input'), this.style('input-group'), this.style(`input-group-${size}`)),
8049
+ children: [/*#__PURE__*/jsx(Input, {
8050
+ id: id,
8051
+ value: this.state.formattedAmount,
8052
+ inputMode: "decimal",
8053
+ disabled: disabled,
8054
+ maxLength: maxLengthOverride,
8055
+ placeholder: formatAmountIfSet(this.props.placeholder, this.props.selectedCurrency.currency, this.state.locale, this.props.maxLengthOverride),
8056
+ autoComplete: "off",
8057
+ onKeyDown: this.handleKeyDown,
8058
+ onChange: this.onAmountChange,
8059
+ onFocus: this.onAmountFocus,
8060
+ onBlur: this.onAmountBlur,
8061
+ onPaste: this.handlePaste
8062
+ }), addon && /*#__PURE__*/jsx("span", {
8063
+ className: classNames(this.style('input-group-addon'), this.style(`input-${size}`), disabled ? this.style('disabled') : ''),
8064
+ children: addon
8065
+ }), isFixedCurrency ? /*#__PURE__*/jsxs("div", {
8066
+ className: classNames(this.style('input-group-addon'), this.style(`input-${size}`), this.style('tw-money-input__fixed-currency'), disabled ? this.style('disabled') : ''),
8067
+ children: [(size === 'lg' || size === 'md') && /*#__PURE__*/jsx("span", {
8068
+ className: classNames(this.style('money-input-currency-flag'), this.style('m-r-2')),
8069
+ children: /*#__PURE__*/jsx(Flag, {
8070
+ code: selectedCurrency.currency.toLowerCase(),
8071
+ intrinsicSize: 24
8072
+ })
8073
+ }), /*#__PURE__*/jsx(Title, {
8074
+ as: "span",
8075
+ type: Typography.TITLE_SUBSECTION,
8076
+ className: size === 'lg' ? this.style('m-r-1') : '',
8077
+ children: selectedCurrency.currency.toUpperCase()
8078
+ })]
8079
+ }) : /*#__PURE__*/jsx("div", {
8080
+ className: classNames(this.style('input-group-btn'), this.style('amount-currency-select-btn')),
8081
+ children: /*#__PURE__*/jsx(SelectInput, {
8082
+ items: selectOptions,
8083
+ value: this.state.selectedOption,
8084
+ renderValue: (currency, withinTrigger) => {
8085
+ return /*#__PURE__*/jsx(SelectInputOptionContent, {
8086
+ title: currency.label,
8087
+ note: withinTrigger ? undefined : currency.note,
8088
+ icon: /*#__PURE__*/jsx(Flag, {
8089
+ code: currency.currency,
8090
+ intrinsicSize: 24
8091
+ })
8092
+ });
8093
+ },
8094
+ renderFooter: this.props.onCustomAction ? () =>
8095
+ /*#__PURE__*/
8096
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events
8097
+ jsx("div", {
8098
+ role: "button",
8099
+ tabIndex: "0",
8100
+ onClick: this.handleCustomAction,
8101
+ children: this.props.customActionLabel
8102
+ }) : null,
8103
+ placeholder: this.formatMessage(messages$3.selectPlaceholder),
8104
+ filterable: true,
8105
+ filterPlaceholder: this.props.searchPlaceholder,
8106
+ disabled: disabled,
8107
+ size: size,
8108
+ onChange: this.handleSelectChange,
8109
+ onSearchChange: this.handleSearchChange,
8110
+ ...selectProps
8193
8111
  })
8194
- })
8195
- );
8196
- }
8197
-
8198
- // eslint-disable-next-line react/prop-types
8199
- function SeparatorOption() {
8200
- return /*#__PURE__*/jsx("li", {
8201
- className: s('np-separator'),
8202
- "aria-hidden": true
8112
+ })]
8203
8113
  });
8204
8114
  }
8205
-
8206
- // eslint-disable-next-line react/prop-types
8207
- function HeaderOption({
8208
- children
8209
- }) {
8210
- return /*#__PURE__*/jsx("li", {
8211
- // eslint-disable-line jsx-a11y/no-noninteractive-element-interactions
8212
- className: classNames(s('np-dropdown-header'), s('np-text-title-group')),
8213
- onClick: stopPropagation$1,
8214
- onKeyPress: stopPropagation$1,
8215
- children: children
8216
- });
8115
+ }
8116
+ function filterOptionsForQuery(options, query) {
8117
+ if (!query) {
8118
+ return options;
8217
8119
  }
8218
- function isOptionSelected(selected, option) {
8219
- return selected?.value === option?.value;
8120
+ const filteredOptions = removeDuplicateValueOptions(options).filter(option => isCurrencyOptionAndFitsQuery(option, query));
8121
+ return sortOptionsLabelsToFirst(filteredOptions, query);
8122
+ }
8123
+ function removeDuplicateValueOptions(options) {
8124
+ const result = [];
8125
+ const resultValues = [];
8126
+ options.forEach(option => {
8127
+ if (option.value && !resultValues.includes(option.value)) {
8128
+ result.push(option);
8129
+ resultValues.push(option.value);
8130
+ }
8131
+ });
8132
+ return result;
8133
+ }
8134
+ function isCurrencyOptionAndFitsQuery(option, query) {
8135
+ if (!option.value) {
8136
+ return false;
8220
8137
  }
8221
- const renderOption = (option, index) => {
8222
- const separatorOption = option;
8223
- if (isSeparatorOption(separatorOption) && separatorOption?.separator) {
8224
- return /*#__PURE__*/jsx(SeparatorOption, {}, index);
8138
+ return contains(option.label, query) || contains(option.searchable, query) || contains(option.note, query);
8139
+ }
8140
+ function contains(property, query) {
8141
+ return property && property.toLowerCase().includes(query.toLowerCase());
8142
+ }
8143
+ function sortOptionsLabelsToFirst(options, query) {
8144
+ return options.sort((first, second) => {
8145
+ const firstContains = contains(first.label, query);
8146
+ const secondContains = contains(second.label, query);
8147
+ if (firstContains && secondContains) {
8148
+ return 0;
8225
8149
  }
8226
- const headerOption = option;
8227
- if (isHeaderOption(headerOption) && headerOption.header) {
8228
- return /*#__PURE__*/jsx(HeaderOption, {
8229
- children: headerOption.header
8230
- }, index);
8150
+ if (firstContains) {
8151
+ return -1;
8231
8152
  }
8232
- const isActive = isOptionSelected(selected, option);
8233
- const selectOption = option;
8234
- const isFocusedWithKeyboard = !selectOption.disabled && keyboardFocusedOptionIndex === getIndexWithoutHeadersForIndexWithHeaders(index);
8235
- const className = classNames(s('np-dropdown-item'), selectOption.disabled ? [s('disabled')] : s('clickable'), {
8236
- [s('active')]: isActive,
8237
- [s('np-dropdown-item--focused')]: isFocusedWithKeyboard
8238
- });
8239
- const handleOnClick = selectOption.disabled ? stopPropagation$1 : createSelectHandlerForOption(selectOption);
8240
- return (
8241
- /*#__PURE__*/
8242
- /* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */
8243
- jsx("li", {
8244
- ref: isFocusedWithKeyboard ? keyboardFocusedReference : undefined,
8245
- id: getUniqueIdForOption(id, option),
8246
- "aria-selected": isActive,
8247
- "aria-disabled": option.disabled,
8248
- role: "option",
8249
- tabIndex: "-1",
8250
- className: className,
8251
- onClick: handleOnClick,
8252
- onKeyPress: handleOnClick,
8253
- children: /*#__PURE__*/jsx("a", {
8254
- disabled: selectOption.disabled,
8255
- children: /*#__PURE__*/jsx(Option$1, {
8256
- ...selectOption,
8257
- classNames: classNamesProp
8258
- })
8259
- })
8260
- }, index)
8261
- );
8262
- };
8263
- function getIndexWithoutHeadersForIndexWithHeaders(index) {
8264
- return options.reduce((sum, option, currentIndex) => {
8265
- if (currentIndex < index && isActionableOption(option)) {
8266
- return sum + 1;
8267
- }
8268
- return sum;
8269
- }, 0);
8270
- }
8271
- const hasActiveOptions = !!defaultOptions.length;
8272
- if (open && (initSearchValue || searchValue)) {
8273
- if (hasActiveOptions && keyboardFocusedOptionIndex == null) {
8274
- setKeyboardFocusedOptionIndex(0);
8275
- }
8276
- if (!hasActiveOptions && keyboardFocusedOptionIndex != null) {
8277
- setKeyboardFocusedOptionIndex(null);
8153
+ if (secondContains) {
8154
+ return 1;
8278
8155
  }
8279
- }
8280
- return /*#__PURE__*/jsxs("div", {
8281
- // eslint-disable-line jsx-a11y/no-static-element-interactions
8282
- ref: selectReference,
8283
- className: classNames(s('np-select'), block ? s('btn-block') : null, s('btn-group')),
8284
- onKeyDown: handleKeyDown,
8285
- onTouchMove: handleTouchStart,
8286
- onFocus: handleOnFocus,
8287
- onBlur: handleOnBlur,
8288
- children: [/*#__PURE__*/jsxs(Button, {
8289
- ref: dropdownButtonReference,
8290
- id: computedId,
8291
- block: block,
8292
- size: size,
8293
- htmlType: "button",
8294
- className: classNames(s('np-dropdown-toggle'), s('np-text-body-large'), inverse ? s('np-dropdown-toggle-navy') : null)
8295
- // reset Button's styles
8296
- ,
8297
- type: null,
8298
- priority: null,
8299
- disabled: disabled,
8300
- "aria-controls": listboxId,
8301
- "aria-expanded": open,
8302
- "aria-autocomplete": "none",
8303
- onClick: handleOnClick,
8304
- ...buttonProps,
8305
- children: [selected ? /*#__PURE__*/jsx(Option$1, {
8306
- ...selected,
8307
- classNames: classNamesProp,
8308
- selected: true
8309
- }) : /*#__PURE__*/jsx("span", {
8310
- className: s('form-control-placeholder'),
8311
- children: placeholder
8312
- }), /*#__PURE__*/jsx(Chevron$1
8313
- // disabled={disabled}
8314
- , {
8315
- className: classNames(s('tw-icon'), s('tw-chevron-up-icon'), s('tw-chevron'), s('bottom'), s('np-select-chevron'))
8316
- })]
8317
- }), isMobile ? isSearchEnabled ? /*#__PURE__*/jsx(Drawer$1, {
8318
- open: open,
8319
- headerTitle: searchPlaceholder || formatMessage(messages$3.searchPlaceholder),
8320
- onClose: handleCloseOptions,
8321
- children: renderOptionsList()
8322
- }) : /*#__PURE__*/jsx(BottomSheet$2, {
8323
- open: open,
8324
- onClose: handleCloseOptions,
8325
- children: renderOptionsList({
8326
- className: isModern ? '' : 'p-a-1'
8327
- })
8328
- }) : /*#__PURE__*/jsx(Panel$1, {
8329
- open: open,
8330
- flip: false,
8331
- altAxis: true,
8332
- anchorRef: selectReference,
8333
- anchorWidth: isDropdownAutoWidth,
8334
- position: dropdownUp ? Position.TOP : Position.BOTTOM,
8335
- onClose: handleCloseOptions,
8336
- children: renderOptionsList({
8337
- className: 'p-a-1'
8338
- })
8339
- })]
8156
+ return 0;
8340
8157
  });
8341
8158
  }
8342
- Select.propTypes = {
8343
- placeholder: PropTypes.string,
8159
+ MoneyInput.propTypes = {
8344
8160
  id: PropTypes.string,
8345
- required: PropTypes.bool,
8346
- disabled: PropTypes.bool,
8347
- inverse: PropTypes.bool,
8348
- dropdownRight: PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl']),
8349
- dropdownWidth: PropTypes.oneOf(['sm', 'md', 'lg']),
8161
+ currencies: PropTypes.arrayOf(Currency).isRequired,
8162
+ selectedCurrency: Currency.isRequired,
8163
+ onCurrencyChange: PropTypes.func,
8164
+ placeholder: PropTypes.number,
8165
+ amount: PropTypes.number,
8350
8166
  size: PropTypes.oneOf(['sm', 'md', 'lg']),
8351
- block: PropTypes.bool,
8352
- selected: PropTypes.shape({
8353
- value: PropTypes.any.isRequired,
8354
- label: PropTypes.node,
8355
- icon: PropTypes.node,
8356
- currency: PropTypes.string,
8357
- note: PropTypes.node,
8358
- secondary: PropTypes.node
8359
- }),
8360
- /**
8361
- * Search toggle
8362
- * if `true` default search functionality being enabled (not case sensitive search in option labels & currency props)
8363
- * if `function` you can define your own search function to implement custom search experience. This search function used while filtering the options array. The custom search function takes two parameters. First is the option the second is the keyword.
8364
- */
8365
- search: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
8366
- onChange: PropTypes.func.isRequired,
8367
- onFocus: PropTypes.func,
8368
- onBlur: PropTypes.func,
8369
- options: PropTypes.arrayOf(PropTypes.shape({
8370
- value: PropTypes.any,
8371
- label: PropTypes.node,
8372
- header: PropTypes.node,
8373
- icon: PropTypes.node,
8374
- currency: PropTypes.string,
8375
- note: PropTypes.node,
8376
- secondary: PropTypes.node,
8377
- separator: PropTypes.bool,
8378
- disabled: PropTypes.bool,
8379
- searchStrings: PropTypes.arrayOf(PropTypes.string)
8380
- })).isRequired,
8167
+ onAmountChange: PropTypes.func,
8168
+ addon: PropTypes.node,
8169
+ searchPlaceholder: PropTypes.string,
8381
8170
  /**
8382
- * To have full control of your search value and response use `onSearchChange` function combined with `searchValue` and custom filtering on the options array.
8383
- * DO NOT USE TOGETHER WITH `search` PROPERTY
8171
+ * Allows the consumer to react to searching, while the search itself is handled internally. Called with `{ searchQuery: string, filteredOptions: Currency[] }`
8384
8172
  */
8385
8173
  onSearchChange: PropTypes.func,
8386
- searchValue: PropTypes.string,
8387
- searchPlaceholder: PropTypes.string,
8174
+ customActionLabel: PropTypes.node,
8175
+ onCustomAction: PropTypes.func,
8388
8176
  classNames: PropTypes.objectOf(PropTypes.string),
8389
- dropdownUp: PropTypes.bool,
8390
- buttonProps: PropTypes.object,
8391
- dropdownProps: PropTypes.object
8177
+ selectProps: PropTypes.object,
8178
+ maxLengthOverride: PropTypes.number
8392
8179
  };
8393
- Select.defaultProps = {
8394
- id: undefined,
8395
- placeholder: undefined,
8396
- size: 'md',
8397
- dropdownRight: null,
8398
- dropdownWidth: null,
8399
- inverse: false,
8400
- required: false,
8401
- disabled: false,
8402
- block: true,
8403
- selected: null,
8404
- onFocus: null,
8405
- onBlur: null,
8180
+ MoneyInput.defaultProps = {
8181
+ id: null,
8182
+ size: Size.LARGE,
8183
+ addon: null,
8184
+ searchPlaceholder: '',
8406
8185
  onSearchChange: undefined,
8407
- search: false,
8408
- searchValue: '',
8409
- searchPlaceholder: undefined,
8186
+ onCurrencyChange: null,
8187
+ placeholder: null,
8188
+ amount: null,
8189
+ onAmountChange: null,
8190
+ customActionLabel: '',
8191
+ onCustomAction: null,
8410
8192
  classNames: {},
8411
- dropdownUp: false,
8412
- buttonProps: {},
8413
- dropdownProps: {}
8193
+ selectProps: {},
8194
+ maxLengthOverride: null
8414
8195
  };
8196
+ var MoneyInput$1 = injectIntl(MoneyInput);
8415
8197
 
8416
- var messages$2 = defineMessages({
8417
- selectPlaceholder: {
8418
- id: "neptune.MoneyInput.Select.placeholder"
8419
- }
8420
- });
8421
-
8422
- // TODO: do not duplicate this between formatting and components
8423
- const currencyDecimals = {
8424
- BIF: 0,
8425
- BYR: 0,
8426
- CLP: 0,
8427
- DJF: 0,
8428
- GNF: 0,
8429
- JPY: 0,
8430
- KMF: 0,
8431
- KRW: 0,
8432
- MGA: 0,
8433
- PYG: 0,
8434
- RWF: 0,
8435
- VND: 0,
8436
- VUV: 0,
8437
- XAF: 0,
8438
- XOF: 0,
8439
- XPF: 0,
8440
- // technically HUF does have decimals, but due to the exchange rate banks
8441
- // do not accept decimal amounts
8442
- HUF: 0,
8443
- BHD: 3,
8444
- JOD: 3,
8445
- KWD: 3,
8446
- OMR: 3,
8447
- TND: 3
8198
+ const NavigationOptionList = ({
8199
+ children
8200
+ }) => {
8201
+ return /*#__PURE__*/jsx("ul", {
8202
+ className: "np-navigation-options-list",
8203
+ children: Children.map(children, child => /*#__PURE__*/jsx("li", {
8204
+ className: "np-navigation-options-list__item",
8205
+ children: child
8206
+ }, child.key))
8207
+ });
8448
8208
  };
8449
- const DEFAULT_CURRENCY_DECIMALS = 2;
8450
- function isNumberLocaleSupported() {
8451
- const number = 1234;
8452
- const numberString = number.toLocaleString && number.toLocaleString(DEFAULT_LOCALE);
8453
- return numberString === '1,234';
8454
- }
8455
- function getValidLocale(locale) {
8456
- try {
8457
- const noUnderscoreLocale = locale.replace(/_/, '-');
8458
- Intl.NumberFormat(noUnderscoreLocale);
8459
- return noUnderscoreLocale;
8460
- } catch {
8461
- return 'en-GB';
8462
- }
8463
- }
8464
- function getCurrencyDecimals(currency = '') {
8465
- const upperCaseCurrency = currency.toUpperCase();
8466
- if (Object.prototype.hasOwnProperty.call(currencyDecimals, upperCaseCurrency)) {
8467
- return currencyDecimals[upperCaseCurrency];
8468
- }
8469
- return DEFAULT_CURRENCY_DECIMALS;
8470
- }
8471
- function getDecimalSeparator(locale) {
8472
- return isNumberLocaleSupported() ? 1.1.toLocaleString(locale)[1] : '.';
8473
- }
8474
- function parseAmount(number, currency, locale) {
8475
- const validLocale = getValidLocale(locale);
8476
- const precision = getCurrencyDecimals(currency);
8477
- const groupSeparator = isNumberLocaleSupported() ? 10000 .toLocaleString(validLocale)[2] : ',';
8478
- const decimalSeparator = getDecimalSeparator(validLocale);
8479
- const numberWithStandardDecimalSeparator = (number ? `${number}` : '').replace(new RegExp(`\\${groupSeparator}`, 'g'), '').replace(new RegExp(`\\${decimalSeparator}`, 'g'), '.').replace(/[^0-9.]/g, '');
8480
- const parsedAmount = parseFloat(parseFloat(numberWithStandardDecimalSeparator).toFixed(precision));
8481
- return Math.abs(parsedAmount);
8482
- }
8209
+ NavigationOptionList.propTypes = {
8210
+ children: PropTypes.node.isRequired
8211
+ };
8212
+ var NavigationOptionList$1 = NavigationOptionList;
8483
8213
 
8484
- const Currency = PropTypes.shape({
8485
- header: PropTypes.string,
8486
- value: PropTypes.string,
8487
- label: PropTypes.string,
8488
- currency: PropTypes.string,
8489
- note: PropTypes.string,
8490
- searchable: PropTypes.string
8491
- });
8492
- const CUSTOM_ACTION = 'CUSTOM_ACTION';
8493
- const isNumberOrNull = v => isNumber(v) || isNull(v);
8494
- const formatAmountIfSet = (amount, currency, locale, maxLengthOverride) => {
8495
- if (maxLengthOverride) {
8496
- return amount || '';
8497
- } else {
8498
- return typeof amount === 'number' ? formatAmount(amount, currency, locale) : '';
8499
- }
8500
- };
8501
- const parseNumber = (amount, currency, locale, maxLengthOverride) => {
8502
- if (!maxLengthOverride) {
8503
- return parseAmount(amount, currency, locale);
8504
- }
8505
- if (maxLengthOverride && amount.length > maxLengthOverride) {
8506
- return 0;
8507
- }
8508
- return +amount;
8509
- };
8510
- const inputKeyCodeAllowlist = new Set([KeyCodes.BACKSPACE, KeyCodes.DELETE, KeyCodes.COMMA, KeyCodes.PERIOD, KeyCodes.DOWN, KeyCodes.UP, KeyCodes.LEFT, KeyCodes.RIGHT, KeyCodes.ENTER, KeyCodes.ESCAPE, KeyCodes.TAB]);
8511
- const inputKeyAllowlist = new Set([Key.PERIOD, Key.COMMA]);
8512
- class MoneyInput extends Component {
8513
- constructor(props) {
8514
- super(props);
8515
- const {
8516
- locale
8517
- } = this.props.intl;
8518
- this.formatMessage = this.props.intl.formatMessage;
8519
- this.state = {
8520
- searchQuery: '',
8521
- formattedAmount: formatAmountIfSet(props.amount, props.selectedCurrency.currency, locale, props.maxLengthOverride),
8522
- locale
8523
- };
8524
- }
8525
- UNSAFE_componentWillReceiveProps(nextProps) {
8526
- this.setState({
8527
- locale: nextProps?.intl?.locale
8528
- });
8529
- if (!this.amountFocused) {
8530
- this.setState({
8531
- formattedAmount: formatAmountIfSet(nextProps.amount, nextProps.selectedCurrency.currency, nextProps?.intl?.locale, nextProps.maxLengthOverride)
8532
- });
8533
- }
8534
- }
8535
- isInputAllowedForKeyEvent = event => {
8536
- const {
8537
- keyCode,
8538
- metaKey,
8539
- key,
8540
- ctrlKey
8541
- } = event;
8542
- const isNumberKey = isNumber(parseInt(key, 10));
8543
- return isNumberKey || metaKey || ctrlKey || inputKeyCodeAllowlist.has(keyCode) || inputKeyAllowlist.has(key);
8544
- };
8545
- handleKeyDown = event => {
8546
- if (!this.isInputAllowedForKeyEvent(event)) {
8547
- event.preventDefault();
8214
+ const STORAGE_NAME = 'dismissedNudges';
8215
+ const getLocalStorage = () => {
8216
+ try {
8217
+ const storageItem = localStorage.getItem(STORAGE_NAME);
8218
+ if (storageItem) {
8219
+ const storage = JSON.parse(storageItem);
8220
+ if (Array.isArray(storage)) {
8221
+ return storage;
8222
+ }
8548
8223
  }
8549
- };
8550
- handlePaste = event => {
8551
- const paste = (event.clipboardData || window.clipboardData).getData('text');
8552
- const {
8553
- locale
8554
- } = this.state;
8555
- const parsed = isEmpty(paste) ? null : parseNumber(paste, this.props.selectedCurrency.currency, locale, this.props.maxLengthOverride);
8556
- if (isNumberOrNull(parsed)) {
8557
- this.setState({
8558
- formattedAmount: formatAmountIfSet(parsed, this.props.selectedCurrency.currency, locale, this.props.maxLengthOverride)
8559
- });
8560
- this.props.onAmountChange(parsed);
8224
+ // eslint-disable-next-line unicorn/prefer-optional-catch-binding, no-empty
8225
+ } catch (error) {}
8226
+ return [];
8227
+ };
8228
+ const Nudge = ({
8229
+ media,
8230
+ mediaName,
8231
+ title,
8232
+ link,
8233
+ href,
8234
+ onClick,
8235
+ onDismiss,
8236
+ persistDismissal,
8237
+ isPreviouslyDismissed,
8238
+ id,
8239
+ className
8240
+ }) => {
8241
+ const [isDismissed, setIsDismissed] = useState(false);
8242
+ const [isMounted, setIsMounted] = useState(false);
8243
+ const handleOnDismiss = () => {
8244
+ const dismissedNudgesStorage = getLocalStorage();
8245
+ if (persistDismissal && id) {
8246
+ try {
8247
+ localStorage.setItem(STORAGE_NAME, JSON.stringify([...(dismissedNudgesStorage ? dismissedNudgesStorage : []), id]));
8248
+ // eslint-disable-next-line unicorn/prefer-optional-catch-binding, no-empty
8249
+ } catch (error) {}
8250
+ setIsDismissed(true);
8561
8251
  }
8562
- event.preventDefault();
8563
- };
8564
- onAmountChange = event => {
8565
- const {
8566
- value
8567
- } = event.target;
8568
- this.setState({
8569
- formattedAmount: value
8570
- });
8571
- const parsed = isEmpty(value) ? null : parseNumber(value, this.props.selectedCurrency.currency, this.state.locale, this.props.maxLengthOverride);
8572
- if (isNumberOrNull(parsed)) {
8573
- this.props.onAmountChange(parsed);
8252
+ if (onDismiss) {
8253
+ onDismiss();
8574
8254
  }
8575
8255
  };
8576
- onAmountBlur = () => {
8577
- this.amountFocused = false;
8578
- this.setAmount();
8579
- };
8580
- onAmountFocus = () => {
8581
- this.amountFocused = true;
8582
- };
8583
- getSelectOptions() {
8584
- const selectOptions = [...filterOptionsForQuery(this.props.currencies, this.state.searchQuery)];
8585
- if (this.props.onCustomAction) {
8586
- selectOptions.push({
8587
- value: CUSTOM_ACTION,
8588
- label: this.props.customActionLabel
8589
- });
8590
- }
8591
- return selectOptions;
8592
- }
8593
- setAmount() {
8594
- this.setState(previousState => {
8595
- const parsed = parseNumber(previousState.formattedAmount, this.props.selectedCurrency.currency, previousState.locale, this.props.maxLengthOverride);
8596
- if (!isNumberOrNull(parsed)) {
8597
- return {
8598
- formattedAmount: previousState.formattedAmount
8599
- };
8256
+ useEffect(() => {
8257
+ if (persistDismissal && id) {
8258
+ const dismissedNudgesStorage = getLocalStorage();
8259
+ let isDismissed = false;
8260
+ if (dismissedNudgesStorage && dismissedNudgesStorage.find(item => item === id)) {
8261
+ setIsDismissed(true);
8262
+ isDismissed = true;
8263
+ }
8264
+ if (isPreviouslyDismissed) {
8265
+ isPreviouslyDismissed(isDismissed);
8600
8266
  }
8601
- return {
8602
- formattedAmount: formatAmountIfSet(parsed, this.props.selectedCurrency.currency, previousState.locale, this.props.maxLengthOverride)
8603
- };
8604
- });
8605
- }
8606
- handleSelectChange = value => {
8607
- this.handleSearchChange('');
8608
- if (this.props.onCustomAction && value.value === CUSTOM_ACTION) {
8609
- this.props.onCustomAction();
8610
- } else {
8611
- this.props.onCurrencyChange(value);
8612
- }
8613
- };
8614
- handleSearchChange = searchQuery => {
8615
- this.setState({
8616
- searchQuery
8617
- });
8618
- if (this.props.onSearchChange) {
8619
- this.props.onSearchChange({
8620
- searchQuery,
8621
- filteredOptions: filterOptionsForQuery(this.props.currencies, searchQuery)
8622
- });
8623
- }
8624
- };
8625
- style = className => this.props.classNames[className] || className;
8626
- render() {
8627
- const {
8628
- selectedCurrency,
8629
- onCurrencyChange,
8630
- size,
8631
- addon,
8632
- id,
8633
- selectProps,
8634
- maxLengthOverride
8635
- } = this.props;
8636
- const selectOptions = this.getSelectOptions();
8637
- const isFixedCurrency = !this.state.searchQuery && (selectOptions.length === 1 && selectOptions[0].currency === selectedCurrency.currency || !onCurrencyChange);
8638
- const disabled = !this.props.onAmountChange;
8639
- return /*#__PURE__*/jsxs("div", {
8640
- className: classNames(this.style('tw-money-input'), this.style('input-group'), this.style(`input-group-${size}`)),
8641
- children: [/*#__PURE__*/jsx(Input, {
8642
- id: id,
8643
- value: this.state.formattedAmount,
8644
- inputMode: "decimal",
8645
- disabled: disabled,
8646
- maxLength: maxLengthOverride,
8647
- placeholder: formatAmountIfSet(this.props.placeholder, this.props.selectedCurrency.currency, this.state.locale, this.props.maxLengthOverride),
8648
- autoComplete: "off",
8649
- onKeyDown: this.handleKeyDown,
8650
- onChange: this.onAmountChange,
8651
- onFocus: this.onAmountFocus,
8652
- onBlur: this.onAmountBlur,
8653
- onPaste: this.handlePaste
8654
- }), addon && /*#__PURE__*/jsx("span", {
8655
- className: classNames(this.style('input-group-addon'), this.style(`input-${size}`), disabled ? this.style('disabled') : ''),
8656
- children: addon
8657
- }), isFixedCurrency ? /*#__PURE__*/jsxs("div", {
8658
- className: classNames(this.style('input-group-addon'), this.style(`input-${size}`), this.style('tw-money-input__fixed-currency'), disabled ? this.style('disabled') : ''),
8659
- children: [(size === 'lg' || size === 'md') && /*#__PURE__*/jsxs(Fragment, {
8660
- children: [/*#__PURE__*/jsx("i", {
8661
- className: classNames(this.style('tw-money-input__keyline'))
8662
- }), /*#__PURE__*/jsx("i", {
8663
- className: classNames(this.style('currency-flag'), this.style(`currency-flag-${selectedCurrency.currency.toLowerCase()}`), this.style('m-r-2'))
8664
- })]
8665
- }), /*#__PURE__*/jsx(Title, {
8666
- as: "span",
8667
- type: Typography.TITLE_SUBSECTION,
8668
- className: size === 'lg' ? this.style('m-r-1') : '',
8669
- children: selectedCurrency.currency.toUpperCase()
8670
- })]
8671
- }) : /*#__PURE__*/jsx("div", {
8672
- className: classNames(this.style('input-group-btn'), this.style('amount-currency-select-btn')),
8673
- children: /*#__PURE__*/jsx(Select, {
8674
- id: id ? `${id}-select` : undefined,
8675
- classNames: this.props.classNames,
8676
- options: selectOptions,
8677
- selected: {
8678
- ...selectedCurrency,
8679
- label: /*#__PURE__*/jsx(Title, {
8680
- as: "span",
8681
- type: Typography.TITLE_SUBSECTION,
8682
- className: "tw-money-input__text",
8683
- children: selectedCurrency.label
8684
- }),
8685
- note: null
8686
- },
8687
- placeholder: this.formatMessage(messages$2.selectPlaceholder),
8688
- searchPlaceholder: this.props.searchPlaceholder,
8689
- searchValue: this.state.searchQuery,
8690
- size: size,
8691
- required: true,
8692
- dropdownWidth: Size.LARGE,
8693
- inverse: true,
8694
- onChange: this.handleSelectChange,
8695
- onSearchChange: this.handleSearchChange,
8696
- ...selectProps
8697
- })
8698
- })]
8699
- });
8700
- }
8701
- }
8702
- function filterOptionsForQuery(options, query) {
8703
- if (!query) {
8704
- return options;
8705
- }
8706
- const filteredOptions = removeDuplicateValueOptions(options).filter(option => isCurrencyOptionAndFitsQuery(option, query));
8707
- return sortOptionsLabelsToFirst(filteredOptions, query);
8708
- }
8709
- function removeDuplicateValueOptions(options) {
8710
- const result = [];
8711
- const resultValues = [];
8712
- options.forEach(option => {
8713
- if (option.value && !resultValues.includes(option.value)) {
8714
- result.push(option);
8715
- resultValues.push(option.value);
8716
8267
  }
8717
- });
8718
- return result;
8719
- }
8720
- function isCurrencyOptionAndFitsQuery(option, query) {
8721
- if (!option.value) {
8722
- return false;
8723
- }
8724
- return contains(option.label, query) || contains(option.searchable, query) || contains(option.note, query);
8725
- }
8726
- function contains(property, query) {
8727
- return property && property.toLowerCase().includes(query.toLowerCase());
8728
- }
8729
- function sortOptionsLabelsToFirst(options, query) {
8730
- return options.sort((first, second) => {
8731
- const firstContains = contains(first.label, query);
8732
- const secondContains = contains(second.label, query);
8733
- if (firstContains && secondContains) {
8734
- return 0;
8735
- }
8736
- if (firstContains) {
8737
- return -1;
8738
- }
8739
- if (secondContains) {
8740
- return 1;
8741
- }
8742
- return 0;
8743
- });
8744
- }
8745
- MoneyInput.propTypes = {
8746
- id: PropTypes.string,
8747
- currencies: PropTypes.arrayOf(Currency).isRequired,
8748
- selectedCurrency: Currency.isRequired,
8749
- onCurrencyChange: PropTypes.func,
8750
- placeholder: PropTypes.number,
8751
- amount: PropTypes.number,
8752
- size: PropTypes.oneOf(['sm', 'md', 'lg']),
8753
- onAmountChange: PropTypes.func,
8754
- addon: PropTypes.node,
8755
- searchPlaceholder: PropTypes.string,
8756
- /**
8757
- * Allows the consumer to react to searching, while the search itself is handled internally. Called with `{ searchQuery: string, filteredOptions: Currency[] }`
8758
- */
8759
- onSearchChange: PropTypes.func,
8760
- customActionLabel: PropTypes.node,
8761
- onCustomAction: PropTypes.func,
8762
- classNames: PropTypes.objectOf(PropTypes.string),
8763
- selectProps: PropTypes.object,
8764
- maxLengthOverride: PropTypes.number
8765
- };
8766
- MoneyInput.defaultProps = {
8767
- id: null,
8768
- size: Size.LARGE,
8769
- addon: null,
8770
- searchPlaceholder: '',
8771
- onSearchChange: undefined,
8772
- onCurrencyChange: null,
8773
- placeholder: null,
8774
- amount: null,
8775
- onAmountChange: null,
8776
- customActionLabel: '',
8777
- onCustomAction: null,
8778
- classNames: {},
8779
- selectProps: {},
8780
- maxLengthOverride: null
8781
- };
8782
- var MoneyInput$1 = injectIntl(MoneyInput);
8783
-
8784
- const NavigationOptionList = ({
8785
- children
8786
- }) => {
8787
- return /*#__PURE__*/jsx("ul", {
8788
- className: "np-navigation-options-list",
8789
- children: Children.map(children, child => /*#__PURE__*/jsx("li", {
8790
- className: "np-navigation-options-list__item",
8791
- children: child
8792
- }, child.key))
8793
- });
8794
- };
8795
- NavigationOptionList.propTypes = {
8796
- children: PropTypes.node.isRequired
8797
- };
8798
- var NavigationOptionList$1 = NavigationOptionList;
8799
-
8800
- const STORAGE_NAME = 'dismissedNudges';
8801
- const getLocalStorage = () => {
8802
- try {
8803
- const storageItem = localStorage.getItem(STORAGE_NAME);
8804
- if (storageItem) {
8805
- const storage = JSON.parse(storageItem);
8806
- if (Array.isArray(storage)) {
8807
- return storage;
8808
- }
8809
- }
8810
- // eslint-disable-next-line unicorn/prefer-optional-catch-binding, no-empty
8811
- } catch (error) {}
8812
- return [];
8813
- };
8814
- const Nudge = ({
8815
- media,
8816
- mediaName,
8817
- title,
8818
- link,
8819
- href,
8820
- onClick,
8821
- onDismiss,
8822
- persistDismissal,
8823
- isPreviouslyDismissed,
8824
- id,
8825
- className
8826
- }) => {
8827
- const [isDismissed, setIsDismissed] = useState(false);
8828
- const [isMounted, setIsMounted] = useState(false);
8829
- const handleOnDismiss = () => {
8830
- const dismissedNudgesStorage = getLocalStorage();
8831
- if (persistDismissal && id) {
8832
- try {
8833
- localStorage.setItem(STORAGE_NAME, JSON.stringify([...(dismissedNudgesStorage ? dismissedNudgesStorage : []), id]));
8834
- // eslint-disable-next-line unicorn/prefer-optional-catch-binding, no-empty
8835
- } catch (error) {}
8836
- setIsDismissed(true);
8837
- }
8838
- if (onDismiss) {
8839
- onDismiss();
8840
- }
8841
- };
8842
- useEffect(() => {
8843
- if (persistDismissal && id) {
8844
- const dismissedNudgesStorage = getLocalStorage();
8845
- let isDismissed = false;
8846
- if (dismissedNudgesStorage && dismissedNudgesStorage.find(item => item === id)) {
8847
- setIsDismissed(true);
8848
- isDismissed = true;
8849
- }
8850
- if (isPreviouslyDismissed) {
8851
- isPreviouslyDismissed(isDismissed);
8852
- }
8853
- }
8854
- setIsMounted(true);
8855
- // eslint-disable-next-line react-hooks/exhaustive-deps
8856
- }, [id, persistDismissal]);
8857
- if (persistDismissal && (isDismissed || !isMounted)) {
8858
- return null;
8268
+ setIsMounted(true);
8269
+ // eslint-disable-next-line react-hooks/exhaustive-deps
8270
+ }, [id, persistDismissal]);
8271
+ if (persistDismissal && (isDismissed || !isMounted)) {
8272
+ return null;
8859
8273
  }
8860
8274
  return /*#__PURE__*/jsxs("div", {
8861
8275
  className: classNames('wds-nudge', className),
@@ -11002,179 +10416,836 @@ const Radio = ({
11002
10416
  })]
11003
10417
  })
11004
10418
  });
11005
- };
11006
- Radio.propTypes = {
11007
- avatar: PropTypes.element,
11008
- checked: PropTypes.bool,
11009
- disabled: PropTypes.bool,
10419
+ };
10420
+ Radio.propTypes = {
10421
+ avatar: PropTypes.element,
10422
+ checked: PropTypes.bool,
10423
+ disabled: PropTypes.bool,
10424
+ id: PropTypes.string,
10425
+ label: PropTypes.string.isRequired,
10426
+ name: PropTypes.string.isRequired,
10427
+ onChange: PropTypes.func.isRequired,
10428
+ secondary: PropTypes.string,
10429
+ value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
10430
+ className: PropTypes.string
10431
+ };
10432
+ Radio.defaultProps = {
10433
+ avatar: undefined,
10434
+ checked: false,
10435
+ disabled: false,
10436
+ id: null,
10437
+ secondary: null,
10438
+ value: '',
10439
+ className: undefined
10440
+ };
10441
+ var Radio$1 = Radio;
10442
+
10443
+ class RadioGroup extends Component {
10444
+ constructor(props) {
10445
+ super(props);
10446
+ this.state = {
10447
+ selectedValue: props.selectedValue
10448
+ };
10449
+ }
10450
+ handleOnChange = selectedValue => {
10451
+ const {
10452
+ onChange
10453
+ } = this.props;
10454
+ this.setState({
10455
+ selectedValue
10456
+ }, onChange && onChange(selectedValue));
10457
+ };
10458
+ render() {
10459
+ const {
10460
+ radios,
10461
+ name
10462
+ } = this.props;
10463
+ const {
10464
+ selectedValue
10465
+ } = this.state;
10466
+ return radios && radios.length > 0 ? /*#__PURE__*/jsx(Fragment, {
10467
+ children: radios.map(({
10468
+ id,
10469
+ avatar,
10470
+ value,
10471
+ label,
10472
+ disabled,
10473
+ secondary,
10474
+ readOnly
10475
+ }, index) => /*#__PURE__*/jsx(Radio$1
10476
+ // eslint-disable-next-line react/no-array-index-key
10477
+ , {
10478
+ id: id,
10479
+ value: value,
10480
+ label: label,
10481
+ name: name,
10482
+ disabled: disabled,
10483
+ checked: selectedValue === value,
10484
+ secondary: secondary,
10485
+ readOnly: readOnly,
10486
+ avatar: avatar,
10487
+ onChange: value_ => this.handleOnChange(value_)
10488
+ }, index))
10489
+ }) : null;
10490
+ }
10491
+ }
10492
+ RadioGroup.propTypes = {
10493
+ radios: PropTypes.arrayOf(PropTypes.shape({
10494
+ id: PropTypes.string,
10495
+ avatar: PropTypes.element,
10496
+ value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
10497
+ secondary: PropTypes.string,
10498
+ label: PropTypes.string.isRequired,
10499
+ disabled: PropTypes.bool,
10500
+ readOnly: PropTypes.bool
10501
+ })).isRequired,
10502
+ onChange: PropTypes.func.isRequired,
10503
+ selectedValue: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
10504
+ name: PropTypes.string.isRequired
10505
+ };
10506
+ RadioGroup.defaultProps = {
10507
+ selectedValue: null
10508
+ };
10509
+ var RadioGroup$1 = RadioGroup;
10510
+
10511
+ const RadioOption = ({
10512
+ 'aria-label': ariaLabel,
10513
+ media,
10514
+ title,
10515
+ content,
10516
+ id,
10517
+ name,
10518
+ checked,
10519
+ onChange,
10520
+ complex,
10521
+ disabled,
10522
+ value,
10523
+ showMediaCircle,
10524
+ showMediaAtAllSizes,
10525
+ isContainerAligned
10526
+ }) => {
10527
+ const sharedProps = {
10528
+ 'aria-label': ariaLabel,
10529
+ media,
10530
+ title,
10531
+ content,
10532
+ name,
10533
+ complex,
10534
+ disabled,
10535
+ showMediaCircle,
10536
+ showMediaAtAllSizes,
10537
+ isContainerAligned
10538
+ };
10539
+ return /*#__PURE__*/jsx(Option$2, {
10540
+ ...sharedProps,
10541
+ button: /*#__PURE__*/jsx(RadioButton$1, {
10542
+ id: id,
10543
+ name: name,
10544
+ checked: checked,
10545
+ disabled: disabled,
10546
+ value: value,
10547
+ onChange: onChange
10548
+ })
10549
+ });
10550
+ };
10551
+ RadioOption.propTypes = {
10552
+ 'aria-label': PropTypes.string,
10553
+ media: PropTypes.node,
10554
+ id: PropTypes.string.isRequired,
10555
+ name: PropTypes.string.isRequired,
10556
+ title: PropTypes.node.isRequired,
10557
+ content: PropTypes.node,
10558
+ checked: PropTypes.bool,
10559
+ onChange: PropTypes.func.isRequired,
10560
+ complex: PropTypes.bool,
10561
+ disabled: PropTypes.bool,
10562
+ value: PropTypes.string,
10563
+ showMediaCircle: PropTypes.bool,
10564
+ showMediaAtAllSizes: PropTypes.bool,
10565
+ isContainerAligned: PropTypes.bool
10566
+ };
10567
+ RadioOption.defaultProps = {
10568
+ 'aria-label': undefined,
10569
+ media: null,
10570
+ content: null,
10571
+ checked: false,
10572
+ complex: false,
10573
+ disabled: false,
10574
+ showMediaCircle: true,
10575
+ showMediaAtAllSizes: false,
10576
+ isContainerAligned: false,
10577
+ value: ''
10578
+ };
10579
+ var RadioOption$1 = RadioOption;
10580
+
10581
+ const Section = ({
10582
+ children,
10583
+ className,
10584
+ withHorizontalPadding = false
10585
+ }) => {
10586
+ return /*#__PURE__*/jsx("div", {
10587
+ className: classNames('np-section', className, {
10588
+ 'np-section--with-horizontal-padding': withHorizontalPadding
10589
+ }),
10590
+ children: children
10591
+ });
10592
+ };
10593
+
10594
+ var messages$2 = defineMessages({
10595
+ searchPlaceholder: {
10596
+ id: "neptune.Select.searchPlaceholder"
10597
+ }
10598
+ });
10599
+
10600
+ // Option.tsx NEW
10601
+ function Option$1({
10602
+ label,
10603
+ currency = '',
10604
+ note = '',
10605
+ secondary = '',
10606
+ icon,
10607
+ classNames = {},
10608
+ selected = false
10609
+ }) {
10610
+ const {
10611
+ isModern
10612
+ } = useTheme();
10613
+ const style = classes => classes.map(className => classNames[className] || className).join(' ');
10614
+ const currencyClassNames = currency ? style(['currency-flag', `currency-flag-${currency.toLowerCase()}`]) : undefined;
10615
+ const iconElement = icon ? /*#__PURE__*/cloneElement(icon, {
10616
+ size: 24,
10617
+ className: 'tw-icon'
10618
+ }) : currency && /*#__PURE__*/jsx("i", {
10619
+ className: currencyClassNames
10620
+ });
10621
+ const titleAndNoteElement = /*#__PURE__*/jsxs(Body, {
10622
+ as: "span",
10623
+ type: Typography.BODY_LARGE,
10624
+ className: selected ? 'text-ellipsis' : undefined,
10625
+ children: [label, note && /*#__PURE__*/jsx(Body, {
10626
+ as: "span",
10627
+ className: isModern ? 'm-l-1' : 'm-l-1 body-2',
10628
+ children: note
10629
+ })]
10630
+ });
10631
+ const secondaryElementClassNames = () => {
10632
+ let classes = undefined;
10633
+ if (selected) {
10634
+ classes = 'text-ellipsis';
10635
+ }
10636
+ if (isModern) {
10637
+ return classes;
10638
+ }
10639
+ return `${classes ? classes + ' ' : ''}body-2`;
10640
+ };
10641
+ const secondaryElement = secondary && /*#__PURE__*/jsx(Body, {
10642
+ className: secondaryElementClassNames(),
10643
+ children: secondary
10644
+ });
10645
+ return iconElement ? /*#__PURE__*/jsxs("div", {
10646
+ className: "d-flex np-option-content",
10647
+ children: [/*#__PURE__*/jsx("div", {
10648
+ className: `d-flex flex-column${selected ? ' justify-content-center' : ''}`,
10649
+ children: iconElement
10650
+ }), /*#__PURE__*/jsxs("div", {
10651
+ className: "d-flex flex-column justify-content-center",
10652
+ children: [titleAndNoteElement, secondaryElement]
10653
+ })]
10654
+ }) : /*#__PURE__*/jsxs(Fragment, {
10655
+ children: [iconElement, titleAndNoteElement, secondaryElement]
10656
+ });
10657
+ }
10658
+
10659
+ const SearchBox = /*#__PURE__*/forwardRef(({
10660
+ id,
10661
+ classNames: classNames$1 = {},
10662
+ focusedOptionId,
10663
+ onChange,
10664
+ onClick,
10665
+ placeholder = undefined,
10666
+ value = ''
10667
+ }, reference) => {
10668
+ const style = className => classNames$1[className] || className;
10669
+ return /*#__PURE__*/jsx("li", {
10670
+ className: style('border-bottom'),
10671
+ children: /*#__PURE__*/jsx("a", {
10672
+ className: `${style('np-select-filter-link')} ${style('p-a-0')}`,
10673
+ children: /*#__PURE__*/jsxs("div", {
10674
+ className: style('input-group'),
10675
+ children: [/*#__PURE__*/jsx("span", {
10676
+ className: classNames('input-group-addon', 'input-group-addon--search'),
10677
+ children: /*#__PURE__*/jsx(Search, {
10678
+ className: classNames(style('tw-icon'), style('tw-icon-search')),
10679
+ size: 24
10680
+ })
10681
+ }), /*#__PURE__*/jsx(Input, {
10682
+ ref: reference,
10683
+ id: id,
10684
+ role: "searchbox",
10685
+ inputMode: "search",
10686
+ className: classNames(style('np-select-filter')),
10687
+ placeholder: placeholder,
10688
+ value: value,
10689
+ spellCheck: "false",
10690
+ "aria-activedescendant": focusedOptionId,
10691
+ onChange: onChange,
10692
+ onClick: onClick
10693
+ })]
10694
+ })
10695
+ })
10696
+ });
10697
+ });
10698
+ var SearchBox$1 = SearchBox;
10699
+
10700
+ const DEFAULT_SEARCH_VALUE = '';
10701
+ const DEFAULT_OPTIONS_PAGE_SIZE = 1000;
10702
+ const includesString = (string1, string2) => string1.toLowerCase().includes(string2.toLowerCase());
10703
+ function defaultFilterFunction(option, searchValue) {
10704
+ if (isPlaceholderOption(option)) {
10705
+ return true;
10706
+ }
10707
+ const {
10708
+ label,
10709
+ note,
10710
+ secondary,
10711
+ currency,
10712
+ searchStrings
10713
+ } = option;
10714
+ return !!label && includesString(label, searchValue) || !!note && includesString(note, searchValue) || !!secondary && includesString(secondary, searchValue) || !!currency && includesString(currency, searchValue) || !!searchStrings && searchStrings.some(string => includesString(string, searchValue));
10715
+ }
10716
+ function isActionableOption(option) {
10717
+ return !option.header && !option.separator && !option.disabled;
10718
+ }
10719
+ function isHeaderOption(option) {
10720
+ return option != null && 'header' in option;
10721
+ }
10722
+ function isSeparatorOption(option) {
10723
+ return option != null && 'separator' in option;
10724
+ }
10725
+ function clamp(from, to, value) {
10726
+ return Math.max(Math.min(to, value), from);
10727
+ }
10728
+
10729
+ /**
10730
+ * No option or placeholder option is selected
10731
+ */
10732
+ const DEFAULT_SELECTED_OPTION = null;
10733
+ function isPlaceholderOption(option) {
10734
+ return option === DEFAULT_SELECTED_OPTION || 'placeholder' in option;
10735
+ }
10736
+ function isSearchableOption(option) {
10737
+ return !isHeaderOption(option) && !isSeparatorOption(option) && !isPlaceholderOption(option);
10738
+ }
10739
+ const getUniqueIdForOption = (parentId = '', option) => {
10740
+ if (option == null) {
10741
+ return undefined;
10742
+ }
10743
+ const uniqueOptionId = option.value || (option.label?.replace(/\s/g, '') ?? '');
10744
+ return `option-${parentId}-${uniqueOptionId}`;
10745
+ };
10746
+
10747
+ /**
10748
+ * @deprecated Use `SelectInput` instead (https://neptune.wise.design/blog/2023-11-28-adopting-our-new-selectinput)
10749
+ */
10750
+ function Select({
10751
+ placeholder,
10752
+ id,
10753
+ required,
10754
+ disabled,
10755
+ inverse,
10756
+ dropdownWidth,
10757
+ size,
10758
+ block,
10759
+ selected,
10760
+ search,
10761
+ onChange,
10762
+ onFocus,
10763
+ onBlur,
10764
+ options: defaultOptions,
10765
+ onSearchChange,
10766
+ searchValue: initSearchValue,
10767
+ searchPlaceholder,
10768
+ // eslint-disable-next-line unicorn/prevent-abbreviations
10769
+ classNames: classNamesProp,
10770
+ dropdownUp,
10771
+ dropdownProps,
10772
+ buttonProps
10773
+ }) {
10774
+ const {
10775
+ formatMessage
10776
+ } = useIntl();
10777
+ const {
10778
+ isModern
10779
+ } = useTheme();
10780
+ const s = className => classNamesProp[className] || className;
10781
+ const [open, setOpen] = useState(false);
10782
+ const [searchValue, setSearchValue] = useState(DEFAULT_SEARCH_VALUE);
10783
+ const [keyboardFocusedOptionIndex, setKeyboardFocusedOptionIndex] = useState(null);
10784
+ const keyboardFocusedReference = useRef();
10785
+ const previousKeyboardFocusedOptionIndex = useRef();
10786
+ const [numberOfOptionsShown, setNumberOfOptionsShown] = useState(DEFAULT_OPTIONS_PAGE_SIZE);
10787
+ const searchBoxReference = useRef(null);
10788
+ const selectReference = useRef(null);
10789
+ const dropdownButtonReference = useRef(null);
10790
+ const optionsListReference = useRef(null);
10791
+ const isSearchEnabled = !!onSearchChange || !!search;
10792
+ const isDropdownAutoWidth = dropdownWidth == null;
10793
+ const fallbackButtonId = useMemo(() => getSimpleRandomId('np-select-'), []);
10794
+ const options = useMemo(() => {
10795
+ if (!search || !searchValue) {
10796
+ return defaultOptions;
10797
+ }
10798
+ return defaultOptions.filter(isSearchableOption).filter(option => {
10799
+ if (typeof search === 'function') {
10800
+ return search(option, searchValue);
10801
+ } else {
10802
+ return defaultFilterFunction(option, searchValue);
10803
+ }
10804
+ });
10805
+ }, [defaultOptions, search, searchValue]);
10806
+ const selectableOptions = useMemo(() => options.filter(isActionableOption), [options]);
10807
+ const focusedOption = selectableOptions[keyboardFocusedOptionIndex];
10808
+ const computedId = id || fallbackButtonId;
10809
+ const listboxId = `${computedId}-listbox`;
10810
+ const searchBoxId = `${computedId}-searchbox`;
10811
+ const {
10812
+ isMobile
10813
+ } = useLayout();
10814
+ useEffect(() => {
10815
+ let cancelled;
10816
+ if (keyboardFocusedOptionIndex >= 0) {
10817
+ requestAnimationFrame(() => {
10818
+ if (!cancelled) {
10819
+ if (isSearchEnabled) {
10820
+ keyboardFocusedReference.current?.scrollIntoView?.({
10821
+ block: 'center'
10822
+ });
10823
+ } else {
10824
+ keyboardFocusedReference.current?.focus();
10825
+ }
10826
+ }
10827
+ });
10828
+ return () => {
10829
+ cancelled = true;
10830
+ };
10831
+ }
10832
+ }, [keyboardFocusedOptionIndex, isSearchEnabled]);
10833
+ const handleOnClick = () => {
10834
+ setOpen(true);
10835
+ };
10836
+ const handleTouchStart = event => {
10837
+ if (event.currentTarget === event.target && open) {
10838
+ handleCloseOptions();
10839
+ }
10840
+ };
10841
+ const handleOnFocus = event => {
10842
+ if (onFocus) {
10843
+ onFocus(event);
10844
+ }
10845
+ };
10846
+ const handleOnBlur = event => {
10847
+ const {
10848
+ nativeEvent
10849
+ } = event;
10850
+ if (nativeEvent) {
10851
+ const elementReceivingFocus = nativeEvent.relatedTarget;
10852
+ const select = event.currentTarget;
10853
+ if (select && elementReceivingFocus && select.contains(elementReceivingFocus)) {
10854
+ return;
10855
+ }
10856
+ }
10857
+ if (onBlur) {
10858
+ onBlur(event);
10859
+ }
10860
+ };
10861
+ const handleSearchChange = event => {
10862
+ setNumberOfOptionsShown(DEFAULT_OPTIONS_PAGE_SIZE);
10863
+ setSearchValue(event.target.value);
10864
+ if (onSearchChange) {
10865
+ onSearchChange(event.target.value);
10866
+ }
10867
+ };
10868
+ const handleKeyDown = event => {
10869
+ switch (event.keyCode) {
10870
+ case KeyCodes.UP:
10871
+ case KeyCodes.DOWN:
10872
+ if (open) {
10873
+ moveFocusWithDifference(event.keyCode === KeyCodes.UP ? -1 : 1);
10874
+ } else {
10875
+ setOpen(true);
10876
+ }
10877
+ stopPropagation$1(event);
10878
+ break;
10879
+ case KeyCodes.SPACE:
10880
+ if (event.target !== searchBoxReference.current) {
10881
+ if (open) {
10882
+ selectKeyboardFocusedOption();
10883
+ } else {
10884
+ setOpen(true);
10885
+ }
10886
+ stopPropagation$1(event);
10887
+ }
10888
+ break;
10889
+ case KeyCodes.ENTER:
10890
+ if (open) {
10891
+ selectKeyboardFocusedOption();
10892
+ } else {
10893
+ setOpen(true);
10894
+ }
10895
+ stopPropagation$1(event);
10896
+ break;
10897
+ case KeyCodes.ESCAPE:
10898
+ handleCloseOptions();
10899
+ stopPropagation$1(event);
10900
+ break;
10901
+ case KeyCodes.TAB:
10902
+ if (open) {
10903
+ selectKeyboardFocusedOption();
10904
+ }
10905
+ break;
10906
+ }
10907
+ };
10908
+ function selectKeyboardFocusedOption() {
10909
+ if (keyboardFocusedOptionIndex != null) {
10910
+ selectableOptions.length > 0 && selectOption(selectableOptions[keyboardFocusedOptionIndex]);
10911
+ }
10912
+ }
10913
+ function moveFocusWithDifference(difference) {
10914
+ const selectedOptionIndex = selectableOptions.reduce((optionIndex, current, index) => {
10915
+ if (optionIndex != null) {
10916
+ return optionIndex;
10917
+ }
10918
+ if (isOptionSelected(selected, current)) {
10919
+ return index;
10920
+ }
10921
+ return null;
10922
+ }, null);
10923
+ const previousFocusedIndex = previousKeyboardFocusedOptionIndex.current ?? -1;
10924
+ let indexToStartMovingFrom = previousFocusedIndex;
10925
+ if (previousFocusedIndex === -1) {
10926
+ if (selectedOptionIndex == null) {
10927
+ setKeyboardFocusedOptionIndex(0);
10928
+ } else {
10929
+ indexToStartMovingFrom = selectedOptionIndex;
10930
+ }
10931
+ }
10932
+ const unClampedNewIndex = indexToStartMovingFrom + difference;
10933
+ const newIndex = clamp(0, selectableOptions.length - 1, unClampedNewIndex);
10934
+ setKeyboardFocusedOptionIndex(newIndex);
10935
+ }
10936
+ useEffect(() => {
10937
+ if (open) {
10938
+ if (!isMobile || searchValue) {
10939
+ if (isSearchEnabled && !!searchBoxReference.current) {
10940
+ searchBoxReference.current.focus();
10941
+ }
10942
+ if (!isSearchEnabled && optionsListReference.current && (previousKeyboardFocusedOptionIndex.current == null || Number.isNaN(previousKeyboardFocusedOptionIndex.current))) {
10943
+ optionsListReference.current.focus();
10944
+ }
10945
+ }
10946
+ previousKeyboardFocusedOptionIndex.current = keyboardFocusedOptionIndex;
10947
+ } else {
10948
+ previousKeyboardFocusedOptionIndex.current = null;
10949
+ }
10950
+ }, [open, searchValue, isSearchEnabled, isMobile, keyboardFocusedOptionIndex]);
10951
+ const handleCloseOptions = () => {
10952
+ setOpen(false);
10953
+ setKeyboardFocusedOptionIndex(null);
10954
+ if (dropdownButtonReference.current) {
10955
+ dropdownButtonReference.current.focus();
10956
+ }
10957
+ };
10958
+ function createSelectHandlerForOption(option) {
10959
+ return event => {
10960
+ stopPropagation$1(event);
10961
+ selectOption(option);
10962
+ };
10963
+ }
10964
+ function selectOption(option) {
10965
+ onChange(isPlaceholderOption(option) ? DEFAULT_SELECTED_OPTION : option);
10966
+ handleCloseOptions();
10967
+ }
10968
+ function renderOptionsList({
10969
+ className
10970
+ } = {}) {
10971
+ const dropdownClass = classNames(s('np-dropdown-menu'), {
10972
+ [s('np-dropdown-menu-desktop')]: !isMobile,
10973
+ [s(`np-dropdown-menu-${dropdownWidth}`)]: !isMobile && !isDropdownAutoWidth
10974
+ }, s(className));
10975
+ const showPlaceholder = !required && !isSearchEnabled && Boolean(placeholder);
10976
+ return /*#__PURE__*/jsxs("ul", {
10977
+ ref: optionsListReference,
10978
+ id: listboxId,
10979
+ role: "listbox",
10980
+ "aria-orientation": "vertical",
10981
+ "aria-activedescendant": getUniqueIdForOption(id, selected),
10982
+ tabIndex: "-1",
10983
+ className: dropdownClass,
10984
+ ...dropdownProps,
10985
+ children: [showPlaceholder && /*#__PURE__*/jsx(PlaceHolderOption, {}), isSearchEnabled && /*#__PURE__*/jsx(SearchBox$1, {
10986
+ ref: searchBoxReference,
10987
+ id: searchBoxId,
10988
+ classNames: classNamesProp,
10989
+ value: initSearchValue || searchValue,
10990
+ placeholder: searchPlaceholder || formatMessage(messages$2.searchPlaceholder),
10991
+ focusedOptionId: getUniqueIdForOption(id, focusedOption),
10992
+ onChange: handleSearchChange,
10993
+ onClick: stopPropagation$1
10994
+ }), options.slice(0, numberOfOptionsShown).map(renderOption), numberOfOptionsShown < options.length && /*#__PURE__*/jsx(ShowMoreOption, {})]
10995
+ });
10996
+ }
10997
+ function ShowMoreOption() {
10998
+ function handleOnClick(event) {
10999
+ stopPropagation$1(event);
11000
+ setNumberOfOptionsShown(numberOfOptionsShown + DEFAULT_OPTIONS_PAGE_SIZE);
11001
+ }
11002
+ return (
11003
+ /*#__PURE__*/
11004
+ /* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */
11005
+ jsx("li", {
11006
+ className: classNames(s('clickable'), s('border-bottom'), s('show-more')),
11007
+ onClick: handleOnClick,
11008
+ onKeyPress: handleOnClick,
11009
+ children: /*#__PURE__*/jsx("a", {
11010
+ children: "..."
11011
+ })
11012
+ })
11013
+ );
11014
+ }
11015
+ function PlaceHolderOption() {
11016
+ const placeholderOption = {
11017
+ placeholder
11018
+ };
11019
+ return (
11020
+ /*#__PURE__*/
11021
+ /* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */
11022
+ jsx("li", {
11023
+ className: classNames(s('clickable'), s('border-bottom')),
11024
+ onClick: createSelectHandlerForOption(placeholderOption),
11025
+ onKeyPress: createSelectHandlerForOption(placeholderOption),
11026
+ children: /*#__PURE__*/jsx("a", {
11027
+ children: placeholder
11028
+ })
11029
+ })
11030
+ );
11031
+ }
11032
+
11033
+ // eslint-disable-next-line react/prop-types
11034
+ function SeparatorOption() {
11035
+ return /*#__PURE__*/jsx("li", {
11036
+ className: s('np-separator'),
11037
+ "aria-hidden": true
11038
+ });
11039
+ }
11040
+
11041
+ // eslint-disable-next-line react/prop-types
11042
+ function HeaderOption({
11043
+ children
11044
+ }) {
11045
+ return /*#__PURE__*/jsx("li", {
11046
+ // eslint-disable-line jsx-a11y/no-noninteractive-element-interactions
11047
+ className: classNames(s('np-dropdown-header'), s('np-text-title-group')),
11048
+ onClick: stopPropagation$1,
11049
+ onKeyPress: stopPropagation$1,
11050
+ children: children
11051
+ });
11052
+ }
11053
+ function isOptionSelected(selected, option) {
11054
+ return selected?.value === option?.value;
11055
+ }
11056
+ const renderOption = (option, index) => {
11057
+ const separatorOption = option;
11058
+ if (isSeparatorOption(separatorOption) && separatorOption?.separator) {
11059
+ return /*#__PURE__*/jsx(SeparatorOption, {}, index);
11060
+ }
11061
+ const headerOption = option;
11062
+ if (isHeaderOption(headerOption) && headerOption.header) {
11063
+ return /*#__PURE__*/jsx(HeaderOption, {
11064
+ children: headerOption.header
11065
+ }, index);
11066
+ }
11067
+ const isActive = isOptionSelected(selected, option);
11068
+ const selectOption = option;
11069
+ const isFocusedWithKeyboard = !selectOption.disabled && keyboardFocusedOptionIndex === getIndexWithoutHeadersForIndexWithHeaders(index);
11070
+ const className = classNames(s('np-dropdown-item'), selectOption.disabled ? [s('disabled')] : s('clickable'), {
11071
+ [s('active')]: isActive,
11072
+ [s('np-dropdown-item--focused')]: isFocusedWithKeyboard
11073
+ });
11074
+ const handleOnClick = selectOption.disabled ? stopPropagation$1 : createSelectHandlerForOption(selectOption);
11075
+ return (
11076
+ /*#__PURE__*/
11077
+ /* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */
11078
+ jsx("li", {
11079
+ ref: isFocusedWithKeyboard ? keyboardFocusedReference : undefined,
11080
+ id: getUniqueIdForOption(id, option),
11081
+ "aria-selected": isActive,
11082
+ "aria-disabled": option.disabled,
11083
+ role: "option",
11084
+ tabIndex: "-1",
11085
+ className: className,
11086
+ onClick: handleOnClick,
11087
+ onKeyPress: handleOnClick,
11088
+ children: /*#__PURE__*/jsx("a", {
11089
+ disabled: selectOption.disabled,
11090
+ children: /*#__PURE__*/jsx(Option$1, {
11091
+ ...selectOption,
11092
+ classNames: classNamesProp
11093
+ })
11094
+ })
11095
+ }, index)
11096
+ );
11097
+ };
11098
+ function getIndexWithoutHeadersForIndexWithHeaders(index) {
11099
+ return options.reduce((sum, option, currentIndex) => {
11100
+ if (currentIndex < index && isActionableOption(option)) {
11101
+ return sum + 1;
11102
+ }
11103
+ return sum;
11104
+ }, 0);
11105
+ }
11106
+ const hasActiveOptions = !!defaultOptions.length;
11107
+ if (open && (initSearchValue || searchValue)) {
11108
+ if (hasActiveOptions && keyboardFocusedOptionIndex == null) {
11109
+ setKeyboardFocusedOptionIndex(0);
11110
+ }
11111
+ if (!hasActiveOptions && keyboardFocusedOptionIndex != null) {
11112
+ setKeyboardFocusedOptionIndex(null);
11113
+ }
11114
+ }
11115
+ return /*#__PURE__*/jsxs("div", {
11116
+ // eslint-disable-line jsx-a11y/no-static-element-interactions
11117
+ ref: selectReference,
11118
+ className: classNames(s('np-select'), block ? s('btn-block') : null, s('btn-group')),
11119
+ onKeyDown: handleKeyDown,
11120
+ onTouchMove: handleTouchStart,
11121
+ onFocus: handleOnFocus,
11122
+ onBlur: handleOnBlur,
11123
+ children: [/*#__PURE__*/jsxs(Button, {
11124
+ ref: dropdownButtonReference,
11125
+ id: computedId,
11126
+ block: block,
11127
+ size: size,
11128
+ htmlType: "button",
11129
+ className: classNames(s('np-dropdown-toggle'), s('np-text-body-large'), inverse ? s('np-dropdown-toggle-navy') : null)
11130
+ // reset Button's styles
11131
+ ,
11132
+ type: null,
11133
+ priority: null,
11134
+ disabled: disabled,
11135
+ "aria-controls": listboxId,
11136
+ "aria-expanded": open,
11137
+ "aria-autocomplete": "none",
11138
+ onClick: handleOnClick,
11139
+ ...buttonProps,
11140
+ children: [selected ? /*#__PURE__*/jsx(Option$1, {
11141
+ ...selected,
11142
+ classNames: classNamesProp,
11143
+ selected: true
11144
+ }) : /*#__PURE__*/jsx("span", {
11145
+ className: s('form-control-placeholder'),
11146
+ children: placeholder
11147
+ }), /*#__PURE__*/jsx(Chevron$1
11148
+ // disabled={disabled}
11149
+ , {
11150
+ className: classNames(s('tw-icon'), s('tw-chevron-up-icon'), s('tw-chevron'), s('bottom'), s('np-select-chevron'))
11151
+ })]
11152
+ }), isMobile ? isSearchEnabled ? /*#__PURE__*/jsx(Drawer$1, {
11153
+ open: open,
11154
+ headerTitle: searchPlaceholder || formatMessage(messages$2.searchPlaceholder),
11155
+ onClose: handleCloseOptions,
11156
+ children: renderOptionsList()
11157
+ }) : /*#__PURE__*/jsx(BottomSheet$2, {
11158
+ open: open,
11159
+ onClose: handleCloseOptions,
11160
+ children: renderOptionsList({
11161
+ className: isModern ? '' : 'p-a-1'
11162
+ })
11163
+ }) : /*#__PURE__*/jsx(Panel$1, {
11164
+ open: open,
11165
+ flip: false,
11166
+ altAxis: true,
11167
+ anchorRef: selectReference,
11168
+ anchorWidth: isDropdownAutoWidth,
11169
+ position: dropdownUp ? Position.TOP : Position.BOTTOM,
11170
+ onClose: handleCloseOptions,
11171
+ children: renderOptionsList({
11172
+ className: 'p-a-1'
11173
+ })
11174
+ })]
11175
+ });
11176
+ }
11177
+ Select.propTypes = {
11178
+ placeholder: PropTypes.string,
11010
11179
  id: PropTypes.string,
11011
- label: PropTypes.string.isRequired,
11012
- name: PropTypes.string.isRequired,
11180
+ required: PropTypes.bool,
11181
+ disabled: PropTypes.bool,
11182
+ inverse: PropTypes.bool,
11183
+ dropdownRight: PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl']),
11184
+ dropdownWidth: PropTypes.oneOf(['sm', 'md', 'lg']),
11185
+ size: PropTypes.oneOf(['sm', 'md', 'lg']),
11186
+ block: PropTypes.bool,
11187
+ selected: PropTypes.shape({
11188
+ value: PropTypes.any.isRequired,
11189
+ label: PropTypes.node,
11190
+ icon: PropTypes.node,
11191
+ currency: PropTypes.string,
11192
+ note: PropTypes.node,
11193
+ secondary: PropTypes.node
11194
+ }),
11195
+ /**
11196
+ * Search toggle
11197
+ * if `true` default search functionality being enabled (not case sensitive search in option labels & currency props)
11198
+ * if `function` you can define your own search function to implement custom search experience. This search function used while filtering the options array. The custom search function takes two parameters. First is the option the second is the keyword.
11199
+ */
11200
+ search: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
11013
11201
  onChange: PropTypes.func.isRequired,
11014
- secondary: PropTypes.string,
11015
- value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
11016
- className: PropTypes.string
11017
- };
11018
- Radio.defaultProps = {
11019
- avatar: undefined,
11020
- checked: false,
11021
- disabled: false,
11022
- id: null,
11023
- secondary: null,
11024
- value: '',
11025
- className: undefined
11026
- };
11027
- var Radio$1 = Radio;
11028
-
11029
- class RadioGroup extends Component {
11030
- constructor(props) {
11031
- super(props);
11032
- this.state = {
11033
- selectedValue: props.selectedValue
11034
- };
11035
- }
11036
- handleOnChange = selectedValue => {
11037
- const {
11038
- onChange
11039
- } = this.props;
11040
- this.setState({
11041
- selectedValue
11042
- }, onChange && onChange(selectedValue));
11043
- };
11044
- render() {
11045
- const {
11046
- radios,
11047
- name
11048
- } = this.props;
11049
- const {
11050
- selectedValue
11051
- } = this.state;
11052
- return radios && radios.length > 0 ? /*#__PURE__*/jsx(Fragment, {
11053
- children: radios.map(({
11054
- id,
11055
- avatar,
11056
- value,
11057
- label,
11058
- disabled,
11059
- secondary,
11060
- readOnly
11061
- }, index) => /*#__PURE__*/jsx(Radio$1
11062
- // eslint-disable-next-line react/no-array-index-key
11063
- , {
11064
- id: id,
11065
- value: value,
11066
- label: label,
11067
- name: name,
11068
- disabled: disabled,
11069
- checked: selectedValue === value,
11070
- secondary: secondary,
11071
- readOnly: readOnly,
11072
- avatar: avatar,
11073
- onChange: value_ => this.handleOnChange(value_)
11074
- }, index))
11075
- }) : null;
11076
- }
11077
- }
11078
- RadioGroup.propTypes = {
11079
- radios: PropTypes.arrayOf(PropTypes.shape({
11080
- id: PropTypes.string,
11081
- avatar: PropTypes.element,
11082
- value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
11083
- secondary: PropTypes.string,
11084
- label: PropTypes.string.isRequired,
11202
+ onFocus: PropTypes.func,
11203
+ onBlur: PropTypes.func,
11204
+ options: PropTypes.arrayOf(PropTypes.shape({
11205
+ value: PropTypes.any,
11206
+ label: PropTypes.node,
11207
+ header: PropTypes.node,
11208
+ icon: PropTypes.node,
11209
+ currency: PropTypes.string,
11210
+ note: PropTypes.node,
11211
+ secondary: PropTypes.node,
11212
+ separator: PropTypes.bool,
11085
11213
  disabled: PropTypes.bool,
11086
- readOnly: PropTypes.bool
11214
+ searchStrings: PropTypes.arrayOf(PropTypes.string)
11087
11215
  })).isRequired,
11088
- onChange: PropTypes.func.isRequired,
11089
- selectedValue: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
11090
- name: PropTypes.string.isRequired
11091
- };
11092
- RadioGroup.defaultProps = {
11093
- selectedValue: null
11094
- };
11095
- var RadioGroup$1 = RadioGroup;
11096
-
11097
- const RadioOption = ({
11098
- 'aria-label': ariaLabel,
11099
- media,
11100
- title,
11101
- content,
11102
- id,
11103
- name,
11104
- checked,
11105
- onChange,
11106
- complex,
11107
- disabled,
11108
- value,
11109
- showMediaCircle,
11110
- showMediaAtAllSizes,
11111
- isContainerAligned
11112
- }) => {
11113
- const sharedProps = {
11114
- 'aria-label': ariaLabel,
11115
- media,
11116
- title,
11117
- content,
11118
- name,
11119
- complex,
11120
- disabled,
11121
- showMediaCircle,
11122
- showMediaAtAllSizes,
11123
- isContainerAligned
11124
- };
11125
- return /*#__PURE__*/jsx(Option$2, {
11126
- ...sharedProps,
11127
- button: /*#__PURE__*/jsx(RadioButton$1, {
11128
- id: id,
11129
- name: name,
11130
- checked: checked,
11131
- disabled: disabled,
11132
- value: value,
11133
- onChange: onChange
11134
- })
11135
- });
11136
- };
11137
- RadioOption.propTypes = {
11138
- 'aria-label': PropTypes.string,
11139
- media: PropTypes.node,
11140
- id: PropTypes.string.isRequired,
11141
- name: PropTypes.string.isRequired,
11142
- title: PropTypes.node.isRequired,
11143
- content: PropTypes.node,
11144
- checked: PropTypes.bool,
11145
- onChange: PropTypes.func.isRequired,
11146
- complex: PropTypes.bool,
11147
- disabled: PropTypes.bool,
11148
- value: PropTypes.string,
11149
- showMediaCircle: PropTypes.bool,
11150
- showMediaAtAllSizes: PropTypes.bool,
11151
- isContainerAligned: PropTypes.bool
11216
+ /**
11217
+ * To have full control of your search value and response use `onSearchChange` function combined with `searchValue` and custom filtering on the options array.
11218
+ * DO NOT USE TOGETHER WITH `search` PROPERTY
11219
+ */
11220
+ onSearchChange: PropTypes.func,
11221
+ searchValue: PropTypes.string,
11222
+ searchPlaceholder: PropTypes.string,
11223
+ classNames: PropTypes.objectOf(PropTypes.string),
11224
+ dropdownUp: PropTypes.bool,
11225
+ buttonProps: PropTypes.object,
11226
+ dropdownProps: PropTypes.object
11152
11227
  };
11153
- RadioOption.defaultProps = {
11154
- 'aria-label': undefined,
11155
- media: null,
11156
- content: null,
11157
- checked: false,
11158
- complex: false,
11228
+ Select.defaultProps = {
11229
+ id: undefined,
11230
+ placeholder: undefined,
11231
+ size: 'md',
11232
+ dropdownRight: null,
11233
+ dropdownWidth: null,
11234
+ inverse: false,
11235
+ required: false,
11159
11236
  disabled: false,
11160
- showMediaCircle: true,
11161
- showMediaAtAllSizes: false,
11162
- isContainerAligned: false,
11163
- value: ''
11164
- };
11165
- var RadioOption$1 = RadioOption;
11166
-
11167
- const Section = ({
11168
- children,
11169
- className,
11170
- withHorizontalPadding = false
11171
- }) => {
11172
- return /*#__PURE__*/jsx("div", {
11173
- className: classNames('np-section', className, {
11174
- 'np-section--with-horizontal-padding': withHorizontalPadding
11175
- }),
11176
- children: children
11177
- });
11237
+ block: true,
11238
+ selected: null,
11239
+ onFocus: null,
11240
+ onBlur: null,
11241
+ onSearchChange: undefined,
11242
+ search: false,
11243
+ searchValue: '',
11244
+ searchPlaceholder: undefined,
11245
+ classNames: {},
11246
+ dropdownUp: false,
11247
+ buttonProps: {},
11248
+ dropdownProps: {}
11178
11249
  };
11179
11250
 
11180
11251
  const CSS_TRANSITION_DURATION = 400;
@@ -15512,5 +15583,5 @@ const translations = {
15512
15583
  'zh-HK': zhHK
15513
15584
  };
15514
15585
 
15515
- export { Accordion, ActionButton, ActionOption, Alert$1 as Alert, ArrowPosition as AlertArrowPosition, Avatar, AvatarType, AvatarWrapper, Badge, Card as BaseCard, Body, BottomSheet$2 as BottomSheet, Breakpoint, Button, Card$2 as Card, Checkbox$1 as Checkbox, CheckboxButton$1 as CheckboxButton, CheckboxOption, Chevron$1 as Chevron, Chip, Chips, CircularButton$1 as CircularButton, ControlType, CriticalCommsBanner, DEFAULT_LANG, DEFAULT_LOCALE, DateInput$1 as DateInput, DateLookup$1 as DateLookup, DateMode, Decision$1 as Decision, Presentation as DecisionPresentation, Type as DecisionType, DefinitionList$1 as DefinitionList, Dimmer$1 as Dimmer, Direction, DirectionProvider, Display, Drawer$1 as Drawer, DropFade, DynamicFieldDefinitionList$1 as DynamicFieldDefinitionList, Emphasis, FileType, FlowNavigation, Header, Image, Info, InfoPresentation, InlineAlert, Input, InputGroup, InputWithDisplayFormat$1 as InputWithDisplayFormat, InstructionsList$1 as InstructionsList, LanguageProvider, Layout$1 as Layout, Link, ListItem$1 as ListItem, Loader$1 as Loader, Logo$1 as Logo, LogoType, Markdown$1 as Markdown, MarkdownNodeType, Modal, Money$1 as Money, MoneyInput$1 as MoneyInput, MonthFormat, NavigationOption, NavigationOptionList$1 as NavigationOptionsList, Nudge, Option$2 as Option, OverlayHeader$1 as OverlayHeader, PhoneNumberInput$1 as PhoneNumberInput, Popover$2 as Popover, Position, Priority, ProcessIndicator$1 as ProcessIndicator, ProfileType, Progress, ProgressBar, PromoCard$1 as PromoCard, PromoCard$1 as PromoCardGroup, Provider$1 as Provider, RTL_LANGUAGES, Radio$1 as Radio, RadioGroup$1 as RadioGroup, RadioOption$1 as RadioOption, SUPPORTED_LANGUAGES, Scroll, SearchInput, Section, Select, SelectInput, SelectInputOptionContent, SelectInputTriggerButton, Sentiment, Size, SlidingPanel$1 as SlidingPanel, SnackbarConsumer, SnackbarContext, SnackbarPortal, SnackbarProvider$1 as SnackbarProvider, Status, StatusIcon, Stepper, Sticky$1 as Sticky, Summary, Switch, SwitchOption, Tabs$1 as Tabs, TextArea, TextareaWithDisplayFormat$1 as TextareaWithDisplayFormat, Theme, Title, Tooltip$1 as Tooltip, Type$1 as Type, Typeahead, Typography, Upload$1 as Upload, UploadInput, UploadStep, Variant, Width, adjustLocale, getCountryFromLocale, getDirectionFromLocale, getLangFromLocale, isBrowser, isServerSide, translations, useDirection, useLayout, useScreenSize, useSnackbar };
15586
+ export { Accordion, ActionButton, ActionOption, Alert$1 as Alert, ArrowPosition as AlertArrowPosition, Avatar, AvatarType, AvatarWrapper, Badge, Card as BaseCard, Body, BottomSheet$2 as BottomSheet, Breakpoint, Button, Card$2 as Card, Checkbox$1 as Checkbox, CheckboxButton$1 as CheckboxButton, CheckboxOption, Chevron$1 as Chevron, Chip, Chips, CircularButton$1 as CircularButton, ControlType, CriticalCommsBanner, DEFAULT_LANG, DEFAULT_LOCALE, DateInput$1 as DateInput, DateLookup$1 as DateLookup, DateMode, Decision$1 as Decision, Presentation as DecisionPresentation, Type as DecisionType, DefinitionList$1 as DefinitionList, Dimmer$1 as Dimmer, Direction, DirectionProvider, Display, Drawer$1 as Drawer, DropFade, DynamicFieldDefinitionList$1 as DynamicFieldDefinitionList, Emphasis, FileType, FlowNavigation, Header, Image, Info, InfoPresentation, InlineAlert, Input, InputGroup, InputWithDisplayFormat$1 as InputWithDisplayFormat, InstructionsList$1 as InstructionsList, LanguageProvider, Layout$1 as Layout, Link, ListItem$1 as ListItem, Loader$1 as Loader, Logo$1 as Logo, LogoType, Markdown$1 as Markdown, MarkdownNodeType, Modal, Money$1 as Money, MoneyInput$1 as MoneyInput, MonthFormat, NavigationOption, NavigationOptionList$1 as NavigationOptionsList, Nudge, Option$2 as Option, OverlayHeader$1 as OverlayHeader, PhoneNumberInput$1 as PhoneNumberInput, Popover$2 as Popover, Position, Priority, ProcessIndicator$1 as ProcessIndicator, ProfileType, Progress, ProgressBar, PromoCard$1 as PromoCard, PromoCard$1 as PromoCardGroup, Provider$1 as Provider, RTL_LANGUAGES, Radio$1 as Radio, RadioGroup$1 as RadioGroup, RadioOption$1 as RadioOption, SUPPORTED_LANGUAGES, Scroll, SearchInput, Section, Select, SelectInput, SelectInputOptionContent, SelectInputTriggerButton, Sentiment, Size, SlidingPanel$1 as SlidingPanel, SnackbarConsumer, SnackbarContext, SnackbarPortal, SnackbarProvider$1 as SnackbarProvider, Status, StatusIcon, Stepper, Sticky$1 as Sticky, Summary, Switch, SwitchOption, Tabs$1 as Tabs, TextArea, TextareaWithDisplayFormat$1 as TextareaWithDisplayFormat, Theme, Title, Tooltip$1 as Tooltip, Type$1 as Type, Typeahead, Typography, Upload$1 as Upload, UploadInput, UploadStep, Variant, Width, adjustLocale, getCountryFromLocale, getDirectionFromLocale, getLangFromLocale, isBrowser, isServerSide, translations, useDirection, useLayout, useSnackbar };
15516
15587
  //# sourceMappingURL=index.esm.js.map