@transferwise/components 0.0.0-experimental-9a61b0a → 0.0.0-experimental-73858ea

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.
@@ -1,7 +1,7 @@
1
1
  import classNames from 'classnames';
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
3
  import * as React from 'react';
4
- import React__default, { forwardRef, cloneElement, useState, useRef, useMemo, useEffect, useLayoutEffect, createContext, useContext, useCallback, PureComponent, createRef, Component, Children, Fragment as Fragment$1 } from 'react';
4
+ import React__default, { forwardRef, cloneElement, useState, useRef, useMemo, useEffect, useCallback, useLayoutEffect, createContext, useContext, PureComponent, createRef, Component, Children, Fragment as Fragment$1 } from 'react';
5
5
  import { ChevronUp, CrossCircleFill, Cross, NavigateAway, Check, Info as Info$1, Alert as Alert$2, ClockBorderless, CheckCircle, InfoCircle, Warning, CrossCircle, Clock, Briefcase, Person, ArrowLeft, QuestionMarkCircle, AlertCircle, Search, ChevronDown, CheckCircleFill, ArrowRight, Download, ClockFill, Upload as Upload$2, Document, Plus, PlusCircle, AlertCircleFill } from '@transferwise/icons';
6
6
  import PropTypes from 'prop-types';
7
7
  import { defineMessages, useIntl, injectIntl, IntlProvider } from 'react-intl';
@@ -10,8 +10,9 @@ import { formatDate, formatNumber, formatMoney, formatAmount } from '@transferwi
10
10
  import throttle from 'lodash.throttle';
11
11
  import { CSSTransition } from 'react-transition-group';
12
12
  import { createPortal } from 'react-dom';
13
- import { isUndefined, isKey, isNumber, isEmpty, isNull, isArray } from '@transferwise/neptune-validation';
13
+ import { FocusScope } from '@react-aria/focus';
14
14
  import mergeRefs from 'react-merge-refs';
15
+ import { isUndefined, isKey, isNumber, isEmpty, isNull, isArray } from '@transferwise/neptune-validation';
15
16
  import { usePopper } from 'react-popper';
16
17
  import { Transition, Listbox } from '@headlessui/react';
17
18
  import { useId } from '@radix-ui/react-id';
@@ -1457,268 +1458,23 @@ function getInitials(name) {
1457
1458
  return allInitials[0] + allInitials.slice(-1);
1458
1459
  }
1459
1460
 
1460
- const THROTTLE_MS = 100;
1461
- const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
1462
- const useClientWidth = ({
1463
- ref,
1464
- throttleMs = THROTTLE_MS
1465
- }) => {
1466
- const [clientWidth, setClientWidth] = useState(null);
1467
- useIsomorphicLayoutEffect(() => {
1468
- // eslint-disable-next-line unicorn/consistent-function-scoping
1469
- const updateClientWidth = () => {
1470
- if (ref) {
1471
- // when `ref` is a window
1472
- if ('innerWidth' in ref) {
1473
- setClientWidth(ref.innerWidth);
1474
- }
1475
- // when `ref` is an element
1476
- else if (ref.current) {
1477
- setClientWidth(ref.current.clientWidth);
1478
- }
1479
- }
1480
- };
1481
- // This assignment saves a reference to the function so it will be the same passed to both addEventListener removeEventListener.
1482
- // If throttle gets passed directly to both add and removeEventListenet the results will be that the event
1483
- // won't get removed even if the component is unmounted.
1484
- const attachedFunction = throttle(updateClientWidth, throttleMs);
1485
- window.addEventListener('resize', attachedFunction, true);
1486
- // using requestAnimationFrame to perform the calculation before the next repaint
1487
- // getting width earlier causes issues in animations when used with react-transition-group
1488
- window.requestAnimationFrame(updateClientWidth);
1489
- return () => window.removeEventListener('resize', attachedFunction, true);
1490
- }, []);
1491
- return [clientWidth];
1492
- };
1493
- useClientWidth.THROTTLE_MS = THROTTLE_MS;
1494
-
1495
- const useConditionalListener = ({
1496
- attachListener,
1497
- callback,
1498
- eventType,
1499
- parent
1500
- }) => {
1501
- useEffect(() => {
1502
- if (attachListener && !isUndefined(parent)) {
1503
- parent.addEventListener(eventType, callback, true);
1504
- }
1505
- return () => {
1506
- if (!isUndefined(parent)) {
1507
- parent.removeEventListener(eventType, callback, true);
1508
- }
1509
- };
1510
- }, [attachListener, callback, eventType, parent]);
1511
- };
1512
-
1513
- const DirectionContext = /*#__PURE__*/createContext(Direction.LTR);
1514
- const DirectionProvider = ({
1515
- direction,
1516
- children
1517
- }) => {
1518
- return /*#__PURE__*/jsx(DirectionContext.Provider, {
1519
- value: direction,
1520
- children: children
1521
- });
1522
- };
1523
-
1524
- const useDirection = () => {
1525
- const direction = useContext(DirectionContext);
1526
- return {
1527
- direction,
1528
- isRTL: direction === 'rtl'
1529
- };
1530
- };
1531
-
1532
- const ObserverParams = {
1533
- threshold: 0.1
1534
- };
1535
-
1536
- /**
1537
- * useHasIntersected.
1538
- * Use this custom hook to detect when an element has became visible inside the viewport. This hook checks only if the intersection happend.
1539
- * Once the intersection has happened the hook will not return false even if the element gets out of the viewport.
1540
- *
1541
- * @param elRef.elRef
1542
- * @param {object} [elRef] - node object that contains a react reference to the element that needs to be observed.
1543
- * @param {strimng} [loading = 'eager'] - string that contains the type of loading.
1544
- * @param elRef.loading
1545
- * @usage `const [hasIntersected] = useHasIntersected({imageRef,loading});`
1546
- */
1547
- const useHasIntersected = ({
1548
- elRef,
1549
- loading
1550
- }) => {
1551
- const [hasIntersected, setHasIntersected] = useState(false);
1552
- const {
1553
- current
1554
- } = elRef || {};
1555
- const isValidReference = () => {
1556
- return elRef && current;
1557
- };
1558
- const handleOnIntersect = (entries, observer) => {
1559
- entries.forEach(entry => {
1560
- if (entry.isIntersecting) {
1561
- setHasIntersected(true);
1562
- observer.unobserve(current);
1563
- }
1564
- });
1565
- };
1566
- useEffect(() => {
1567
- let observer;
1568
- let didCancel = false;
1569
-
1570
- // Check if window is define for SSR and Old browsers fallback
1571
- if (typeof window === 'undefined' || !window.IntersectionObserver || !isValidReference()) {
1572
- setHasIntersected(true);
1573
- } else if (!didCancel) {
1574
- observer = new IntersectionObserver(handleOnIntersect, ObserverParams);
1575
- observer.observe(current);
1576
- }
1577
- return () => {
1578
- didCancel = true;
1579
- if (observer) {
1580
- observer.unobserve(current);
1581
- }
1582
- };
1583
- }, [elRef]);
1584
- if (loading === 'eager') {
1585
- return [false];
1586
- }
1587
- return [hasIntersected];
1588
- };
1589
-
1590
- const useLayout = () => {
1591
- const windowReference = typeof window === 'undefined' ? undefined : window;
1592
- const [breakpoint, setBreakpoint] = useState();
1593
- const [clientWidth] = useClientWidth({
1594
- ref: windowReference
1595
- });
1596
- useEffect(() => {
1597
- if (!clientWidth) {
1598
- return;
1599
- }
1600
- if (clientWidth <= Breakpoint.EXTRA_SMALL) {
1601
- setBreakpoint(Breakpoint.EXTRA_SMALL);
1602
- return;
1603
- }
1604
- if (Breakpoint.EXTRA_SMALL < clientWidth && clientWidth <= Breakpoint.SMALL) {
1605
- setBreakpoint(Breakpoint.SMALL);
1606
- return;
1607
- }
1608
- if (Breakpoint.SMALL < clientWidth && clientWidth <= Breakpoint.MEDIUM) {
1609
- setBreakpoint(Breakpoint.MEDIUM);
1610
- return;
1611
- }
1612
- if (Breakpoint.MEDIUM < clientWidth && clientWidth <= Breakpoint.LARGE) {
1613
- setBreakpoint(Breakpoint.LARGE);
1614
- return;
1615
- }
1616
- if (Breakpoint.LARGE < clientWidth) {
1617
- setBreakpoint(Breakpoint.EXTRA_LARGE);
1618
- }
1619
- }, [clientWidth]);
1620
- return {
1621
- isMobile: !!breakpoint && [Breakpoint.EXTRA_SMALL, Breakpoint.SMALL].includes(breakpoint),
1622
- isExtraSmall: breakpoint === Breakpoint.EXTRA_SMALL,
1623
- isSmall: breakpoint === Breakpoint.SMALL,
1624
- isMedium: breakpoint === Breakpoint.MEDIUM,
1625
- isLarge: breakpoint === Breakpoint.LARGE,
1626
- isExtraLarge: breakpoint === Breakpoint.EXTRA_LARGE
1627
- };
1628
- };
1629
-
1630
- /**
1631
- * This function returns the first and the last focusable elements within a node.
1632
- *
1633
- * @param {Node} focusBoundaryContainer - the area that contains focused elements.
1634
- * @returns {object} focusableEls - which contains the first focusable element and the last focusable element. First and last can be the same element if area contains one or none focusable element.
1635
- */
1636
-
1637
- const getFocusableElements = focusBoundaryContainer => {
1638
- const focusableEls = {
1639
- first: focusBoundaryContainer,
1640
- last: focusBoundaryContainer
1641
- };
1642
- if (focusBoundaryContainer?.querySelectorAll) {
1643
- const allEls = [...focusBoundaryContainer.querySelectorAll('a, button, input, textarea, select, details,[tabindex]:not([tabindex="-1"])')].filter(element => !element.hasAttribute('disabled'));
1644
- if (allEls.length > 0) {
1645
- [focusableEls.first] = allEls;
1646
- focusableEls.last = allEls[allEls.length - 1];
1647
- }
1648
- }
1649
- return focusableEls;
1650
- };
1651
-
1652
- /**
1653
- * This function resets the focus to either last of first focusable elements within a node.
1654
- *
1655
- * @param {object} focusableEls - contains the first last of first focusable elements within a node.
1656
- * @param {object} event - the triggered event
1657
- */
1658
-
1659
- const resetFocus = ({
1660
- focusableEls: {
1661
- first,
1662
- last
1663
- },
1664
- event
1665
- }) => {
1666
- const {
1667
- activeElement
1668
- } = document;
1669
- if (event.shiftKey && activeElement === first) {
1670
- if (last) {
1671
- last.focus();
1672
- }
1673
- event.preventDefault();
1674
- }
1675
- if (!event.shiftKey && activeElement === last) {
1676
- if (first) {
1677
- first.focus();
1678
- }
1679
- event.preventDefault();
1680
- }
1681
- };
1682
-
1683
- const {
1684
- TAB
1685
- } = Key;
1686
1461
  const FocusBoundary = ({
1687
1462
  children
1688
1463
  }) => {
1689
- const boundaryReference = useRef(null);
1690
- const parent = isUndefined(document) ? undefined : document;
1691
- const [focusableEls, setFocusableEls] = useState({});
1464
+ const wrapperReference = useRef(null);
1692
1465
  useEffect(() => {
1693
- if (boundaryReference?.current) {
1694
- boundaryReference.current.focus({
1695
- preventScroll: true
1696
- });
1697
- setFocusableEls(getFocusableElements(boundaryReference.current));
1698
- }
1466
+ wrapperReference.current?.focus({
1467
+ preventScroll: true
1468
+ });
1699
1469
  }, []);
1700
- // If event type is Tab the resetFocus will force the focus to either the first focusable or last in boundaryRef .
1701
- useConditionalListener({
1702
- eventType: 'keydown',
1703
- callback: event => {
1704
- if (isKey({
1705
- keyType: TAB,
1706
- event
1707
- })) {
1708
- resetFocus({
1709
- event,
1710
- focusableEls
1711
- });
1712
- }
1713
- },
1714
- attachListener: true,
1715
- parent
1716
- });
1717
1470
  return /*#__PURE__*/jsx("div", {
1718
- ref: boundaryReference,
1471
+ ref: wrapperReference,
1719
1472
  tabIndex: -1,
1720
- className: "np-focus-boundary outline-none",
1721
- children: children
1473
+ children: /*#__PURE__*/jsx(FocusScope, {
1474
+ contain: true,
1475
+ restoreFocus: true,
1476
+ children: children
1477
+ })
1722
1478
  });
1723
1479
  };
1724
1480
  var FocusBoundary$1 = FocusBoundary;
@@ -1974,6 +1730,176 @@ SlidingPanel.defaultProps = {
1974
1730
  };
1975
1731
  var SlidingPanel$1 = SlidingPanel;
1976
1732
 
1733
+ const THROTTLE_MS = 100;
1734
+ const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
1735
+ const useClientWidth = ({
1736
+ ref,
1737
+ throttleMs = THROTTLE_MS
1738
+ }) => {
1739
+ const [clientWidth, setClientWidth] = useState(null);
1740
+ useIsomorphicLayoutEffect(() => {
1741
+ // eslint-disable-next-line unicorn/consistent-function-scoping
1742
+ const updateClientWidth = () => {
1743
+ if (ref) {
1744
+ // when `ref` is a window
1745
+ if ('innerWidth' in ref) {
1746
+ setClientWidth(ref.innerWidth);
1747
+ }
1748
+ // when `ref` is an element
1749
+ else if (ref.current) {
1750
+ setClientWidth(ref.current.clientWidth);
1751
+ }
1752
+ }
1753
+ };
1754
+ // This assignment saves a reference to the function so it will be the same passed to both addEventListener removeEventListener.
1755
+ // If throttle gets passed directly to both add and removeEventListenet the results will be that the event
1756
+ // won't get removed even if the component is unmounted.
1757
+ const attachedFunction = throttle(updateClientWidth, throttleMs);
1758
+ window.addEventListener('resize', attachedFunction, true);
1759
+ // using requestAnimationFrame to perform the calculation before the next repaint
1760
+ // getting width earlier causes issues in animations when used with react-transition-group
1761
+ window.requestAnimationFrame(updateClientWidth);
1762
+ return () => window.removeEventListener('resize', attachedFunction, true);
1763
+ }, []);
1764
+ return [clientWidth];
1765
+ };
1766
+ useClientWidth.THROTTLE_MS = THROTTLE_MS;
1767
+
1768
+ const useConditionalListener = ({
1769
+ attachListener,
1770
+ callback,
1771
+ eventType,
1772
+ parent
1773
+ }) => {
1774
+ useEffect(() => {
1775
+ if (attachListener && !isUndefined(parent)) {
1776
+ parent.addEventListener(eventType, callback, true);
1777
+ }
1778
+ return () => {
1779
+ if (!isUndefined(parent)) {
1780
+ parent.removeEventListener(eventType, callback, true);
1781
+ }
1782
+ };
1783
+ }, [attachListener, callback, eventType, parent]);
1784
+ };
1785
+
1786
+ const DirectionContext = /*#__PURE__*/createContext(Direction.LTR);
1787
+ const DirectionProvider = ({
1788
+ direction,
1789
+ children
1790
+ }) => {
1791
+ return /*#__PURE__*/jsx(DirectionContext.Provider, {
1792
+ value: direction,
1793
+ children: children
1794
+ });
1795
+ };
1796
+
1797
+ const useDirection = () => {
1798
+ const direction = useContext(DirectionContext);
1799
+ return {
1800
+ direction,
1801
+ isRTL: direction === 'rtl'
1802
+ };
1803
+ };
1804
+
1805
+ const ObserverParams = {
1806
+ threshold: 0.1
1807
+ };
1808
+
1809
+ /**
1810
+ * useHasIntersected.
1811
+ * Use this custom hook to detect when an element has became visible inside the viewport. This hook checks only if the intersection happend.
1812
+ * Once the intersection has happened the hook will not return false even if the element gets out of the viewport.
1813
+ *
1814
+ * @param elRef.elRef
1815
+ * @param {object} [elRef] - node object that contains a react reference to the element that needs to be observed.
1816
+ * @param {strimng} [loading = 'eager'] - string that contains the type of loading.
1817
+ * @param elRef.loading
1818
+ * @usage `const [hasIntersected] = useHasIntersected({imageRef,loading});`
1819
+ */
1820
+ const useHasIntersected = ({
1821
+ elRef,
1822
+ loading
1823
+ }) => {
1824
+ const [hasIntersected, setHasIntersected] = useState(false);
1825
+ const {
1826
+ current
1827
+ } = elRef || {};
1828
+ const isValidReference = () => {
1829
+ return elRef && current;
1830
+ };
1831
+ const handleOnIntersect = (entries, observer) => {
1832
+ entries.forEach(entry => {
1833
+ if (entry.isIntersecting) {
1834
+ setHasIntersected(true);
1835
+ observer.unobserve(current);
1836
+ }
1837
+ });
1838
+ };
1839
+ useEffect(() => {
1840
+ let observer;
1841
+ let didCancel = false;
1842
+
1843
+ // Check if window is define for SSR and Old browsers fallback
1844
+ if (typeof window === 'undefined' || !window.IntersectionObserver || !isValidReference()) {
1845
+ setHasIntersected(true);
1846
+ } else if (!didCancel) {
1847
+ observer = new IntersectionObserver(handleOnIntersect, ObserverParams);
1848
+ observer.observe(current);
1849
+ }
1850
+ return () => {
1851
+ didCancel = true;
1852
+ if (observer) {
1853
+ observer.unobserve(current);
1854
+ }
1855
+ };
1856
+ }, [elRef]);
1857
+ if (loading === 'eager') {
1858
+ return [false];
1859
+ }
1860
+ return [hasIntersected];
1861
+ };
1862
+
1863
+ const useLayout = () => {
1864
+ const windowReference = typeof window === 'undefined' ? undefined : window;
1865
+ const [breakpoint, setBreakpoint] = useState();
1866
+ const [clientWidth] = useClientWidth({
1867
+ ref: windowReference
1868
+ });
1869
+ useEffect(() => {
1870
+ if (!clientWidth) {
1871
+ return;
1872
+ }
1873
+ if (clientWidth <= Breakpoint.EXTRA_SMALL) {
1874
+ setBreakpoint(Breakpoint.EXTRA_SMALL);
1875
+ return;
1876
+ }
1877
+ if (Breakpoint.EXTRA_SMALL < clientWidth && clientWidth <= Breakpoint.SMALL) {
1878
+ setBreakpoint(Breakpoint.SMALL);
1879
+ return;
1880
+ }
1881
+ if (Breakpoint.SMALL < clientWidth && clientWidth <= Breakpoint.MEDIUM) {
1882
+ setBreakpoint(Breakpoint.MEDIUM);
1883
+ return;
1884
+ }
1885
+ if (Breakpoint.MEDIUM < clientWidth && clientWidth <= Breakpoint.LARGE) {
1886
+ setBreakpoint(Breakpoint.LARGE);
1887
+ return;
1888
+ }
1889
+ if (Breakpoint.LARGE < clientWidth) {
1890
+ setBreakpoint(Breakpoint.EXTRA_LARGE);
1891
+ }
1892
+ }, [clientWidth]);
1893
+ return {
1894
+ isMobile: !!breakpoint && [Breakpoint.EXTRA_SMALL, Breakpoint.SMALL].includes(breakpoint),
1895
+ isExtraSmall: breakpoint === Breakpoint.EXTRA_SMALL,
1896
+ isSmall: breakpoint === Breakpoint.SMALL,
1897
+ isMedium: breakpoint === Breakpoint.MEDIUM,
1898
+ isLarge: breakpoint === Breakpoint.LARGE,
1899
+ isExtraLarge: breakpoint === Breakpoint.EXTRA_LARGE
1900
+ };
1901
+ };
1902
+
1977
1903
  const INITIAL_Y_POSITION = 0;
1978
1904
  const CONTENT_SCROLL_THRESHOLD = 1;
1979
1905
  const MOVE_OFFSET_THRESHOLD = 50;