@transferwise/components 0.0.0-experimental-a213c0c → 0.0.0-experimental-ac06ea4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.mjs CHANGED
@@ -1,25 +1,25 @@
1
1
  import * as React from 'react';
2
- import React__default, { forwardRef, cloneElement, useState, useRef, useEffect, useCallback, Component, createContext, useContext, useImperativeHandle, createElement, useMemo, PureComponent, createRef, isValidElement, Children, Fragment as Fragment$1 } from 'react';
2
+ import React__default, { forwardRef, cloneElement, useState, useEffect, useRef, useMemo, Component, useCallback, createContext, useContext, useImperativeHandle, createElement, PureComponent, createRef, isValidElement, Children, Fragment as Fragment$1 } from 'react';
3
3
  import { useId } from '@radix-ui/react-id';
4
4
  import classNames from 'classnames';
5
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
- import { ChevronUp, NavigateAway, CrossCircleFill, Cross, Card as Card$3, Bank, BankTransfer, Check, Info as Info$1, Alert as Alert$1, ClockBorderless, Briefcase, Person, AlertCircleFill, ArrowLeft, AlertCircle, QuestionMarkCircle, Search, CrossCircle, ChevronDown, CheckCircleFill, ArrowRight, Download, ClockFill, Upload as Upload$2, Document, Plus, PlusCircle } from '@transferwise/icons';
7
- import { usePopper } from 'react-popper';
6
+ import { ChevronUp, CrossCircleFill, Cross, NavigateAway, Check, Info as Info$1, Alert as Alert$1, ClockBorderless, Briefcase, Person, AlertCircleFill, ArrowLeft, AlertCircle, QuestionMarkCircle, Search, CrossCircle, ChevronDown, CheckCircleFill, ArrowRight, Download, ClockFill, Upload as Upload$2, Document, Plus, PlusCircle } from '@transferwise/icons';
7
+ import { defineMessages, useIntl, injectIntl, IntlProvider } from 'react-intl';
8
+ import PropTypes from 'prop-types';
9
+ import commonmark from 'commonmark';
8
10
  import { useTheme, ThemeProvider } from '@wise/components-theming';
9
11
  import { CSSTransition } from 'react-transition-group';
10
12
  import { FocusScope } from '@react-aria/focus';
11
13
  import { createPortal } from 'react-dom';
12
- import { defineMessages, useIntl, injectIntl, IntlProvider } from 'react-intl';
13
- import PropTypes from 'prop-types';
14
- import { Flag, Illustration } from '@wise/art';
15
14
  import { useSyncExternalStore } from 'use-sync-external-store/shim/index.js';
16
15
  import { isUndefined, isNumber, isEmpty, isNull } from '@transferwise/neptune-validation';
17
- import commonmark from 'commonmark';
18
16
  import { formatDate, formatMoney, formatAmount } from '@transferwise/formatting';
19
17
  import { Transition, Listbox } from '@headlessui/react';
20
18
  import mergeProps from 'merge-props';
21
19
  import { useFloating, useDismiss, useRole, useInteractions, FloatingPortal, FloatingFocusManager, offset, flip, shift, size, autoUpdate } from '@floating-ui/react';
22
20
  import { usePreventScroll } from '@react-aria/overlays';
21
+ import { usePopper } from 'react-popper';
22
+ import { Flag, Illustration } from '@wise/art';
23
23
  import clamp$2 from 'lodash.clamp';
24
24
  import debounce from 'lodash.debounce';
25
25
  import requiredIf from 'react-required-if';
@@ -670,383 +670,590 @@ const ActionOption = ({
670
670
  });
671
671
  };
672
672
 
673
- const FocusBoundary = ({
674
- children
673
+ var messages$d = defineMessages({
674
+ ariaLabel: {
675
+ id: "neptune.CloseButton.ariaLabel"
676
+ }
677
+ });
678
+
679
+ const CloseButton = /*#__PURE__*/forwardRef(function CloseButton({
680
+ 'aria-label': ariaLabel,
681
+ size = Size.MEDIUM,
682
+ filled = false,
683
+ className,
684
+ onClick,
685
+ isDisabled,
686
+ testId
687
+ }, reference) {
688
+ const intl = useIntl();
689
+ ariaLabel ??= intl.formatMessage(messages$d.ariaLabel);
690
+ const Icon = filled ? CrossCircleFill : Cross;
691
+ return /*#__PURE__*/jsx("button", {
692
+ ref: reference,
693
+ type: "button",
694
+ className: classNames('np-close-button', 'close btn-link', 'text-no-decoration', {
695
+ 'np-close-button--large': size === Size.MEDIUM,
696
+ 'np-close-button--x-large': size === Size.LARGE
697
+ }, className),
698
+ "aria-label": ariaLabel,
699
+ "aria-disabled": isDisabled,
700
+ disabled: isDisabled,
701
+ "data-testid": testId,
702
+ onClick: onClick,
703
+ children: /*#__PURE__*/jsx(Icon, {
704
+ size: size === Size.SMALL ? 16 : 24
705
+ })
706
+ });
707
+ });
708
+
709
+ var messages$c = defineMessages({
710
+ opensInNewTab: {
711
+ id: "neptune.Link.opensInNewTab"
712
+ }
713
+ });
714
+
715
+ const Link = ({
716
+ className,
717
+ children,
718
+ href,
719
+ target,
720
+ type,
721
+ 'aria-label': ariaLabel,
722
+ onClick,
723
+ ...props
675
724
  }) => {
676
- const wrapperReference = useRef(null);
677
- useEffect(() => {
678
- wrapperReference.current?.focus({
679
- preventScroll: true
680
- });
681
- }, []);
682
- return /*#__PURE__*/jsx(FocusScope, {
683
- contain: true,
684
- restoreFocus: true,
685
- children: /*#__PURE__*/jsx("div", {
686
- ref: wrapperReference,
687
- tabIndex: -1,
688
- children: children
725
+ const isBlank = target === '_blank';
726
+ const {
727
+ formatMessage
728
+ } = useIntl();
729
+ return /*#__PURE__*/jsxs("a", {
730
+ href: href,
731
+ target: target,
732
+ className: classNames('np-link', type ? `np-text-${type}` : undefined, 'd-inline-flex', className),
733
+ "aria-label": ariaLabel,
734
+ rel: isBlank ? 'noreferrer' : undefined,
735
+ onClick: onClick,
736
+ ...props,
737
+ children: [children, " ", isBlank && /*#__PURE__*/jsx(NavigateAway, {
738
+ title: formatMessage(messages$c.opensInNewTab)
739
+ })]
740
+ });
741
+ };
742
+
743
+ const iconTypeMap = {
744
+ positive: Check,
745
+ neutral: Info$1,
746
+ warning: Alert$1,
747
+ negative: Cross,
748
+ pending: ClockBorderless,
749
+ info: Info$1,
750
+ error: Cross,
751
+ success: Check
752
+ };
753
+ const StatusIcon = ({
754
+ sentiment = 'neutral',
755
+ size = 'md'
756
+ }) => {
757
+ const Icon = iconTypeMap[sentiment];
758
+ const iconColor = sentiment === 'warning' || sentiment === 'pending' ? 'dark' : 'light';
759
+ return /*#__PURE__*/jsx("span", {
760
+ "data-testid": "status-icon",
761
+ className: classNames('status-circle', 'status-circle-' + size, sentiment),
762
+ children: /*#__PURE__*/jsx(Icon, {
763
+ className: classNames('status-icon', iconColor)
689
764
  })
690
765
  });
691
766
  };
692
767
 
693
- function withNextPortalWrapper(Component) {
694
- return function (props) {
695
- const [mounted, setMounted] = useState(false);
696
- useEffect(() => {
697
- setMounted(true);
698
- }, [setMounted]);
699
- return mounted ? /*#__PURE__*/createPortal( /*#__PURE__*/jsx(Component, {
700
- ...props
701
- }), document.body) : null;
702
- };
768
+ const DEFAULT_TYPE = Typography.TITLE_GROUP;
769
+ const titleTypeMapping = {
770
+ [Typography.TITLE_SCREEN]: 'h1',
771
+ [Typography.TITLE_SECTION]: 'h2',
772
+ [Typography.TITLE_SUBSECTION]: 'h3',
773
+ [Typography.TITLE_BODY]: 'h4',
774
+ [Typography.TITLE_GROUP]: 'h5'
775
+ };
776
+ function Title({
777
+ as,
778
+ type = DEFAULT_TYPE,
779
+ className,
780
+ ...props
781
+ }) {
782
+ const mapping = titleTypeMapping[type];
783
+ const isTypeSupported = mapping !== undefined;
784
+ if (isTypeSupported) {
785
+ const HeaderTag = as ?? mapping;
786
+ return /*#__PURE__*/jsx(HeaderTag, {
787
+ ...props,
788
+ className: classNames(`np-text-${type}`, className)
789
+ });
790
+ }
791
+ const HeaderTag = as ?? titleTypeMapping[DEFAULT_TYPE];
792
+ return /*#__PURE__*/jsx(HeaderTag, {
793
+ ...props,
794
+ className: classNames(`np-text-${DEFAULT_TYPE}`, className)
795
+ });
703
796
  }
704
797
 
705
- /**
706
- * Dimmer state management inspired by Material UI's ModalManager (https://github.com/mui-org/material-ui)
707
- */
708
- class DimmerManager {
709
- /**
710
- * Dimmer refs
711
- */
712
- dimmers;
713
- constructor() {
714
- this.dimmers = [];
715
- }
716
- add(dimmer) {
717
- let dimmerIndex = this.dimmers.indexOf(dimmer);
718
- if (dimmerIndex !== -1) {
719
- return dimmerIndex;
720
- }
721
- dimmerIndex = this.dimmers.length;
722
- this.dimmers.push(dimmer);
723
- return dimmerIndex;
724
- }
725
- remove(dimmer) {
726
- const dimmerIndex = this.dimmers.indexOf(dimmer);
727
- if (dimmerIndex !== -1) {
728
- this.dimmers.splice(dimmerIndex, 1);
729
- }
730
- return dimmerIndex;
798
+ function logActionRequired(message) {
799
+ if (['development', 'test'].includes(process?.env?.NODE_ENV)) {
800
+ // eslint-disable-next-line no-console
801
+ console.warn(message);
731
802
  }
732
- isTop(dimmer) {
733
- return this.dimmers.length > 0 && this.dimmers[this.dimmers.length - 1] === dimmer;
803
+ }
804
+ function logActionRequiredIf(message, conditional) {
805
+ if (conditional) {
806
+ logActionRequired(message);
734
807
  }
735
808
  }
736
809
 
737
- const EXIT_ANIMATION$1 = 350;
738
- const dimmerManager = new DimmerManager();
739
- const handleTouchMove = event => {
740
- const isTouchedElementDimmer = event.target.classList.contains('dimmer');
741
- // disable scroll on iOS devices for Dimmer area
742
- // this is because of bug in WebKit https://bugs.webkit.org/show_bug.cgi?id=220908
743
- // note: scrolling still works for children(s) as expected
744
- if (isIosDevice() && isTouchedElementDimmer) {
745
- event.stopPropagation();
746
- event.preventDefault();
747
- }
748
- };
749
- const Dimmer = ({
750
- children,
810
+ const reader = new commonmark.Parser();
811
+ const writer = new commonmark.HtmlRenderer({
812
+ safe: true
813
+ });
814
+ const NODE_TYPE_LIST = Object.values(MarkdownNodeType);
815
+ function Markdown({
816
+ as: Element = 'div',
817
+ allowList,
818
+ blockList,
819
+ config,
751
820
  className,
752
- disableClickToClose = false,
753
- contentPosition,
754
- fadeContentOnEnter = false,
755
- fadeContentOnExit = false,
756
- open = false,
757
- scrollable = false,
758
- transparent = false,
759
- onClose
760
- }) => {
761
- const [hasNotExited, setHasNotExited] = useState(false);
762
- const dimmerReference = useRef(null);
763
- const closeOnClick = event => {
764
- if (event.target === dimmerReference.current) {
765
- onClose?.(event);
766
- }
821
+ children
822
+ }) {
823
+ if (!children) {
824
+ return null;
825
+ }
826
+ const linkTarget = config?.link?.target ?? '_self';
827
+ const paragraphClass = config?.paragraph?.className ?? '';
828
+ if (allowList != null && blockList != null) {
829
+ logActionRequired('Markdown supports only one of `allowList` or `blockList` to be used at a time. `blockList` will be ignored.');
830
+ }
831
+ const parser = nodes => {
832
+ const parsed = reader.parse(nodes);
833
+ const toExclude = allowList != null ? NODE_TYPE_LIST.filter(type => !allowList.includes(type)) : blockList;
834
+ return toExclude != null ? stripNodes({
835
+ parsed,
836
+ blockList: toExclude
837
+ }) : parsed;
767
838
  };
768
- const handleClick = event => {
769
- if (disableClickToClose || !onClose) {
770
- return;
771
- }
772
- closeOnClick(event);
839
+ const createMarkup = () => {
840
+ const parsed = parser(children);
841
+ return writer.render(parsed).replace(/<a href="/g, `<a target="${linkTarget}" href="`).replace(/<p>/g, `<p class="${paragraphClass}">`);
773
842
  };
774
- const handleKeyDown = useCallback(event => {
775
- if (event.key !== 'Escape') {
776
- return;
777
- }
778
- event.stopPropagation();
779
- if (onClose && dimmerReference.current && dimmerManager.isTop(dimmerReference.current)) {
780
- onClose(event);
781
- }
782
- }, [onClose]);
783
- const onEnter = () => {
784
- setHasNotExited(true);
785
- if (dimmerReference.current) {
786
- dimmerManager.add(dimmerReference.current);
787
- }
788
- };
789
- const onExited = () => {
790
- setHasNotExited(false);
791
- if (dimmerReference.current) {
792
- dimmerManager.remove(dimmerReference.current);
843
+ return /*#__PURE__*/jsx(Element, {
844
+ className: className,
845
+ dangerouslySetInnerHTML: {
846
+ __html: createMarkup()
793
847
  }
794
- };
795
- useEffect(() => {
796
- const localReferenceCopy = dimmerReference.current;
797
- if (open) {
798
- document.addEventListener('keydown', handleKeyDown);
799
- localReferenceCopy?.addEventListener('touchmove', handleTouchMove, {
800
- passive: true
801
- });
848
+ });
849
+ }
850
+ function stripNodes({
851
+ blockList,
852
+ parsed
853
+ }) {
854
+ if (!parsed) {
855
+ return parsed;
856
+ }
857
+ const walker = parsed.walker();
858
+ for (let event = walker.next(); event != null; event = walker.next()) {
859
+ const {
860
+ node
861
+ } = event;
862
+ if (blockList.includes(node.type) && !event.entering) {
863
+ while (node.firstChild != null) {
864
+ node.insertBefore(node.firstChild);
865
+ }
866
+ node.unlink();
802
867
  }
803
- return () => {
804
- document.removeEventListener('keydown', handleKeyDown);
805
- localReferenceCopy?.removeEventListener('touchmove', handleTouchMove);
806
- };
807
- }, [handleKeyDown, open]);
808
- return /*#__PURE__*/jsx(DimmerWrapper, {
809
- open: open,
810
- hasNotExited: hasNotExited,
811
- children: /*#__PURE__*/jsx(CSSTransition, {
812
- nodeRef: dimmerReference,
813
- in: open,
814
- appear: true
815
- // Wait for animation to finish before unmount.
816
- ,
817
- timeout: {
818
- enter: 0,
819
- exit: EXIT_ANIMATION$1
820
- },
821
- classNames: {
822
- enter: classNames({
823
- 'dimmer--enter-fade': fadeContentOnEnter
824
- }),
825
- enterDone: classNames('dimmer--enter-done', {
826
- 'dimmer--enter-fade': fadeContentOnEnter
827
- }),
828
- exit: classNames('dimmer--exit', {
829
- 'dimmer--exit-fade': fadeContentOnExit
830
- })
831
- },
832
- unmountOnExit: true,
833
- onEnter: onEnter,
834
- onExited: onExited,
835
- children: /*#__PURE__*/jsx(DimmerContentWrapper, {
836
- scrollBody: !transparent,
837
- children: /*#__PURE__*/jsx(FocusBoundary, {
838
- children: /*#__PURE__*/jsx("div", {
839
- ref: dimmerReference,
840
- className: classNames('dimmer', {
841
- 'dimmer--scrollable': scrollable,
842
- 'dimmer--transparent': transparent
843
- }, className),
844
- role: "presentation",
845
- onClick: handleClick,
846
- children: /*#__PURE__*/jsx("div", {
847
- className: classNames('dimmer-content-positioner', contentPosition != null && ['d-flex justify-content-center', {
848
- 'align-items-start': contentPosition === 'top',
849
- 'align-items-center': contentPosition === 'center',
850
- 'align-items-end': contentPosition === 'bottom'
851
- }]),
852
- children: children
853
- })
854
- })
855
- })
856
- })
857
- })
868
+ }
869
+ return parsed;
870
+ }
871
+
872
+ const allowList = [MarkdownNodeType.STRONG];
873
+ const InlineMarkdown = props => {
874
+ return /*#__PURE__*/jsx(Markdown, {
875
+ ...props,
876
+ as: "span",
877
+ allowList: allowList,
878
+ blockList: undefined
858
879
  });
859
880
  };
860
- const DimmerWrapper = ({
861
- open,
862
- hasNotExited,
863
- children
864
- }) => {
865
- const {
866
- screenMode,
867
- theme
868
- } = useTheme();
869
- return open || hasNotExited ? /*#__PURE__*/jsx(ThemeProvider, {
870
- theme: "personal",
871
- screenMode: theme === 'personal' ? screenMode : 'light',
872
- isNotRootProvider: true,
873
- children: children
874
- }) : /*#__PURE__*/jsx(Fragment, {
875
- children: children
876
- });
881
+ InlineMarkdown.propTypes = {
882
+ children: PropTypes.string.isRequired,
883
+ className: PropTypes.string
877
884
  };
878
- const DimmerContentWrapper = ({
879
- children,
880
- scrollBody
881
- }) => {
882
- useEffect(() => {
883
- if (scrollBody) {
884
- addNoScrollClass();
885
- }
886
- return () => {
887
- if (scrollBody) {
888
- removeNoScrollClass();
889
- }
890
- };
891
- }, [scrollBody]);
892
- return children;
885
+ InlineMarkdown.defaultProps = {
886
+ className: undefined
893
887
  };
894
- var Dimmer$1 = withNextPortalWrapper(Dimmer);
895
888
 
896
- const POPOVER_OFFSET = [0, 16];
897
- // By default the flip positioning explores only the opposite alternative. So if left is passed and there's no enough space
898
- // the right one gets chosen. If there's no space on both sides popover goes back to the initially chosen one left.
899
- // This mapping forces popover to try the four available positions before going back to the initial chosen one.
900
- const fallbackPlacements = {
901
- [Position.TOP]: [Position.BOTTOM, Position.RIGHT, Position.LEFT],
902
- [Position.BOTTOM]: [Position.TOP, Position.RIGHT, Position.LEFT],
903
- [Position.LEFT]: [Position.RIGHT, Position.TOP, Position.BOTTOM],
904
- [Position.RIGHT]: [Position.LEFT, Position.TOP, Position.BOTTOM]
905
- };
906
- const Panel = /*#__PURE__*/forwardRef(({
907
- arrow = false,
908
- flip = true,
909
- altAxis = false,
910
- children,
911
- open = false,
912
- onClose,
913
- position = Position.BOTTOM,
914
- anchorRef,
915
- anchorWidth = false,
916
- ...rest
917
- }, reference) => {
918
- const [arrowElement, setArrowElement] = useState(null);
919
- const [popperElement, setPopperElement] = useState(null);
920
- const modifiers = [];
921
- if (altAxis) {
922
- modifiers.push({
923
- // https://popper.js.org/docs/v2/modifiers/prevent-overflow
924
- name: 'preventOverflow',
925
- options: {
926
- altAxis: true,
927
- tether: false
928
- }
929
- });
930
- }
931
- if (arrow) {
932
- modifiers.push({
933
- name: 'arrow',
934
- options: {
935
- element: arrowElement,
936
- options: {
937
- padding: 8 // 8px from the edges of the popper
938
- }
939
- }
940
- });
941
- // This lets you displace a popper element from its reference element.
942
- modifiers.push({
943
- name: 'offset',
944
- options: {
945
- offset: POPOVER_OFFSET
946
- }
947
- });
948
- }
949
- if (flip && fallbackPlacements[position]) {
950
- modifiers.push({
951
- name: 'flip',
952
- options: {
953
- fallbackPlacements: fallbackPlacements[position]
954
- }
955
- });
889
+ var AlertArrowPosition;
890
+ (function (AlertArrowPosition) {
891
+ AlertArrowPosition["TOP_LEFT"] = "up-left";
892
+ AlertArrowPosition["TOP"] = "up-center";
893
+ AlertArrowPosition["TOP_RIGHT"] = "up-right";
894
+ AlertArrowPosition["BOTTOM_LEFT"] = "down-left";
895
+ AlertArrowPosition["BOTTOM"] = "down-center";
896
+ AlertArrowPosition["BOTTOM_RIGHT"] = "down-right";
897
+ })(AlertArrowPosition || (AlertArrowPosition = {}));
898
+ function resolveType(type) {
899
+ switch (type) {
900
+ case 'success':
901
+ return 'positive';
902
+ case 'info':
903
+ return 'neutral';
904
+ case 'error':
905
+ return 'negative';
906
+ default:
907
+ return type;
956
908
  }
957
- const {
958
- styles,
959
- attributes,
960
- forceUpdate
961
- } = usePopper(anchorRef.current, popperElement, {
962
- placement: position,
963
- modifiers
964
- });
965
- // If the trigger is not visible when the position is calculated, it will be incorrect. Because this can happen repeatedly (on resize for example),
966
- // it is most simple just to always position before opening
909
+ }
910
+ function Alert({
911
+ arrow,
912
+ action,
913
+ children,
914
+ className,
915
+ dismissible,
916
+ icon,
917
+ onDismiss,
918
+ message,
919
+ size,
920
+ title,
921
+ type = 'neutral',
922
+ variant = 'desktop'
923
+ }) {
967
924
  useEffect(() => {
968
- if (open && forceUpdate) {
969
- forceUpdate();
925
+ if (arrow !== undefined) {
926
+ logActionRequired("Alert component doesn't support 'arrow' anymore, use 'InlineAlert' instead.");
970
927
  }
971
- }, [open]);
972
- const contentStyle = {
973
- ...(anchorWidth ? {
974
- width: anchorRef.current?.clientWidth
975
- } : undefined)
976
- };
977
- return /*#__PURE__*/jsx(Dimmer$1, {
978
- open: open,
979
- transparent: true,
980
- fadeContentOnEnter: true,
981
- fadeContentOnExit: true,
982
- onClose: onClose,
983
- children: /*#__PURE__*/jsx("div", {
984
- ...rest,
985
- ref: setPopperElement,
986
- role: "dialog"
987
- // eslint-disable-next-line react/forbid-dom-props
988
- ,
989
- style: {
990
- ...styles.popper
991
- },
992
- ...attributes.popper,
993
- className: classNames('np-panel', {
994
- 'np-panel--open': open
995
- }, rest['className']),
996
- children: /*#__PURE__*/jsxs("div", {
997
- ref: reference
998
- /* eslint-disable-next-line react/forbid-dom-props */,
999
- style: contentStyle,
1000
- className: classNames('np-panel__content'),
1001
- children: [children, arrow && /*#__PURE__*/jsx("div", {
1002
- ref: setArrowElement,
1003
- className: classNames('np-panel__arrow')
1004
- // eslint-disable-next-line react/forbid-dom-props
1005
- ,
1006
- style: styles.arrow
928
+ }, [arrow]);
929
+ useEffect(() => {
930
+ if (children !== undefined) {
931
+ logActionRequired("Alert component doesn't support 'children' anymore, use 'message' instead.");
932
+ }
933
+ }, [children]);
934
+ useEffect(() => {
935
+ if (dismissible !== undefined) {
936
+ logActionRequired("Alert component doesn't support 'dismissible' anymore, use 'onDismiss' instead.");
937
+ }
938
+ }, [dismissible]);
939
+ useEffect(() => {
940
+ if (size !== undefined) {
941
+ logActionRequired("Alert component doesn't support 'size' anymore, please remove that prop.");
942
+ }
943
+ }, [size]);
944
+ const resolvedType = resolveType(type);
945
+ useEffect(() => {
946
+ if (resolvedType !== type) {
947
+ logActionRequired(`Alert component has deprecated '${type}' value for the 'type' prop. Please use '${resolvedType}' instead.`);
948
+ }
949
+ }, [resolvedType, type]);
950
+ const [shouldFire, setShouldFire] = useState(false);
951
+ const closeButtonReference = useRef(null);
952
+ return /*#__PURE__*/jsxs("div", {
953
+ className: classNames('alert d-flex', `alert-${resolvedType}`, arrow != null && alertArrowClassNames(arrow), className),
954
+ "data-testid": "alert",
955
+ onTouchStart: () => setShouldFire(true),
956
+ onTouchEnd: event => {
957
+ if (shouldFire && action &&
958
+ // Check if current event is triggered from closeButton
959
+ event.target instanceof Node && closeButtonReference.current && !closeButtonReference.current.contains(event.target)) {
960
+ if (action.target === '_blank') {
961
+ window.top?.open(action.href);
962
+ } else {
963
+ window.top?.location.assign(action.href);
964
+ }
965
+ }
966
+ setShouldFire(false);
967
+ },
968
+ onTouchMove: () => setShouldFire(false),
969
+ children: [/*#__PURE__*/jsxs("div", {
970
+ className: classNames('alert__content', 'd-flex', 'flex-grow-1', variant),
971
+ "data-testid": variant,
972
+ children: [icon ? /*#__PURE__*/jsx("div", {
973
+ className: "alert__icon",
974
+ children: icon
975
+ }) : /*#__PURE__*/jsx(StatusIcon, {
976
+ size: Size.LARGE,
977
+ sentiment: resolvedType
978
+ }), /*#__PURE__*/jsxs("div", {
979
+ className: "alert__message",
980
+ children: [/*#__PURE__*/jsxs("div", {
981
+ role: Sentiment.NEGATIVE === resolvedType ? 'alert' : 'status',
982
+ children: [title && /*#__PURE__*/jsx(Title, {
983
+ className: "m-b-1",
984
+ type: Typography.TITLE_BODY,
985
+ children: title
986
+ }), /*#__PURE__*/jsx(Body, {
987
+ as: "span",
988
+ className: "d-block",
989
+ type: Typography.BODY_LARGE,
990
+ children: children || /*#__PURE__*/jsx(InlineMarkdown, {
991
+ children: message
992
+ })
993
+ })]
994
+ }), action && /*#__PURE__*/jsx(Link, {
995
+ href: action.href,
996
+ className: "m-t-1",
997
+ "aria-label": action['aria-label'],
998
+ target: action.target,
999
+ type: Typography.LINK_LARGE,
1000
+ children: action.text
1007
1001
  })]
1008
- })
1009
- })
1002
+ })]
1003
+ }), onDismiss && /*#__PURE__*/jsx(CloseButton, {
1004
+ ref: closeButtonReference,
1005
+ className: "m-l-2",
1006
+ onClick: onDismiss
1007
+ })]
1010
1008
  });
1011
- });
1009
+ }
1010
+ function alertArrowClassNames(arrow) {
1011
+ switch (arrow) {
1012
+ case 'down-center':
1013
+ return 'arrow arrow-bottom arrow-center';
1014
+ case 'down-left':
1015
+ return 'arrow arrow-bottom arrow-left';
1016
+ case 'down-right':
1017
+ return 'arrow arrow-bottom arrow-right';
1018
+ case 'up-center':
1019
+ return 'arrow arrow-center';
1020
+ case 'up-right':
1021
+ return 'arrow arrow-right';
1022
+ case 'up-left':
1023
+ default:
1024
+ return 'arrow';
1025
+ }
1026
+ }
1012
1027
 
1013
- const Section = ({
1014
- children,
1028
+ // TODO: consider to move this enum into component file once we migrate it on TypeScript or replace with some common enum
1029
+ var AvatarType;
1030
+ (function (AvatarType) {
1031
+ AvatarType["THUMBNAIL"] = "thumbnail";
1032
+ AvatarType["ICON"] = "icon";
1033
+ AvatarType["EMOJI"] = "emoji";
1034
+ AvatarType["INITIALS"] = "initials";
1035
+ })(AvatarType || (AvatarType = {}));
1036
+
1037
+ /*
1038
+ * The colors we support generating an Avatar background color from a "seed" for.
1039
+ * Changing this array will change the assignment between seeds.
1040
+ * Do not change this array.
1041
+ */
1042
+ const avatarColors = ['--color-bright-blue', '--color-bright-yellow', '--color-bright-pink', '--color-bright-orange'];
1043
+ /*
1044
+ * Takes in a "seed" string and spits out an index for the color we should use.
1045
+ * This implementation has been synced across all three clients so that we consistently
1046
+ * generate branded avatar colors when rendering a list of Avatars.
1047
+ * Do not change this implementation.
1048
+ */
1049
+ const hashSeed = seed => {
1050
+ const base = 31;
1051
+ const modulo = avatarColors.length;
1052
+ let hashValue = 0;
1053
+ let basePow = 1;
1054
+ for (let i = 0; i < seed.length; i += 1) {
1055
+ hashValue = (hashValue + seed.charCodeAt(i) * basePow) % modulo;
1056
+ basePow = basePow * base % modulo;
1057
+ }
1058
+ return hashValue;
1059
+ };
1060
+ const getAvatarColorFromSeed = seed => {
1061
+ return avatarColors[hashSeed(seed)];
1062
+ };
1063
+
1064
+ const backwardsCompatibleSize = size => {
1065
+ switch (size) {
1066
+ case 'sm':
1067
+ return 24;
1068
+ case 'md':
1069
+ return 48;
1070
+ case 'lg':
1071
+ return 72;
1072
+ default:
1073
+ return size;
1074
+ }
1075
+ };
1076
+ const Avatar = ({
1077
+ backgroundColor = null,
1078
+ backgroundColorSeed = null,
1079
+ children = null,
1015
1080
  className,
1016
- withHorizontalPadding = false
1081
+ outlined = false,
1082
+ size: sizeFromProps = 48,
1083
+ theme = Theme.LIGHT,
1084
+ type = 'thumbnail'
1017
1085
  }) => {
1086
+ const backgroundColorFromSeed = useMemo(() => !backgroundColor && backgroundColorSeed ? `var(${getAvatarColorFromSeed(backgroundColorSeed)})` : undefined, [backgroundColor, backgroundColorSeed]);
1087
+ const size = backwardsCompatibleSize(sizeFromProps);
1018
1088
  return /*#__PURE__*/jsx("div", {
1019
- className: classNames('np-section', className, {
1020
- 'np-section--with-horizontal-padding': withHorizontalPadding
1089
+ className: classNames('tw-avatar', className, `tw-avatar--${size}`, `tw-avatar--${type}`, {
1090
+ 'tw-avatar--outlined': outlined,
1091
+ 'tw-avatar--branded': Boolean(backgroundColorFromSeed),
1092
+ 'np-text-title-body': type === 'initials'
1021
1093
  }),
1022
- children: children
1094
+ children: /*#__PURE__*/jsx("div", {
1095
+ className: "tw-avatar__content",
1096
+ style: {
1097
+ backgroundColor: backgroundColor || backgroundColorFromSeed
1098
+ },
1099
+ children: children
1100
+ })
1023
1101
  });
1024
1102
  };
1025
1103
 
1026
- const radius = {
1027
- xxs: 6,
1028
- xs: 11,
1029
- sm: 22,
1030
- xl: 61
1104
+ const Badge = ({
1105
+ badge,
1106
+ className = undefined,
1107
+ size = Size.SMALL,
1108
+ border = Theme.LIGHT,
1109
+ children
1110
+ }) => {
1111
+ const classes = classNames('tw-badge', {
1112
+ [`tw-badge-border-${border}`]: border,
1113
+ [`tw-badge-${size}`]: size
1114
+ }, className);
1115
+ return /*#__PURE__*/jsxs("div", {
1116
+ className: classes,
1117
+ children: [/*#__PURE__*/jsx("div", {
1118
+ className: "tw-badge__children",
1119
+ children: children
1120
+ }), /*#__PURE__*/jsx("div", {
1121
+ className: "tw-badge__content",
1122
+ children: badge
1123
+ })]
1124
+ });
1031
1125
  };
1032
- const ANIMATION_DURATION_IN_MS = 1500;
1033
- class ProcessIndicator extends Component {
1034
- constructor(props) {
1035
- super(props);
1036
- this.state = {
1037
- status: props.status,
1038
- size: props.size
1039
- };
1040
- this.interval = null;
1041
- this.timeout = null;
1042
- }
1043
1126
 
1044
- /**
1045
- * Create interval for animation duration (1500ms)
1046
- * Update state only at the end of every interval
1047
- * (end of animation loop) if props changed before end of animation
1048
- */
1049
- componentDidMount() {
1127
+ const OptionalBadge = ({
1128
+ url,
1129
+ altText,
1130
+ statusIcon,
1131
+ children,
1132
+ ...rest
1133
+ }) => {
1134
+ if (url) {
1135
+ return /*#__PURE__*/jsx(Badge, {
1136
+ badge: /*#__PURE__*/jsx("img", {
1137
+ src: url,
1138
+ alt: altText
1139
+ }),
1140
+ ...rest,
1141
+ children: children
1142
+ });
1143
+ }
1144
+ if (statusIcon) {
1145
+ return /*#__PURE__*/jsx(Badge, {
1146
+ badge: /*#__PURE__*/jsx(StatusIcon, {
1147
+ sentiment: statusIcon,
1148
+ size: Size.SMALL
1149
+ }),
1150
+ ...rest,
1151
+ children: children
1152
+ });
1153
+ }
1154
+ return /*#__PURE__*/jsx(Fragment, {
1155
+ children: children
1156
+ });
1157
+ };
1158
+ const AvatarWrapper = ({
1159
+ url,
1160
+ profileType,
1161
+ profileId,
1162
+ badgeUrl,
1163
+ badgeAltText,
1164
+ badgeStatusIcon,
1165
+ name,
1166
+ avatarProps,
1167
+ badgeProps
1168
+ }) => {
1169
+ const [hasImageLoadError, setImageLoadError] = useState(false);
1170
+ const isBusinessProfile = profileType === ProfileType.BUSINESS;
1171
+ // Reset the errored state when url changes
1172
+ useEffect(() => setImageLoadError(false), [url]);
1173
+ const getAvatarProps = () => {
1174
+ if (url && !hasImageLoadError) {
1175
+ return {
1176
+ type: AvatarType.THUMBNAIL,
1177
+ children: /*#__PURE__*/jsx("img", {
1178
+ src: url,
1179
+ alt: "",
1180
+ onError: () => setImageLoadError(true)
1181
+ }),
1182
+ ...avatarProps
1183
+ };
1184
+ }
1185
+ if (profileType) {
1186
+ return {
1187
+ type: AvatarType.ICON,
1188
+ children: isBusinessProfile ? /*#__PURE__*/jsx(Briefcase, {
1189
+ size: "24"
1190
+ }) : /*#__PURE__*/jsx(Person, {
1191
+ size: "24"
1192
+ }),
1193
+ ...avatarProps
1194
+ };
1195
+ }
1196
+ if (name) {
1197
+ return {
1198
+ type: AvatarType.INITIALS,
1199
+ children: /*#__PURE__*/jsx(Fragment, {
1200
+ children: getInitials(name)
1201
+ }),
1202
+ backgroundColorSeed: profileId?.toString(),
1203
+ ...avatarProps
1204
+ };
1205
+ }
1206
+ return {
1207
+ type: AvatarType.ICON,
1208
+ children: /*#__PURE__*/jsx(Person, {
1209
+ size: "24"
1210
+ }),
1211
+ ...avatarProps
1212
+ };
1213
+ };
1214
+ return /*#__PURE__*/jsx(OptionalBadge, {
1215
+ url: badgeUrl,
1216
+ altText: badgeAltText,
1217
+ statusIcon: badgeStatusIcon,
1218
+ ...badgeProps,
1219
+ children: /*#__PURE__*/jsx(Avatar, {
1220
+ size: Size.MEDIUM,
1221
+ ...getAvatarProps()
1222
+ })
1223
+ });
1224
+ };
1225
+ function getInitials(name) {
1226
+ const allInitials = name.split(' ').map(part => part[0]).join('').toUpperCase();
1227
+ if (allInitials.length === 1) {
1228
+ return allInitials[0];
1229
+ }
1230
+ return allInitials[0] + allInitials.slice(-1);
1231
+ }
1232
+
1233
+ const radius = {
1234
+ xxs: 6,
1235
+ xs: 11,
1236
+ sm: 22,
1237
+ xl: 61
1238
+ };
1239
+ const ANIMATION_DURATION_IN_MS = 1500;
1240
+ class ProcessIndicator extends Component {
1241
+ constructor(props) {
1242
+ super(props);
1243
+ this.state = {
1244
+ status: props.status,
1245
+ size: props.size
1246
+ };
1247
+ this.interval = null;
1248
+ this.timeout = null;
1249
+ }
1250
+
1251
+ /**
1252
+ * Create interval for animation duration (1500ms)
1253
+ * Update state only at the end of every interval
1254
+ * (end of animation loop) if props changed before end of animation
1255
+ */
1256
+ componentDidMount() {
1050
1257
  this.interval = setInterval(() => {
1051
1258
  const statusFromState = this.state.status;
1052
1259
  const sizeFromState = this.state.size;
@@ -1146,7 +1353,7 @@ ProcessIndicator.defaultProps = {
1146
1353
  };
1147
1354
  var ProcessIndicator$1 = ProcessIndicator;
1148
1355
 
1149
- var messages$d = defineMessages({
1356
+ var messages$b = defineMessages({
1150
1357
  loadingAriaLabel: {
1151
1358
  id: "neptune.Button.loadingAriaLabel"
1152
1359
  }
@@ -1163,18 +1370,6 @@ const priorityClassMap = {
1163
1370
  [Priority.TERTIARY]: 'btn-priority-3'
1164
1371
  };
1165
1372
 
1166
- function logActionRequired(message) {
1167
- if (['development', 'test'].includes(process?.env?.NODE_ENV)) {
1168
- // eslint-disable-next-line no-console
1169
- console.warn(message);
1170
- }
1171
- }
1172
- function logActionRequiredIf(message, conditional) {
1173
- if (conditional) {
1174
- logActionRequired(message);
1175
- }
1176
- }
1177
-
1178
1373
  const deprecatedTypeMap = {
1179
1374
  [Type.PRIMARY]: ControlType.ACCENT,
1180
1375
  [Type.SECONDARY]: ControlType.ACCENT,
@@ -1277,7 +1472,7 @@ const Button = /*#__PURE__*/forwardRef(({
1277
1472
  className: classes,
1278
1473
  ...props,
1279
1474
  "aria-live": loading ? 'polite' : 'off',
1280
- "aria-label": loading ? intl.formatMessage(messages$d.loadingAriaLabel) : rest['aria-label'],
1475
+ "aria-label": loading ? intl.formatMessage(messages$b.loadingAriaLabel) : rest['aria-label'],
1281
1476
  children: [children, loading && /*#__PURE__*/jsx(ProcessIndicator$1, {
1282
1477
  size: processIndicatorSize(),
1283
1478
  className: "btn-loader"
@@ -1285,1504 +1480,972 @@ const Button = /*#__PURE__*/forwardRef(({
1285
1480
  });
1286
1481
  });
1287
1482
 
1288
- var messages$c = defineMessages({
1289
- opensInNewTab: {
1290
- id: "neptune.Link.opensInNewTab"
1291
- }
1292
- });
1293
-
1294
- const Link = ({
1295
- className,
1296
- children,
1297
- href,
1298
- target,
1299
- type,
1300
- 'aria-label': ariaLabel,
1301
- onClick,
1302
- ...props
1303
- }) => {
1304
- const isBlank = target === '_blank';
1483
+ const Card$1 = /*#__PURE__*/forwardRef((props, reference) => {
1305
1484
  const {
1306
- formatMessage
1307
- } = useIntl();
1308
- return /*#__PURE__*/jsxs("a", {
1309
- href: href,
1310
- target: target,
1311
- className: classNames('np-link', type ? `np-text-${type}` : undefined, 'd-inline-flex', className),
1312
- "aria-label": ariaLabel,
1313
- rel: isBlank ? 'noreferrer' : undefined,
1314
- onClick: onClick,
1315
- ...props,
1316
- children: [children, " ", isBlank && /*#__PURE__*/jsx(NavigateAway, {
1317
- title: formatMessage(messages$c.opensInNewTab)
1485
+ 'aria-label': ariaLabel,
1486
+ as: Element,
1487
+ isExpanded,
1488
+ title,
1489
+ details,
1490
+ children,
1491
+ onClick,
1492
+ icon,
1493
+ id,
1494
+ className,
1495
+ ...rest
1496
+ } = props;
1497
+ const isOpen = !!(isExpanded && children);
1498
+ return /*#__PURE__*/jsxs(Element, {
1499
+ ref: reference,
1500
+ className: classNames('np-card', className, {
1501
+ 'np-card--expanded': isOpen,
1502
+ 'np-card--inactive': !children,
1503
+ 'np-card--has-icon': !!icon
1504
+ }),
1505
+ id: id,
1506
+ "data-testid": rest['data-testid'],
1507
+ children: [/*#__PURE__*/jsx(Option$2, {
1508
+ "aria-label": ariaLabel,
1509
+ as: children ? 'button' : 'div',
1510
+ className: classNames('np-card__button'),
1511
+ media: icon,
1512
+ title: title,
1513
+ content: details,
1514
+ showMediaAtAllSizes: true,
1515
+ button: children && /*#__PURE__*/jsx(Chevron, {
1516
+ orientation: isOpen ? Position.TOP : Position.BOTTOM
1517
+ }),
1518
+ onClick: () => children && onClick(!isExpanded)
1519
+ }), /*#__PURE__*/jsx("div", {
1520
+ className: classNames('np-card__divider', {
1521
+ 'np-card__divider--expanded': isOpen
1522
+ })
1523
+ }), isOpen && /*#__PURE__*/jsx(Body, {
1524
+ as: "span",
1525
+ type: Typography.BODY_LARGE,
1526
+ className: "np-card__content d-block",
1527
+ children: children
1318
1528
  })]
1319
1529
  });
1530
+ });
1531
+ const hasChildren = ({
1532
+ children
1533
+ }) => children;
1534
+ Card$1.propTypes = {
1535
+ 'aria-label': PropTypes.string,
1536
+ as: PropTypes.string,
1537
+ isExpanded: requiredIf(PropTypes.bool, hasChildren),
1538
+ title: PropTypes.node.isRequired,
1539
+ details: PropTypes.node.isRequired,
1540
+ onClick: requiredIf(PropTypes.func, hasChildren),
1541
+ icon: PropTypes.node,
1542
+ children: PropTypes.node,
1543
+ id: PropTypes.string,
1544
+ className: PropTypes.string,
1545
+ 'data-testid': PropTypes.string
1320
1546
  };
1321
-
1322
- const DEFAULT_TYPE = Typography.TITLE_GROUP;
1323
- const titleTypeMapping = {
1324
- [Typography.TITLE_SCREEN]: 'h1',
1325
- [Typography.TITLE_SECTION]: 'h2',
1326
- [Typography.TITLE_SUBSECTION]: 'h3',
1327
- [Typography.TITLE_BODY]: 'h4',
1328
- [Typography.TITLE_GROUP]: 'h5'
1547
+ Card$1.defaultProps = {
1548
+ 'aria-label': undefined,
1549
+ as: 'div',
1550
+ children: null,
1551
+ id: null,
1552
+ className: null,
1553
+ 'data-testid': null
1329
1554
  };
1330
- function Title({
1331
- as,
1332
- type = DEFAULT_TYPE,
1555
+ var Card$2 = Card$1;
1556
+
1557
+ const CheckboxButton = /*#__PURE__*/forwardRef(({
1558
+ checked,
1333
1559
  className,
1334
- ...props
1335
- }) {
1336
- const mapping = titleTypeMapping[type];
1337
- const isTypeSupported = mapping !== undefined;
1338
- if (isTypeSupported) {
1339
- const HeaderTag = as ?? mapping;
1340
- return /*#__PURE__*/jsx(HeaderTag, {
1341
- ...props,
1342
- className: classNames(`np-text-${type}`, className)
1343
- });
1344
- }
1345
- const HeaderTag = as ?? titleTypeMapping[DEFAULT_TYPE];
1346
- return /*#__PURE__*/jsx(HeaderTag, {
1347
- ...props,
1348
- className: classNames(`np-text-${DEFAULT_TYPE}`, className)
1349
- });
1350
- }
1560
+ disabled,
1561
+ onChange,
1562
+ ...rest
1563
+ }, reference) => /*#__PURE__*/jsxs("span", {
1564
+ className: classNames('np-checkbox-button', className, disabled && 'disabled'),
1565
+ children: [/*#__PURE__*/jsx("input", {
1566
+ ...rest,
1567
+ ref: reference,
1568
+ type: "checkbox",
1569
+ disabled: disabled,
1570
+ checked: checked,
1571
+ onChange: onChange
1572
+ }), /*#__PURE__*/jsx("span", {
1573
+ className: "tw-checkbox-button",
1574
+ children: /*#__PURE__*/jsx("span", {
1575
+ className: "tw-checkbox-check"
1576
+ })
1577
+ })]
1578
+ }));
1579
+ var CheckboxButton$1 = CheckboxButton;
1351
1580
 
1352
- const HeaderAction = ({
1353
- action
1581
+ const Checkbox = ({
1582
+ id,
1583
+ checked,
1584
+ required,
1585
+ disabled,
1586
+ readOnly,
1587
+ label,
1588
+ className,
1589
+ secondary,
1590
+ onChange,
1591
+ onFocus,
1592
+ onBlur
1354
1593
  }) => {
1355
1594
  const {
1356
1595
  isModern
1357
1596
  } = useTheme();
1358
- const props = {
1359
- 'aria-label': action['aria-label']
1360
- };
1361
- if ('href' in action) {
1362
- return /*#__PURE__*/jsx(Link, {
1363
- href: action.href,
1364
- target: action.target,
1365
- onClick: action.onClick,
1366
- ...props,
1367
- children: action.text
1368
- });
1369
- }
1370
- return isModern ? /*#__PURE__*/jsx(Button, {
1371
- className: "np-header__button",
1372
- priority: "tertiary",
1373
- size: "sm",
1374
- onClick: action.onClick,
1375
- ...props,
1376
- children: action.text
1377
- }) : /*#__PURE__*/jsx(ActionButton, {
1378
- onClick: action.onClick,
1379
- ...props,
1380
- children: action.text
1597
+ const classList = classNames('np-checkbox', {
1598
+ checkbox: true,
1599
+ 'checkbox-lg': secondary,
1600
+ disabled: isModern && disabled
1601
+ }, className);
1602
+ const innerDisabled = disabled || readOnly;
1603
+ return /*#__PURE__*/jsx("div", {
1604
+ id: id,
1605
+ className: classList,
1606
+ children: /*#__PURE__*/jsxs("label", {
1607
+ className: classNames({
1608
+ disabled
1609
+ }),
1610
+ children: [/*#__PURE__*/jsx(CheckboxButton$1, {
1611
+ className: "p-r-2",
1612
+ checked: checked,
1613
+ disabled: innerDisabled,
1614
+ required: !innerDisabled && required,
1615
+ onFocus: onFocus,
1616
+ onChange: () => onChange(!checked),
1617
+ onBlur: onBlur
1618
+ }), /*#__PURE__*/jsxs(Body, {
1619
+ as: "span",
1620
+ className: "np-checkbox__text",
1621
+ type: secondary ? Typography.BODY_LARGE_BOLD : Typography.BODY_LARGE,
1622
+ children: [/*#__PURE__*/jsx("span", {
1623
+ className: required ? 'has-required' : undefined,
1624
+ children: label
1625
+ }), secondary && /*#__PURE__*/jsx(Body, {
1626
+ as: "span",
1627
+ className: classNames({
1628
+ secondary: !isModern
1629
+ }),
1630
+ children: secondary
1631
+ })]
1632
+ })]
1633
+ })
1381
1634
  });
1382
1635
  };
1383
- /**
1384
- *
1385
- * Neptune Web: https://transferwise.github.io/neptune-web/components/content/Header
1386
- *
1387
- */
1388
- const Header = ({
1389
- action,
1390
- as = 'h5',
1391
- title,
1392
- className
1393
- }) => {
1394
- if (!action) {
1395
- return /*#__PURE__*/jsx(Title, {
1396
- as: as,
1397
- type: Typography.TITLE_GROUP,
1398
- className: classNames('np-header', 'np-header__title', className),
1399
- children: title
1400
- });
1401
- }
1402
- if (as === 'legend') {
1403
- // eslint-disable-next-line no-console
1404
- console.warn('Legends should be the first child in a fieldset, and this is not possible when including an action');
1405
- }
1406
- return /*#__PURE__*/jsxs("div", {
1407
- className: classNames('np-header', className),
1408
- children: [/*#__PURE__*/jsx(Title, {
1409
- as: as,
1410
- type: Typography.TITLE_GROUP,
1411
- className: "np-header__title",
1412
- children: title
1413
- }), /*#__PURE__*/jsx(HeaderAction, {
1414
- action: action
1415
- })]
1416
- });
1636
+ Checkbox.propTypes = {
1637
+ id: PropTypes.string,
1638
+ checked: PropTypes.bool,
1639
+ required: PropTypes.bool,
1640
+ disabled: PropTypes.bool,
1641
+ readOnly: PropTypes.bool,
1642
+ label: PropTypes.node.isRequired,
1643
+ secondary: PropTypes.string,
1644
+ onFocus: PropTypes.func,
1645
+ onChange: PropTypes.func.isRequired,
1646
+ onBlur: PropTypes.func,
1647
+ className: PropTypes.string
1417
1648
  };
1418
-
1419
- const useConditionalListener = ({
1420
- attachListener,
1421
- callback,
1422
- eventType,
1423
- parent
1424
- }) => {
1425
- useEffect(() => {
1426
- if (attachListener && !isUndefined(parent)) {
1427
- parent.addEventListener(eventType, callback, true);
1428
- }
1429
- return () => {
1430
- if (!isUndefined(parent)) {
1431
- parent.removeEventListener(eventType, callback, true);
1432
- }
1433
- };
1434
- }, [attachListener, callback, eventType, parent]);
1649
+ Checkbox.defaultProps = {
1650
+ id: null,
1651
+ checked: false,
1652
+ required: false,
1653
+ disabled: false,
1654
+ readOnly: false,
1655
+ secondary: null,
1656
+ onFocus: null,
1657
+ onBlur: null,
1658
+ className: undefined
1435
1659
  };
1436
1660
 
1437
- const DirectionContext = /*#__PURE__*/createContext(Direction.LTR);
1438
- const DirectionProvider = ({
1439
- direction,
1440
- children
1441
- }) => {
1442
- return /*#__PURE__*/jsx(DirectionContext.Provider, {
1443
- value: direction,
1444
- children: children
1661
+ const CheckboxOption = /*#__PURE__*/forwardRef(({
1662
+ checked,
1663
+ disabled,
1664
+ onChange,
1665
+ ...rest
1666
+ }, reference) => {
1667
+ return /*#__PURE__*/jsx(Option$2, {
1668
+ ...rest,
1669
+ ref: reference,
1670
+ disabled: disabled,
1671
+ button: /*#__PURE__*/jsx(CheckboxButton$1, {
1672
+ checked: checked,
1673
+ disabled: disabled,
1674
+ onChange: () => onChange?.(!checked)
1675
+ })
1445
1676
  });
1446
- };
1447
-
1448
- const useDirection = () => {
1449
- const direction = useContext(DirectionContext);
1450
- return {
1451
- direction,
1452
- isRTL: direction === 'rtl'
1453
- };
1454
- };
1455
-
1456
- const ObserverParams = {
1457
- threshold: 0.1
1458
- };
1677
+ });
1459
1678
 
1460
- /**
1461
- * useHasIntersected.
1462
- * Use this custom hook to detect when an element has became visible inside the viewport. This hook checks only if the intersection happend.
1463
- * Once the intersection has happened the hook will not return false even if the element gets out of the viewport.
1464
- *
1465
- * @param elRef.elRef
1466
- * @param {object} [elRef] - node object that contains a react reference to the element that needs to be observed.
1467
- * @param {strimng} [loading = 'eager'] - string that contains the type of loading.
1468
- * @param elRef.loading
1469
- * @usage `const [hasIntersected] = useHasIntersected({imageRef,loading});`
1470
- */
1471
- const useHasIntersected = ({
1472
- elRef,
1473
- loading
1679
+ const Chip = ({
1680
+ label,
1681
+ value,
1682
+ onRemove,
1683
+ onClick,
1684
+ onKeyPress,
1685
+ className = undefined,
1686
+ 'aria-label': ariaLabel,
1687
+ 'aria-checked': ariaChecked,
1688
+ role,
1689
+ closeButton
1474
1690
  }) => {
1475
- const [hasIntersected, setHasIntersected] = useState(false);
1691
+ const isActionable = onClick || onKeyPress;
1692
+ const defaultRole = isActionable ? 'button' : undefined;
1693
+ const tabIndex = isActionable ? 0 : -1;
1476
1694
  const {
1477
- current
1478
- } = elRef || {};
1479
- const isValidReference = () => {
1480
- return elRef && current;
1481
- };
1482
- const handleOnIntersect = (entries, observer) => {
1483
- entries.forEach(entry => {
1484
- if (entry.isIntersecting) {
1485
- setHasIntersected(true);
1486
- observer.unobserve(current);
1487
- }
1488
- });
1489
- };
1695
+ isModern
1696
+ } = useTheme();
1697
+ const closeButtonReference = useRef(null);
1698
+ const previousCloseButtonShown = useRef();
1490
1699
  useEffect(() => {
1491
- let observer;
1492
- let didCancel = false;
1493
-
1494
- // Check if window is define for SSR and Old browsers fallback
1495
- if (typeof window === 'undefined' || !window.IntersectionObserver || !isValidReference()) {
1496
- setHasIntersected(true);
1497
- } else if (!didCancel) {
1498
- observer = new IntersectionObserver(handleOnIntersect, ObserverParams);
1499
- observer.observe(current);
1700
+ if (closeButtonReference.current != null && previousCloseButtonShown.current === false) {
1701
+ closeButtonReference.current?.focus();
1500
1702
  }
1501
- return () => {
1502
- didCancel = true;
1503
- if (observer) {
1504
- observer.unobserve(current);
1505
- }
1506
- };
1507
- }, [elRef]);
1508
- if (loading === 'eager') {
1509
- return [false];
1510
- }
1511
- return [hasIntersected];
1512
- };
1513
-
1514
- // eslint-disable-next-line import/extensions
1515
- function useMedia(query) {
1516
- return useSyncExternalStore(onStoreChange => {
1517
- const mediaQueryList = window.matchMedia(query);
1518
- mediaQueryList.addEventListener('change', onStoreChange);
1519
- return () => {
1520
- mediaQueryList.removeEventListener('change', onStoreChange);
1521
- };
1522
- }, () => typeof window !== 'undefined' ? window.matchMedia(query).matches : undefined, () => undefined);
1523
- }
1524
-
1525
- function useScreenSize(size) {
1526
- return useMedia(`(min-width: ${size}px)`);
1527
- }
1528
-
1529
- /**
1530
- * @deprecated Prefer `useScreenSize` instead.
1531
- */
1532
- const useLayout = () => {
1533
- const screenXs = useScreenSize(Breakpoint.EXTRA_SMALL);
1534
- const screenSm = useScreenSize(Breakpoint.SMALL);
1535
- const screenMd = useScreenSize(Breakpoint.MEDIUM);
1536
- const screenLg = useScreenSize(Breakpoint.LARGE);
1537
- const screenXl = useScreenSize(Breakpoint.EXTRA_LARGE);
1538
- return {
1539
- isMobile: screenSm != null ? !screenSm : undefined,
1540
- isExtraSmall: screenXs,
1541
- isSmall: screenSm,
1542
- isMedium: screenMd,
1543
- isLarge: screenLg,
1544
- isExtraLarge: screenXl
1545
- };
1703
+ previousCloseButtonShown.current = closeButtonReference.current != null;
1704
+ }, [onRemove]);
1705
+ return /*#__PURE__*/jsxs("div", {
1706
+ role: role ?? defaultRole,
1707
+ tabIndex: tabIndex,
1708
+ "aria-label": ariaLabel,
1709
+ "aria-checked": ariaChecked,
1710
+ className: classNames('np-chip', 'd-flex', 'align-items-center', 'justify-content-between', onRemove ? 'np-chip--removable' : '', className),
1711
+ ...(isActionable && {
1712
+ onClick,
1713
+ onKeyPress
1714
+ }),
1715
+ children: [isModern ? /*#__PURE__*/jsx(Body, {
1716
+ "aria-hidden": !!onRemove,
1717
+ type: Typography.BODY_DEFAULT_BOLD,
1718
+ children: label
1719
+ }) : /*#__PURE__*/jsx("span", {
1720
+ "aria-hidden": "false",
1721
+ className: "np-chip-label",
1722
+ children: label
1723
+ }), onRemove ? /*#__PURE__*/jsx(CloseButton, {
1724
+ ref: closeButtonReference,
1725
+ className: isModern ? `btn-unstyled` : `btn-unstyled m-l-1`,
1726
+ "aria-label": closeButton && closeButton['aria-label'],
1727
+ size: "sm",
1728
+ filled: isModern,
1729
+ onClick: onRemove
1730
+ }) : null]
1731
+ }, value);
1546
1732
  };
1547
1733
 
1548
- var messages$b = defineMessages({
1734
+ var messages$a = defineMessages({
1549
1735
  ariaLabel: {
1550
- id: "neptune.CloseButton.ariaLabel"
1736
+ id: "neptune.Chips.ariaLabel"
1551
1737
  }
1552
1738
  });
1553
1739
 
1554
- const CloseButton = /*#__PURE__*/forwardRef(function CloseButton({
1740
+ const Chips = ({
1741
+ chips,
1742
+ onChange,
1743
+ selected,
1555
1744
  'aria-label': ariaLabel,
1556
- size = Size.MEDIUM,
1557
- filled = false,
1558
1745
  className,
1559
- onClick,
1560
- isDisabled,
1561
- testId
1562
- }, reference) {
1746
+ multiple
1747
+ }) => {
1563
1748
  const intl = useIntl();
1564
- ariaLabel ??= intl.formatMessage(messages$b.ariaLabel);
1565
- const Icon = filled ? CrossCircleFill : Cross;
1566
- return /*#__PURE__*/jsx("button", {
1567
- ref: reference,
1568
- type: "button",
1569
- className: classNames('np-close-button', 'close btn-link', 'text-no-decoration', {
1570
- 'np-close-button--large': size === Size.MEDIUM,
1571
- 'np-close-button--x-large': size === Size.LARGE
1572
- }, className),
1749
+ const isSelected = value => Array.isArray(selected) ? selected.includes(value) : selected === value;
1750
+ const handleOnChange = (selectedValue, isCurrentlyEnabled) => {
1751
+ onChange({
1752
+ isEnabled: !isCurrentlyEnabled,
1753
+ selectedValue
1754
+ });
1755
+ };
1756
+ return /*#__PURE__*/jsx("div", {
1757
+ className: classNames('np-chips d-flex', className),
1573
1758
  "aria-label": ariaLabel,
1574
- "aria-disabled": isDisabled,
1575
- disabled: isDisabled,
1576
- "data-testid": testId,
1577
- onClick: onClick,
1578
- children: /*#__PURE__*/jsx(Icon, {
1579
- size: size === Size.SMALL ? 16 : 24
1759
+ role: !multiple ? 'radiogroup' : undefined,
1760
+ children: chips.map(chip => {
1761
+ const chipSelected = isSelected(chip.value);
1762
+ return /*#__PURE__*/jsx(Chip, {
1763
+ value: chip.value,
1764
+ label: chip.label,
1765
+ closeButton: {
1766
+ 'aria-label': intl.formatMessage(messages$a.ariaLabel, {
1767
+ choice: chip.label
1768
+ })
1769
+ },
1770
+ className: classNames('text-xs-nowrap', {
1771
+ 'np-chip--selected': chipSelected,
1772
+ 'p-r-1': multiple && chipSelected
1773
+ }),
1774
+ ...(multiple && chipSelected ? {
1775
+ onRemove: () => handleOnChange(chip.value, chipSelected),
1776
+ 'aria-label': chip.label
1777
+ } : {
1778
+ onClick: () => handleOnChange(chip.value, chipSelected),
1779
+ onKeyPress: () => handleOnChange(chip.value, chipSelected),
1780
+ role: !multiple ? 'radio' : undefined,
1781
+ 'aria-checked': chipSelected
1782
+ })
1783
+ }, chip.value);
1580
1784
  })
1581
1785
  });
1582
- });
1786
+ };
1583
1787
 
1584
- const EXIT_ANIMATION = 350;
1585
- const SlidingPanel = /*#__PURE__*/forwardRef(({
1586
- position = 'left',
1587
- open,
1588
- showSlidingPanelBorder,
1589
- slidingPanelPositionFixed,
1788
+ const CircularButton = ({
1590
1789
  className,
1591
1790
  children,
1791
+ disabled,
1792
+ icon,
1793
+ priority = Priority.PRIMARY,
1794
+ type = ControlType.ACCENT,
1592
1795
  ...rest
1593
- }, reference) => {
1594
- const localReference = useRef(null);
1595
- useImperativeHandle(reference, () => localReference.current, []);
1596
- return /*#__PURE__*/createElement(CSSTransition, {
1597
- ...rest,
1598
- key: `sliding-panel--open-${position}`,
1599
- nodeRef: localReference,
1600
- in: open
1601
- // Wait for animation to finish before unmount.
1602
- ,
1603
- timeout: {
1604
- enter: 0,
1605
- exit: EXIT_ANIMATION
1606
- },
1607
- classNames: "sliding-panel",
1608
- appear: true,
1609
- unmountOnExit: true
1610
- }, /*#__PURE__*/jsx("div", {
1611
- ref: localReference,
1612
- className: classNames('sliding-panel', `sliding-panel--open-${position}`, showSlidingPanelBorder && `sliding-panel--border-${position}`, slidingPanelPositionFixed && 'sliding-panel--fixed', className),
1613
- children: children
1614
- }));
1615
- });
1796
+ }) => {
1797
+ const classes = classNames('btn np-btn', typeClassMap[type], priorityClassMap[priority]);
1798
+ const iconElement = Number(icon.props.size) !== 24 ? /*#__PURE__*/cloneElement(icon, {
1799
+ size: 24
1800
+ }) : icon;
1801
+ return /*#__PURE__*/jsxs("label", {
1802
+ className: classNames('np-circular-btn', priority, type, disabled && 'disabled', className),
1803
+ children: [/*#__PURE__*/jsx("input", {
1804
+ type: "button",
1805
+ "aria-label": children,
1806
+ className: classes,
1807
+ disabled: disabled,
1808
+ ...rest
1809
+ }), iconElement, /*#__PURE__*/jsx(Body, {
1810
+ as: "span",
1811
+ className: "np-circular-btn__label",
1812
+ type: Typography.BODY_DEFAULT_BOLD,
1813
+ children: children
1814
+ })]
1815
+ });
1816
+ };
1616
1817
 
1617
- const Drawer = ({
1618
- children,
1619
- className,
1620
- footerContent,
1621
- headerTitle,
1622
- onClose,
1623
- open,
1624
- position
1818
+ const FocusBoundary = ({
1819
+ children
1625
1820
  }) => {
1626
- logActionRequiredIf('Drawer now expects `onClose`, and will soon make this prop required. Please update your usage to provide it.', !onClose);
1627
- const {
1628
- isMobile
1629
- } = useLayout();
1630
- const titleId = useId();
1631
- return /*#__PURE__*/jsx(Dimmer$1, {
1632
- open: open,
1633
- onClose: onClose,
1634
- children: /*#__PURE__*/jsx(SlidingPanel, {
1635
- open: open,
1636
- position: isMobile ? Position.BOTTOM : position,
1637
- children: /*#__PURE__*/jsxs("div", {
1638
- role: "dialog",
1639
- "aria-modal": true,
1640
- "aria-labelledby": headerTitle ? titleId : undefined,
1641
- className: classNames('np-drawer', className),
1642
- children: [/*#__PURE__*/jsxs("div", {
1643
- className: classNames('np-drawer-header', {
1644
- 'np-drawer-header--withborder': headerTitle
1645
- }),
1646
- children: [headerTitle && /*#__PURE__*/jsx(Title, {
1647
- id: titleId,
1648
- type: Typography.TITLE_BODY,
1649
- children: headerTitle
1650
- }), /*#__PURE__*/jsx(CloseButton, {
1651
- onClick: onClose
1652
- })]
1653
- }), children && /*#__PURE__*/jsx("div", {
1654
- className: classNames('np-drawer-content'),
1655
- children: children
1656
- }), footerContent && /*#__PURE__*/jsx("div", {
1657
- className: classNames('np-drawer-footer'),
1658
- children: footerContent
1659
- })]
1660
- })
1821
+ const wrapperReference = useRef(null);
1822
+ useEffect(() => {
1823
+ wrapperReference.current?.focus({
1824
+ preventScroll: true
1825
+ });
1826
+ }, []);
1827
+ return /*#__PURE__*/jsx(FocusScope, {
1828
+ contain: true,
1829
+ restoreFocus: true,
1830
+ children: /*#__PURE__*/jsx("div", {
1831
+ ref: wrapperReference,
1832
+ tabIndex: -1,
1833
+ children: children
1661
1834
  })
1662
1835
  });
1663
1836
  };
1664
- Drawer.propTypes = {
1665
- /** The content to appear in the drawer body. */
1666
- children: PropTypes.node,
1667
- className: PropTypes.string,
1668
- /** The content to appear in the drawer footer. */
1669
- footerContent: PropTypes.node,
1670
- /** The content to appear in the drawer header. */
1671
- headerTitle: PropTypes.node,
1672
- /** The action to perform on close click. */
1673
- onClose: PropTypes.func,
1674
- /** The status of Drawer either open or not. */
1675
- open: PropTypes.bool,
1676
- /** The placement of Drawer on the screen either left or right. On mobile it will default to bottom. */
1677
- position: PropTypes.oneOf(['left', 'right', 'bottom'])
1678
- };
1679
- Drawer.defaultProps = {
1680
- children: null,
1681
- className: undefined,
1682
- footerContent: null,
1683
- headerTitle: null,
1684
- onClose: null,
1685
- open: false,
1686
- position: Position.RIGHT
1687
- };
1688
- var Drawer$1 = Drawer;
1689
1837
 
1690
- const INITIAL_Y_POSITION = 0;
1691
- const CONTENT_SCROLL_THRESHOLD = 1;
1692
- const MOVE_OFFSET_THRESHOLD = 50;
1838
+ function withNextPortalWrapper(Component) {
1839
+ return function (props) {
1840
+ const [mounted, setMounted] = useState(false);
1841
+ useEffect(() => {
1842
+ setMounted(true);
1843
+ }, [setMounted]);
1844
+ return mounted ? /*#__PURE__*/createPortal( /*#__PURE__*/jsx(Component, {
1845
+ ...props
1846
+ }), document.body) : null;
1847
+ };
1848
+ }
1849
+
1693
1850
  /**
1694
- * Neptune: https://transferwise.github.io/neptune/components/bottom-sheet/
1695
- *
1696
- * Neptune Web: https://transferwise.github.io/neptune-web/components/overlays/BottomSheet
1697
- *
1851
+ * Dimmer state management inspired by Material UI's ModalManager (https://github.com/mui-org/material-ui)
1698
1852
  */
1699
- const BottomSheet$1 = props => {
1700
- const bottomSheetReference = useRef(null);
1701
- const topBarReference = useRef(null);
1702
- const contentReference = useRef(null);
1703
- const [pressed, setPressed] = useState(false);
1704
- /**
1705
- * Used to track `requestAnimationFrame` requests
1706
- *
1707
- * @see https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame#return_value
1708
- */
1709
- const animationId = useRef(0);
1853
+ class DimmerManager {
1710
1854
  /**
1711
- * Difference between initial coordinate ({@link initialYCoordinate})
1712
- * and new position (when user moves component), it's get calculated on `onTouchMove` and `onMouseMove` events
1713
- *
1714
- * @see {@link calculateOffsetAfterMove}
1855
+ * Dimmer refs
1715
1856
  */
1716
- const moveOffset = useRef(0);
1717
- const initialYCoordinate = useRef(0);
1718
- // apply shadow to the bottom of top-bar when scroll over content
1719
- useConditionalListener({
1720
- attachListener: props.open && !isServerSide(),
1721
- callback: () => {
1722
- if (topBarReference.current !== null) {
1723
- const {
1724
- classList
1725
- } = topBarReference.current;
1726
- if (!isContentScrollPositionAtTop()) {
1727
- classList.add('np-bottom-sheet--top-bar--shadow');
1728
- } else {
1729
- classList.remove('np-bottom-sheet--top-bar--shadow');
1730
- }
1731
- }
1732
- },
1733
- eventType: 'scroll',
1734
- parent: isServerSide() ? undefined : document
1735
- });
1736
- function move(newHeight) {
1737
- if (bottomSheetReference.current !== null) {
1738
- bottomSheetReference.current.style.transform = `translateY(${newHeight}px)`;
1739
- }
1857
+ dimmers;
1858
+ constructor() {
1859
+ this.dimmers = [];
1740
1860
  }
1741
- function close(event) {
1742
- setPressed(false);
1743
- moveOffset.current = INITIAL_Y_POSITION;
1744
- if (bottomSheetReference.current !== null) {
1745
- bottomSheetReference.current.style.removeProperty('transform');
1861
+ add(dimmer) {
1862
+ let dimmerIndex = this.dimmers.indexOf(dimmer);
1863
+ if (dimmerIndex !== -1) {
1864
+ return dimmerIndex;
1746
1865
  }
1747
- if (props.onClose) {
1748
- props.onClose(event);
1866
+ dimmerIndex = this.dimmers.length;
1867
+ this.dimmers.push(dimmer);
1868
+ return dimmerIndex;
1869
+ }
1870
+ remove(dimmer) {
1871
+ const dimmerIndex = this.dimmers.indexOf(dimmer);
1872
+ if (dimmerIndex !== -1) {
1873
+ this.dimmers.splice(dimmerIndex, 1);
1749
1874
  }
1875
+ return dimmerIndex;
1750
1876
  }
1751
- const onSwipeStart = event => {
1752
- initialYCoordinate.current = ('touches' in event ? event.touches[0] : event).clientY;
1753
- setPressed(true);
1877
+ isTop(dimmer) {
1878
+ return this.dimmers.length > 0 && this.dimmers[this.dimmers.length - 1] === dimmer;
1879
+ }
1880
+ }
1881
+
1882
+ const EXIT_ANIMATION$1 = 350;
1883
+ const dimmerManager = new DimmerManager();
1884
+ const handleTouchMove = event => {
1885
+ const isTouchedElementDimmer = event.target.classList.contains('dimmer');
1886
+ // disable scroll on iOS devices for Dimmer area
1887
+ // this is because of bug in WebKit https://bugs.webkit.org/show_bug.cgi?id=220908
1888
+ // note: scrolling still works for children(s) as expected
1889
+ if (isIosDevice() && isTouchedElementDimmer) {
1890
+ event.stopPropagation();
1891
+ event.preventDefault();
1892
+ }
1893
+ };
1894
+ const Dimmer = ({
1895
+ children,
1896
+ className,
1897
+ disableClickToClose = false,
1898
+ contentPosition,
1899
+ fadeContentOnEnter = false,
1900
+ fadeContentOnExit = false,
1901
+ open = false,
1902
+ scrollable = false,
1903
+ transparent = false,
1904
+ onClose
1905
+ }) => {
1906
+ const [hasNotExited, setHasNotExited] = useState(false);
1907
+ const dimmerReference = useRef(null);
1908
+ const closeOnClick = event => {
1909
+ if (event.target === dimmerReference.current) {
1910
+ onClose?.(event);
1911
+ }
1754
1912
  };
1755
- const onSwipeMove = event => {
1756
- if (pressed) {
1757
- const {
1758
- clientY
1759
- } = 'touches' in event ? event.touches[0] : event;
1760
- const offset = calculateOffsetAfterMove(clientY);
1761
- // check whether move is to the bottom only and content scroll position is at the top
1762
- if (offset > INITIAL_Y_POSITION && isContentScrollPositionAtTop()) {
1763
- moveOffset.current = offset;
1764
- animationId.current = requestAnimationFrame(() => {
1765
- if (animationId.current !== undefined && bottomSheetReference.current !== null) {
1766
- move(offset);
1767
- }
1768
- });
1769
- }
1913
+ const handleClick = event => {
1914
+ if (disableClickToClose || !onClose) {
1915
+ return;
1770
1916
  }
1917
+ closeOnClick(event);
1771
1918
  };
1772
- function onSwipeEnd(event) {
1773
- // stop moving component
1774
- cancelAnimationFrame(animationId.current);
1775
- setPressed(false);
1776
- // check whether move down is strong enough
1777
- // and content scroll position is at the top to close the component
1778
- if (moveOffset.current > MOVE_OFFSET_THRESHOLD && isContentScrollPositionAtTop()) {
1779
- close(event);
1919
+ const handleKeyDown = useCallback(event => {
1920
+ if (event.key !== 'Escape') {
1921
+ return;
1780
1922
  }
1781
- // otherwise move component back to default (initial) position
1782
- else {
1783
- move(INITIAL_Y_POSITION);
1923
+ event.stopPropagation();
1924
+ if (onClose && dimmerReference.current && dimmerManager.isTop(dimmerReference.current)) {
1925
+ onClose(event);
1784
1926
  }
1785
- moveOffset.current = INITIAL_Y_POSITION;
1786
- }
1787
- function isContentScrollPositionAtTop() {
1788
- return contentReference?.current?.scrollTop !== undefined && contentReference.current.scrollTop <= CONTENT_SCROLL_THRESHOLD;
1789
- }
1790
- /**
1791
- * Calculates how hard user moves component,
1792
- * result value used to determine whether to hide component or re-position to default state
1793
- *
1794
- * @param afterMoveYCoordinate
1795
- */
1796
- function calculateOffsetAfterMove(afterMoveYCoordinate) {
1797
- return afterMoveYCoordinate - initialYCoordinate.current;
1798
- }
1799
- /**
1800
- * Set `max-height` for content part (in order to keep it scrollable for content overflow cases) of the component
1801
- * and ensures space for safe zone (32px) at the top.
1802
- */
1803
- function setContentMaxHeight() {
1804
- const safeZoneHeight = '64px';
1805
- const topbarHeight = '32px';
1806
- const windowHight = isServerSide() ? 0 : window.innerHeight;
1807
- /**
1808
- * Calculate _real_ height of the screen (taking into account parts of browser interface).
1809
- *
1810
- * See https://css-tricks.com/the-trick-to-viewport-units-on-mobile for more details.
1811
- */
1812
- const screenHeight = `${windowHight * 0.01 * 100}px`;
1813
- return {
1814
- maxHeight: `calc(${screenHeight} - ${safeZoneHeight} - ${topbarHeight})`
1927
+ }, [onClose]);
1928
+ const onEnter = () => {
1929
+ setHasNotExited(true);
1930
+ if (dimmerReference.current) {
1931
+ dimmerManager.add(dimmerReference.current);
1932
+ }
1933
+ };
1934
+ const onExited = () => {
1935
+ setHasNotExited(false);
1936
+ if (dimmerReference.current) {
1937
+ dimmerManager.remove(dimmerReference.current);
1938
+ }
1939
+ };
1940
+ useEffect(() => {
1941
+ const localReferenceCopy = dimmerReference.current;
1942
+ if (open) {
1943
+ document.addEventListener('keydown', handleKeyDown);
1944
+ localReferenceCopy?.addEventListener('touchmove', handleTouchMove, {
1945
+ passive: true
1946
+ });
1947
+ }
1948
+ return () => {
1949
+ document.removeEventListener('keydown', handleKeyDown);
1950
+ localReferenceCopy?.removeEventListener('touchmove', handleTouchMove);
1815
1951
  };
1816
- }
1817
- const is400Zoom = useMedia(`(max-width: ${Breakpoint.ZOOM_400}px)`);
1818
- return is400Zoom ? /*#__PURE__*/jsx(Drawer$1, {
1819
- open: props.open,
1820
- className: props.className,
1821
- onClose: close,
1822
- children: props.children
1823
- }) : /*#__PURE__*/jsx(Dimmer$1, {
1824
- open: props.open,
1825
- fadeContentOnEnter: true,
1826
- fadeContentOnExit: true,
1827
- onClose: close,
1828
- children: /*#__PURE__*/jsx(SlidingPanel, {
1829
- ref: bottomSheetReference,
1830
- open: props.open,
1831
- position: Position.BOTTOM,
1832
- className: classNames('np-bottom-sheet', props.className),
1833
- children: /*#__PURE__*/jsxs("div", {
1834
- role: "dialog",
1835
- "aria-modal": true,
1836
- onTouchStart: onSwipeStart,
1837
- onTouchMove: onSwipeMove,
1838
- onTouchEnd: onSwipeEnd,
1839
- onMouseDown: onSwipeStart,
1840
- onMouseMove: onSwipeMove,
1841
- onMouseUp: onSwipeEnd,
1842
- children: [/*#__PURE__*/jsxs("div", {
1843
- ref: topBarReference,
1844
- className: "np-bottom-sheet--top-bar",
1845
- children: [/*#__PURE__*/jsx("div", {
1846
- className: "np-bottom-sheet--handler"
1847
- }), /*#__PURE__*/jsx(CloseButton, {
1848
- size: "sm",
1849
- className: "sr-only np-bottom-sheet--close-btn",
1850
- onClick: close
1851
- })]
1852
- }), /*#__PURE__*/jsx("div", {
1853
- ref: contentReference,
1854
- style: setContentMaxHeight(),
1855
- className: "np-bottom-sheet--content",
1856
- children: props.children
1857
- })]
1952
+ }, [handleKeyDown, open]);
1953
+ return /*#__PURE__*/jsx(DimmerWrapper, {
1954
+ open: open,
1955
+ hasNotExited: hasNotExited,
1956
+ children: /*#__PURE__*/jsx(CSSTransition, {
1957
+ nodeRef: dimmerReference,
1958
+ in: open,
1959
+ appear: true
1960
+ // Wait for animation to finish before unmount.
1961
+ ,
1962
+ timeout: {
1963
+ enter: 0,
1964
+ exit: EXIT_ANIMATION$1
1965
+ },
1966
+ classNames: {
1967
+ enter: classNames({
1968
+ 'dimmer--enter-fade': fadeContentOnEnter
1969
+ }),
1970
+ enterDone: classNames('dimmer--enter-done', {
1971
+ 'dimmer--enter-fade': fadeContentOnEnter
1972
+ }),
1973
+ exit: classNames('dimmer--exit', {
1974
+ 'dimmer--exit-fade': fadeContentOnExit
1975
+ })
1976
+ },
1977
+ unmountOnExit: true,
1978
+ onEnter: onEnter,
1979
+ onExited: onExited,
1980
+ children: /*#__PURE__*/jsx(DimmerContentWrapper, {
1981
+ scrollBody: !transparent,
1982
+ children: /*#__PURE__*/jsx(FocusBoundary, {
1983
+ children: /*#__PURE__*/jsx("div", {
1984
+ ref: dimmerReference,
1985
+ className: classNames('dimmer', {
1986
+ 'dimmer--scrollable': scrollable,
1987
+ 'dimmer--transparent': transparent
1988
+ }, className),
1989
+ role: "presentation",
1990
+ onClick: handleClick,
1991
+ children: /*#__PURE__*/jsx("div", {
1992
+ className: classNames('dimmer-content-positioner', contentPosition != null && ['d-flex justify-content-center', {
1993
+ 'align-items-start': contentPosition === 'top',
1994
+ 'align-items-center': contentPosition === 'center',
1995
+ 'align-items-end': contentPosition === 'bottom'
1996
+ }]),
1997
+ children: children
1998
+ })
1999
+ })
2000
+ })
1858
2001
  })
1859
2002
  })
1860
2003
  });
1861
2004
  };
1862
-
1863
- function SelectOption({
1864
- selected: selectedValueProp = undefined,
1865
- ...props
1866
- }) {
1867
- const rootRef = useRef(null);
1868
- const [selected, setSelected] = useState(selectedValueProp);
1869
- const [showMenu, setShowMenu] = useState(false);
1870
- function handleOnClick(status) {
2005
+ const DimmerWrapper = ({
2006
+ open,
2007
+ hasNotExited,
2008
+ children
2009
+ }) => {
2010
+ const {
2011
+ screenMode,
2012
+ theme
2013
+ } = useTheme();
2014
+ return open || hasNotExited ? /*#__PURE__*/jsx(ThemeProvider, {
2015
+ theme: "personal",
2016
+ screenMode: theme === 'personal' ? screenMode : 'light',
2017
+ isNotRootProvider: true,
2018
+ children: children
2019
+ }) : /*#__PURE__*/jsx(Fragment, {
2020
+ children: children
2021
+ });
2022
+ };
2023
+ const DimmerContentWrapper = ({
2024
+ children,
2025
+ scrollBody
2026
+ }) => {
2027
+ useEffect(() => {
2028
+ if (scrollBody) {
2029
+ addNoScrollClass();
2030
+ }
1871
2031
  return () => {
1872
- console.log('handleOnClick', status);
1873
- setShowMenu(status);
2032
+ if (scrollBody) {
2033
+ removeNoScrollClass();
2034
+ }
1874
2035
  };
1875
- }
1876
- function handleOnSelect(data) {
2036
+ }, [scrollBody]);
2037
+ return children;
2038
+ };
2039
+ var Dimmer$1 = withNextPortalWrapper(Dimmer);
2040
+
2041
+ const useConditionalListener = ({
2042
+ attachListener,
2043
+ callback,
2044
+ eventType,
2045
+ parent
2046
+ }) => {
2047
+ useEffect(() => {
2048
+ if (attachListener && !isUndefined(parent)) {
2049
+ parent.addEventListener(eventType, callback, true);
2050
+ }
1877
2051
  return () => {
1878
- setShowMenu(false);
1879
- setSelected(data);
2052
+ if (!isUndefined(parent)) {
2053
+ parent.removeEventListener(eventType, callback, true);
2054
+ }
1880
2055
  };
1881
- }
1882
- const isSelected = selected !== undefined;
1883
- const {
1884
- isMobile
1885
- } = useLayout();
1886
- const options = /*#__PURE__*/jsxs(Section, {
1887
- children: [/*#__PURE__*/jsx(Header, {
1888
- title: "Payment Methods"
1889
- }), /*#__PURE__*/jsx(Option$2, {
1890
- showMediaAtAllSizes: true,
1891
- media: /*#__PURE__*/jsx(Flag, {
1892
- code: "gbp"
1893
- }),
1894
- title: "Wise GBP balance",
1895
- content: /*#__PURE__*/jsxs(Fragment, {
1896
- children: [/*#__PURE__*/jsx("span", {
1897
- children: "300 GBP available"
1898
- }), /*#__PURE__*/jsx("span", {
1899
- children: "0 GBP in fees, should arrive in seconds"
1900
- })]
1901
- }),
1902
- onClick: handleOnSelect({
1903
- media: /*#__PURE__*/jsx(Flag, {
1904
- code: "gbp"
1905
- }),
1906
- title: 'Wise GBP balance'
1907
- })
1908
- }), /*#__PURE__*/jsx(Option$2, {
1909
- media: /*#__PURE__*/jsx(Card$3, {}),
1910
- title: "Credit card",
1911
- content: /*#__PURE__*/jsxs(Fragment, {
1912
- children: [/*#__PURE__*/jsx("div", {
1913
- children: "Transfer the money to Wise using your bank account."
1914
- }), /*#__PURE__*/jsx("div", {
1915
- children: "0.32 GBP in fees, should arrive in seconds"
1916
- })]
1917
- }),
1918
- onClick: handleOnSelect({
1919
- media: /*#__PURE__*/jsx(Card$3, {}),
1920
- title: 'Credit card',
1921
- content: 'Pay with your credit card'
1922
- })
1923
- }), /*#__PURE__*/jsx(Option$2, {
1924
- media: /*#__PURE__*/jsx(Card$3, {}),
1925
- title: "Debit card",
1926
- content: /*#__PURE__*/jsxs(Fragment, {
1927
- children: [/*#__PURE__*/jsx("span", {
1928
- children: "Send from your Visa or Mastercard."
1929
- }), /*#__PURE__*/jsx("span", {
1930
- children: "0.74 GBP in fees, should arrive in seconds"
1931
- })]
1932
- }),
1933
- onClick: handleOnSelect({
1934
- media: /*#__PURE__*/jsx(Card$3, {}),
1935
- title: 'Debit card',
1936
- content: 'Pay with your debit card'
1937
- })
1938
- }), /*#__PURE__*/jsx(Option$2, {
1939
- media: /*#__PURE__*/jsx(Bank, {}),
1940
- title: "Swift Transfer",
1941
- content: /*#__PURE__*/jsxs(Fragment, {
1942
- children: [/*#__PURE__*/jsx("span", {
1943
- children: "Send money internationally. Your bank will charge you extra fees."
1944
- }), /*#__PURE__*/jsx("span", {
1945
- children: "0.32 GBP in fees, should arrive by Thursday"
1946
- })]
1947
- })
1948
- }), /*#__PURE__*/jsx(Option$2, {
1949
- media: /*#__PURE__*/jsx(BankTransfer, {}),
1950
- title: "Bank Transfer",
1951
- content: /*#__PURE__*/jsxs(Fragment, {
1952
- children: [/*#__PURE__*/jsx("span", {
1953
- children: "Transfer the money to Wise using your bank account."
1954
- }), /*#__PURE__*/jsx("span", {
1955
- children: "0.32 GBP in fees, should arrive in seconds"
1956
- })]
1957
- })
1958
- }), /*#__PURE__*/jsx(Option$2, {
1959
- media: /*#__PURE__*/jsx(Card$3, {}),
1960
- title: "Debit card"
1961
- }), /*#__PURE__*/jsx(Option$2, {
1962
- media: /*#__PURE__*/jsx(Bank, {}),
1963
- title: "Swift"
1964
- }), /*#__PURE__*/jsx(Option$2, {
1965
- media: /*#__PURE__*/jsx(BankTransfer, {}),
1966
- title: "Bank Transfer"
1967
- })]
1968
- });
1969
- return /*#__PURE__*/jsxs(Fragment, {
1970
- children: [/*#__PURE__*/jsx(Option$2, {
1971
- ref: rootRef,
1972
- media: isSelected ? selected.media : props.media,
1973
- title: isSelected ? selected.title : props.title,
1974
- content: isSelected ? selected.content : props.content,
1975
- className: classNames('np-select-option', {
1976
- 'np-select-option-selected': isSelected
1977
- }, props.className),
1978
- button: isSelected ? /*#__PURE__*/jsx(Chevron, {}) : /*#__PURE__*/jsx(ActionButton, {
1979
- onClick: handleOnClick(true),
1980
- children: "Choose"
1981
- }),
1982
- onClick: isSelected ? handleOnClick(true) : undefined
1983
- }), isMobile ? /*#__PURE__*/jsx(BottomSheet$1, {
1984
- open: showMenu,
1985
- onClose: handleOnClick(false),
1986
- children: options
1987
- }) : /*#__PURE__*/jsx(Panel, {
1988
- open: showMenu,
1989
- flip: false,
1990
- altAxis: true,
1991
- anchorRef: rootRef,
1992
- anchorWidth: false,
1993
- position: Position.BOTTOM,
1994
- onClose: handleOnClick(false),
1995
- children: /*#__PURE__*/jsx("div", {
1996
- className: "np-select-option-list p-a-2",
1997
- children: options
1998
- })
1999
- })]
2000
- });
2001
- }
2002
-
2003
- const iconTypeMap = {
2004
- positive: Check,
2005
- neutral: Info$1,
2006
- warning: Alert$1,
2007
- negative: Cross,
2008
- pending: ClockBorderless,
2009
- info: Info$1,
2010
- error: Cross,
2011
- success: Check
2056
+ }, [attachListener, callback, eventType, parent]);
2012
2057
  };
2013
- const StatusIcon = ({
2014
- sentiment = 'neutral',
2015
- size = 'md'
2058
+
2059
+ const DirectionContext = /*#__PURE__*/createContext(Direction.LTR);
2060
+ const DirectionProvider = ({
2061
+ direction,
2062
+ children
2016
2063
  }) => {
2017
- const Icon = iconTypeMap[sentiment];
2018
- const iconColor = sentiment === 'warning' || sentiment === 'pending' ? 'dark' : 'light';
2019
- return /*#__PURE__*/jsx("span", {
2020
- "data-testid": "status-icon",
2021
- className: classNames('status-circle', 'status-circle-' + size, sentiment),
2022
- children: /*#__PURE__*/jsx(Icon, {
2023
- className: classNames('status-icon', iconColor)
2024
- })
2064
+ return /*#__PURE__*/jsx(DirectionContext.Provider, {
2065
+ value: direction,
2066
+ children: children
2025
2067
  });
2026
2068
  };
2027
2069
 
2028
- const reader = new commonmark.Parser();
2029
- const writer = new commonmark.HtmlRenderer({
2030
- safe: true
2031
- });
2032
- const NODE_TYPE_LIST = Object.values(MarkdownNodeType);
2033
- function Markdown({
2034
- as: Element = 'div',
2035
- allowList,
2036
- blockList,
2037
- config,
2038
- className,
2039
- children
2040
- }) {
2041
- if (!children) {
2042
- return null;
2043
- }
2044
- const linkTarget = config?.link?.target ?? '_self';
2045
- const paragraphClass = config?.paragraph?.className ?? '';
2046
- if (allowList != null && blockList != null) {
2047
- logActionRequired('Markdown supports only one of `allowList` or `blockList` to be used at a time. `blockList` will be ignored.');
2048
- }
2049
- const parser = nodes => {
2050
- const parsed = reader.parse(nodes);
2051
- const toExclude = allowList != null ? NODE_TYPE_LIST.filter(type => !allowList.includes(type)) : blockList;
2052
- return toExclude != null ? stripNodes({
2053
- parsed,
2054
- blockList: toExclude
2055
- }) : parsed;
2056
- };
2057
- const createMarkup = () => {
2058
- const parsed = parser(children);
2059
- return writer.render(parsed).replace(/<a href="/g, `<a target="${linkTarget}" href="`).replace(/<p>/g, `<p class="${paragraphClass}">`);
2070
+ const useDirection = () => {
2071
+ const direction = useContext(DirectionContext);
2072
+ return {
2073
+ direction,
2074
+ isRTL: direction === 'rtl'
2060
2075
  };
2061
- return /*#__PURE__*/jsx(Element, {
2062
- className: className,
2063
- dangerouslySetInnerHTML: {
2064
- __html: createMarkup()
2065
- }
2066
- });
2067
- }
2068
- function stripNodes({
2069
- blockList,
2070
- parsed
2071
- }) {
2072
- if (!parsed) {
2073
- return parsed;
2074
- }
2075
- const walker = parsed.walker();
2076
- for (let event = walker.next(); event != null; event = walker.next()) {
2077
- const {
2078
- node
2079
- } = event;
2080
- if (blockList.includes(node.type) && !event.entering) {
2081
- while (node.firstChild != null) {
2082
- node.insertBefore(node.firstChild);
2083
- }
2084
- node.unlink();
2085
- }
2086
- }
2087
- return parsed;
2088
- }
2089
-
2090
- const allowList = [MarkdownNodeType.STRONG];
2091
- const InlineMarkdown = props => {
2092
- return /*#__PURE__*/jsx(Markdown, {
2093
- ...props,
2094
- as: "span",
2095
- allowList: allowList,
2096
- blockList: undefined
2097
- });
2098
- };
2099
- InlineMarkdown.propTypes = {
2100
- children: PropTypes.string.isRequired,
2101
- className: PropTypes.string
2102
2076
  };
2103
- InlineMarkdown.defaultProps = {
2104
- className: undefined
2077
+
2078
+ const ObserverParams = {
2079
+ threshold: 0.1
2105
2080
  };
2106
2081
 
2107
- var AlertArrowPosition;
2108
- (function (AlertArrowPosition) {
2109
- AlertArrowPosition["TOP_LEFT"] = "up-left";
2110
- AlertArrowPosition["TOP"] = "up-center";
2111
- AlertArrowPosition["TOP_RIGHT"] = "up-right";
2112
- AlertArrowPosition["BOTTOM_LEFT"] = "down-left";
2113
- AlertArrowPosition["BOTTOM"] = "down-center";
2114
- AlertArrowPosition["BOTTOM_RIGHT"] = "down-right";
2115
- })(AlertArrowPosition || (AlertArrowPosition = {}));
2116
- function resolveType(type) {
2117
- switch (type) {
2118
- case 'success':
2119
- return 'positive';
2120
- case 'info':
2121
- return 'neutral';
2122
- case 'error':
2123
- return 'negative';
2124
- default:
2125
- return type;
2126
- }
2127
- }
2128
- function Alert({
2129
- arrow,
2130
- action,
2131
- children,
2132
- className,
2133
- dismissible,
2134
- icon,
2135
- onDismiss,
2136
- message,
2137
- size,
2138
- title,
2139
- type = 'neutral',
2140
- variant = 'desktop'
2141
- }) {
2142
- useEffect(() => {
2143
- if (arrow !== undefined) {
2144
- logActionRequired("Alert component doesn't support 'arrow' anymore, use 'InlineAlert' instead.");
2145
- }
2146
- }, [arrow]);
2147
- useEffect(() => {
2148
- if (children !== undefined) {
2149
- logActionRequired("Alert component doesn't support 'children' anymore, use 'message' instead.");
2150
- }
2151
- }, [children]);
2152
- useEffect(() => {
2153
- if (dismissible !== undefined) {
2154
- logActionRequired("Alert component doesn't support 'dismissible' anymore, use 'onDismiss' instead.");
2155
- }
2156
- }, [dismissible]);
2157
- useEffect(() => {
2158
- if (size !== undefined) {
2159
- logActionRequired("Alert component doesn't support 'size' anymore, please remove that prop.");
2160
- }
2161
- }, [size]);
2162
- const resolvedType = resolveType(type);
2082
+ /**
2083
+ * useHasIntersected.
2084
+ * Use this custom hook to detect when an element has became visible inside the viewport. This hook checks only if the intersection happend.
2085
+ * Once the intersection has happened the hook will not return false even if the element gets out of the viewport.
2086
+ *
2087
+ * @param elRef.elRef
2088
+ * @param {object} [elRef] - node object that contains a react reference to the element that needs to be observed.
2089
+ * @param {strimng} [loading = 'eager'] - string that contains the type of loading.
2090
+ * @param elRef.loading
2091
+ * @usage `const [hasIntersected] = useHasIntersected({imageRef,loading});`
2092
+ */
2093
+ const useHasIntersected = ({
2094
+ elRef,
2095
+ loading
2096
+ }) => {
2097
+ const [hasIntersected, setHasIntersected] = useState(false);
2098
+ const {
2099
+ current
2100
+ } = elRef || {};
2101
+ const isValidReference = () => {
2102
+ return elRef && current;
2103
+ };
2104
+ const handleOnIntersect = (entries, observer) => {
2105
+ entries.forEach(entry => {
2106
+ if (entry.isIntersecting) {
2107
+ setHasIntersected(true);
2108
+ observer.unobserve(current);
2109
+ }
2110
+ });
2111
+ };
2163
2112
  useEffect(() => {
2164
- if (resolvedType !== type) {
2165
- logActionRequired(`Alert component has deprecated '${type}' value for the 'type' prop. Please use '${resolvedType}' instead.`);
2113
+ let observer;
2114
+ let didCancel = false;
2115
+
2116
+ // Check if window is define for SSR and Old browsers fallback
2117
+ if (typeof window === 'undefined' || !window.IntersectionObserver || !isValidReference()) {
2118
+ setHasIntersected(true);
2119
+ } else if (!didCancel) {
2120
+ observer = new IntersectionObserver(handleOnIntersect, ObserverParams);
2121
+ observer.observe(current);
2166
2122
  }
2167
- }, [resolvedType, type]);
2168
- const [shouldFire, setShouldFire] = useState(false);
2169
- const closeButtonReference = useRef(null);
2170
- return /*#__PURE__*/jsxs("div", {
2171
- className: classNames('alert d-flex', `alert-${resolvedType}`, arrow != null && alertArrowClassNames(arrow), className),
2172
- "data-testid": "alert",
2173
- onTouchStart: () => setShouldFire(true),
2174
- onTouchEnd: event => {
2175
- if (shouldFire && action &&
2176
- // Check if current event is triggered from closeButton
2177
- event.target instanceof Node && closeButtonReference.current && !closeButtonReference.current.contains(event.target)) {
2178
- if (action.target === '_blank') {
2179
- window.top?.open(action.href);
2180
- } else {
2181
- window.top?.location.assign(action.href);
2182
- }
2123
+ return () => {
2124
+ didCancel = true;
2125
+ if (observer) {
2126
+ observer.unobserve(current);
2183
2127
  }
2184
- setShouldFire(false);
2185
- },
2186
- onTouchMove: () => setShouldFire(false),
2187
- children: [/*#__PURE__*/jsxs("div", {
2188
- className: classNames('alert__content', 'd-flex', 'flex-grow-1', variant),
2189
- "data-testid": variant,
2190
- children: [icon ? /*#__PURE__*/jsx("div", {
2191
- className: "alert__icon",
2192
- children: icon
2193
- }) : /*#__PURE__*/jsx(StatusIcon, {
2194
- size: Size.LARGE,
2195
- sentiment: resolvedType
2196
- }), /*#__PURE__*/jsxs("div", {
2197
- className: "alert__message",
2198
- children: [/*#__PURE__*/jsxs("div", {
2199
- role: Sentiment.NEGATIVE === resolvedType ? 'alert' : 'status',
2200
- children: [title && /*#__PURE__*/jsx(Title, {
2201
- className: "m-b-1",
2202
- type: Typography.TITLE_BODY,
2203
- children: title
2204
- }), /*#__PURE__*/jsx(Body, {
2205
- as: "span",
2206
- className: "d-block",
2207
- type: Typography.BODY_LARGE,
2208
- children: children || /*#__PURE__*/jsx(InlineMarkdown, {
2209
- children: message
2210
- })
2211
- })]
2212
- }), action && /*#__PURE__*/jsx(Link, {
2213
- href: action.href,
2214
- className: "m-t-1",
2215
- "aria-label": action['aria-label'],
2216
- target: action.target,
2217
- type: Typography.LINK_LARGE,
2218
- children: action.text
2219
- })]
2220
- })]
2221
- }), onDismiss && /*#__PURE__*/jsx(CloseButton, {
2222
- ref: closeButtonReference,
2223
- className: "m-l-2",
2224
- onClick: onDismiss
2225
- })]
2226
- });
2227
- }
2228
- function alertArrowClassNames(arrow) {
2229
- switch (arrow) {
2230
- case 'down-center':
2231
- return 'arrow arrow-bottom arrow-center';
2232
- case 'down-left':
2233
- return 'arrow arrow-bottom arrow-left';
2234
- case 'down-right':
2235
- return 'arrow arrow-bottom arrow-right';
2236
- case 'up-center':
2237
- return 'arrow arrow-center';
2238
- case 'up-right':
2239
- return 'arrow arrow-right';
2240
- case 'up-left':
2241
- default:
2242
- return 'arrow';
2128
+ };
2129
+ }, [elRef]);
2130
+ if (loading === 'eager') {
2131
+ return [false];
2243
2132
  }
2133
+ return [hasIntersected];
2134
+ };
2135
+
2136
+ // eslint-disable-next-line import/extensions
2137
+ function useMedia(query) {
2138
+ return useSyncExternalStore(onStoreChange => {
2139
+ const mediaQueryList = window.matchMedia(query);
2140
+ mediaQueryList.addEventListener('change', onStoreChange);
2141
+ return () => {
2142
+ mediaQueryList.removeEventListener('change', onStoreChange);
2143
+ };
2144
+ }, () => typeof window !== 'undefined' ? window.matchMedia(query).matches : undefined, () => undefined);
2244
2145
  }
2245
2146
 
2246
- // TODO: consider to move this enum into component file once we migrate it on TypeScript or replace with some common enum
2247
- var AvatarType;
2248
- (function (AvatarType) {
2249
- AvatarType["THUMBNAIL"] = "thumbnail";
2250
- AvatarType["ICON"] = "icon";
2251
- AvatarType["EMOJI"] = "emoji";
2252
- AvatarType["INITIALS"] = "initials";
2253
- })(AvatarType || (AvatarType = {}));
2147
+ function useScreenSize(size) {
2148
+ return useMedia(`(min-width: ${size}px)`);
2149
+ }
2254
2150
 
2255
- /*
2256
- * The colors we support generating an Avatar background color from a "seed" for.
2257
- * Changing this array will change the assignment between seeds.
2258
- * Do not change this array.
2151
+ /**
2152
+ * @deprecated Prefer `useScreenSize` instead.
2259
2153
  */
2260
- const avatarColors = ['--color-bright-blue', '--color-bright-yellow', '--color-bright-pink', '--color-bright-orange'];
2261
- /*
2262
- * Takes in a "seed" string and spits out an index for the color we should use.
2263
- * This implementation has been synced across all three clients so that we consistently
2264
- * generate branded avatar colors when rendering a list of Avatars.
2265
- * Do not change this implementation.
2266
- */
2267
- const hashSeed = seed => {
2268
- const base = 31;
2269
- const modulo = avatarColors.length;
2270
- let hashValue = 0;
2271
- let basePow = 1;
2272
- for (let i = 0; i < seed.length; i += 1) {
2273
- hashValue = (hashValue + seed.charCodeAt(i) * basePow) % modulo;
2274
- basePow = basePow * base % modulo;
2275
- }
2276
- return hashValue;
2277
- };
2278
- const getAvatarColorFromSeed = seed => {
2279
- return avatarColors[hashSeed(seed)];
2154
+ const useLayout = () => {
2155
+ const screenXs = useScreenSize(Breakpoint.EXTRA_SMALL);
2156
+ const screenSm = useScreenSize(Breakpoint.SMALL);
2157
+ const screenMd = useScreenSize(Breakpoint.MEDIUM);
2158
+ const screenLg = useScreenSize(Breakpoint.LARGE);
2159
+ const screenXl = useScreenSize(Breakpoint.EXTRA_LARGE);
2160
+ return {
2161
+ isMobile: screenSm != null ? !screenSm : undefined,
2162
+ isExtraSmall: screenXs,
2163
+ isSmall: screenSm,
2164
+ isMedium: screenMd,
2165
+ isLarge: screenLg,
2166
+ isExtraLarge: screenXl
2167
+ };
2280
2168
  };
2281
2169
 
2282
- const backwardsCompatibleSize = size => {
2283
- switch (size) {
2284
- case 'sm':
2285
- return 24;
2286
- case 'md':
2287
- return 48;
2288
- case 'lg':
2289
- return 72;
2290
- default:
2291
- return size;
2292
- }
2293
- };
2294
- const Avatar = ({
2295
- backgroundColor = null,
2296
- backgroundColorSeed = null,
2297
- children = null,
2170
+ const EXIT_ANIMATION = 350;
2171
+ const SlidingPanel = /*#__PURE__*/forwardRef(({
2172
+ position = 'left',
2173
+ open,
2174
+ showSlidingPanelBorder,
2175
+ slidingPanelPositionFixed,
2298
2176
  className,
2299
- outlined = false,
2300
- size: sizeFromProps = 48,
2301
- theme = Theme.LIGHT,
2302
- type = 'thumbnail'
2303
- }) => {
2304
- const backgroundColorFromSeed = useMemo(() => !backgroundColor && backgroundColorSeed ? `var(${getAvatarColorFromSeed(backgroundColorSeed)})` : undefined, [backgroundColor, backgroundColorSeed]);
2305
- const size = backwardsCompatibleSize(sizeFromProps);
2306
- return /*#__PURE__*/jsx("div", {
2307
- className: classNames('tw-avatar', className, `tw-avatar--${size}`, `tw-avatar--${type}`, {
2308
- 'tw-avatar--outlined': outlined,
2309
- 'tw-avatar--branded': Boolean(backgroundColorFromSeed),
2310
- 'np-text-title-body': type === 'initials'
2311
- }),
2312
- children: /*#__PURE__*/jsx("div", {
2313
- className: "tw-avatar__content",
2314
- style: {
2315
- backgroundColor: backgroundColor || backgroundColorFromSeed
2316
- },
2317
- children: children
2318
- })
2319
- });
2320
- };
2321
-
2322
- const Badge = ({
2323
- badge,
2324
- className = undefined,
2325
- size = Size.SMALL,
2326
- border = Theme.LIGHT,
2327
- children
2328
- }) => {
2329
- const classes = classNames('tw-badge', {
2330
- [`tw-badge-border-${border}`]: border,
2331
- [`tw-badge-${size}`]: size
2332
- }, className);
2333
- return /*#__PURE__*/jsxs("div", {
2334
- className: classes,
2335
- children: [/*#__PURE__*/jsx("div", {
2336
- className: "tw-badge__children",
2337
- children: children
2338
- }), /*#__PURE__*/jsx("div", {
2339
- className: "tw-badge__content",
2340
- children: badge
2341
- })]
2342
- });
2343
- };
2344
-
2345
- const OptionalBadge = ({
2346
- url,
2347
- altText,
2348
- statusIcon,
2349
2177
  children,
2350
2178
  ...rest
2351
- }) => {
2352
- if (url) {
2353
- return /*#__PURE__*/jsx(Badge, {
2354
- badge: /*#__PURE__*/jsx("img", {
2355
- src: url,
2356
- alt: altText
2357
- }),
2358
- ...rest,
2359
- children: children
2360
- });
2361
- }
2362
- if (statusIcon) {
2363
- return /*#__PURE__*/jsx(Badge, {
2364
- badge: /*#__PURE__*/jsx(StatusIcon, {
2365
- sentiment: statusIcon,
2366
- size: Size.SMALL
2367
- }),
2368
- ...rest,
2369
- children: children
2370
- });
2371
- }
2372
- return /*#__PURE__*/jsx(Fragment, {
2179
+ }, reference) => {
2180
+ const localReference = useRef(null);
2181
+ useImperativeHandle(reference, () => localReference.current, []);
2182
+ return /*#__PURE__*/createElement(CSSTransition, {
2183
+ ...rest,
2184
+ key: `sliding-panel--open-${position}`,
2185
+ nodeRef: localReference,
2186
+ in: open
2187
+ // Wait for animation to finish before unmount.
2188
+ ,
2189
+ timeout: {
2190
+ enter: 0,
2191
+ exit: EXIT_ANIMATION
2192
+ },
2193
+ classNames: "sliding-panel",
2194
+ appear: true,
2195
+ unmountOnExit: true
2196
+ }, /*#__PURE__*/jsx("div", {
2197
+ ref: localReference,
2198
+ className: classNames('sliding-panel', `sliding-panel--open-${position}`, showSlidingPanelBorder && `sliding-panel--border-${position}`, slidingPanelPositionFixed && 'sliding-panel--fixed', className),
2373
2199
  children: children
2374
- });
2375
- };
2376
- const AvatarWrapper = ({
2377
- url,
2378
- profileType,
2379
- profileId,
2380
- badgeUrl,
2381
- badgeAltText,
2382
- badgeStatusIcon,
2383
- name,
2384
- avatarProps,
2385
- badgeProps
2200
+ }));
2201
+ });
2202
+
2203
+ const Drawer = ({
2204
+ children,
2205
+ className,
2206
+ footerContent,
2207
+ headerTitle,
2208
+ onClose,
2209
+ open,
2210
+ position
2386
2211
  }) => {
2387
- const [hasImageLoadError, setImageLoadError] = useState(false);
2388
- const isBusinessProfile = profileType === ProfileType.BUSINESS;
2389
- // Reset the errored state when url changes
2390
- useEffect(() => setImageLoadError(false), [url]);
2391
- const getAvatarProps = () => {
2392
- if (url && !hasImageLoadError) {
2393
- return {
2394
- type: AvatarType.THUMBNAIL,
2395
- children: /*#__PURE__*/jsx("img", {
2396
- src: url,
2397
- alt: "",
2398
- onError: () => setImageLoadError(true)
2399
- }),
2400
- ...avatarProps
2401
- };
2402
- }
2403
- if (profileType) {
2404
- return {
2405
- type: AvatarType.ICON,
2406
- children: isBusinessProfile ? /*#__PURE__*/jsx(Briefcase, {
2407
- size: "24"
2408
- }) : /*#__PURE__*/jsx(Person, {
2409
- size: "24"
2410
- }),
2411
- ...avatarProps
2412
- };
2413
- }
2414
- if (name) {
2415
- return {
2416
- type: AvatarType.INITIALS,
2417
- children: /*#__PURE__*/jsx(Fragment, {
2418
- children: getInitials(name)
2419
- }),
2420
- backgroundColorSeed: profileId?.toString(),
2421
- ...avatarProps
2422
- };
2423
- }
2424
- return {
2425
- type: AvatarType.ICON,
2426
- children: /*#__PURE__*/jsx(Person, {
2427
- size: "24"
2428
- }),
2429
- ...avatarProps
2430
- };
2431
- };
2432
- return /*#__PURE__*/jsx(OptionalBadge, {
2433
- url: badgeUrl,
2434
- altText: badgeAltText,
2435
- statusIcon: badgeStatusIcon,
2436
- ...badgeProps,
2437
- children: /*#__PURE__*/jsx(Avatar, {
2438
- size: Size.MEDIUM,
2439
- ...getAvatarProps()
2212
+ logActionRequiredIf('Drawer now expects `onClose`, and will soon make this prop required. Please update your usage to provide it.', !onClose);
2213
+ const {
2214
+ isMobile
2215
+ } = useLayout();
2216
+ const titleId = useId();
2217
+ return /*#__PURE__*/jsx(Dimmer$1, {
2218
+ open: open,
2219
+ onClose: onClose,
2220
+ children: /*#__PURE__*/jsx(SlidingPanel, {
2221
+ open: open,
2222
+ position: isMobile ? Position.BOTTOM : position,
2223
+ children: /*#__PURE__*/jsxs("div", {
2224
+ role: "dialog",
2225
+ "aria-modal": true,
2226
+ "aria-labelledby": headerTitle ? titleId : undefined,
2227
+ className: classNames('np-drawer', className),
2228
+ children: [/*#__PURE__*/jsxs("div", {
2229
+ className: classNames('np-drawer-header', {
2230
+ 'np-drawer-header--withborder': headerTitle
2231
+ }),
2232
+ children: [headerTitle && /*#__PURE__*/jsx(Title, {
2233
+ id: titleId,
2234
+ type: Typography.TITLE_BODY,
2235
+ children: headerTitle
2236
+ }), /*#__PURE__*/jsx(CloseButton, {
2237
+ onClick: onClose
2238
+ })]
2239
+ }), children && /*#__PURE__*/jsx("div", {
2240
+ className: classNames('np-drawer-content'),
2241
+ children: children
2242
+ }), footerContent && /*#__PURE__*/jsx("div", {
2243
+ className: classNames('np-drawer-footer'),
2244
+ children: footerContent
2245
+ })]
2246
+ })
2440
2247
  })
2441
2248
  });
2442
2249
  };
2443
- function getInitials(name) {
2444
- const allInitials = name.split(' ').map(part => part[0]).join('').toUpperCase();
2445
- if (allInitials.length === 1) {
2446
- return allInitials[0];
2447
- }
2448
- return allInitials[0] + allInitials.slice(-1);
2449
- }
2450
-
2451
- const Card$1 = /*#__PURE__*/forwardRef((props, reference) => {
2452
- const {
2453
- 'aria-label': ariaLabel,
2454
- as: Element,
2455
- isExpanded,
2456
- title,
2457
- details,
2458
- children,
2459
- onClick,
2460
- icon,
2461
- id,
2462
- className,
2463
- ...rest
2464
- } = props;
2465
- const isOpen = !!(isExpanded && children);
2466
- return /*#__PURE__*/jsxs(Element, {
2467
- ref: reference,
2468
- className: classNames('np-card', className, {
2469
- 'np-card--expanded': isOpen,
2470
- 'np-card--inactive': !children,
2471
- 'np-card--has-icon': !!icon
2472
- }),
2473
- id: id,
2474
- "data-testid": rest['data-testid'],
2475
- children: [/*#__PURE__*/jsx(Option$2, {
2476
- "aria-label": ariaLabel,
2477
- as: children ? 'button' : 'div',
2478
- className: classNames('np-card__button'),
2479
- media: icon,
2480
- title: title,
2481
- content: details,
2482
- showMediaAtAllSizes: true,
2483
- button: children && /*#__PURE__*/jsx(Chevron, {
2484
- orientation: isOpen ? Position.TOP : Position.BOTTOM
2485
- }),
2486
- onClick: () => children && onClick(!isExpanded)
2487
- }), /*#__PURE__*/jsx("div", {
2488
- className: classNames('np-card__divider', {
2489
- 'np-card__divider--expanded': isOpen
2490
- })
2491
- }), isOpen && /*#__PURE__*/jsx(Body, {
2492
- as: "span",
2493
- type: Typography.BODY_LARGE,
2494
- className: "np-card__content d-block",
2495
- children: children
2496
- })]
2497
- });
2498
- });
2499
- const hasChildren = ({
2500
- children
2501
- }) => children;
2502
- Card$1.propTypes = {
2503
- 'aria-label': PropTypes.string,
2504
- as: PropTypes.string,
2505
- isExpanded: requiredIf(PropTypes.bool, hasChildren),
2506
- title: PropTypes.node.isRequired,
2507
- details: PropTypes.node.isRequired,
2508
- onClick: requiredIf(PropTypes.func, hasChildren),
2509
- icon: PropTypes.node,
2250
+ Drawer.propTypes = {
2251
+ /** The content to appear in the drawer body. */
2510
2252
  children: PropTypes.node,
2511
- id: PropTypes.string,
2512
2253
  className: PropTypes.string,
2513
- 'data-testid': PropTypes.string
2254
+ /** The content to appear in the drawer footer. */
2255
+ footerContent: PropTypes.node,
2256
+ /** The content to appear in the drawer header. */
2257
+ headerTitle: PropTypes.node,
2258
+ /** The action to perform on close click. */
2259
+ onClose: PropTypes.func,
2260
+ /** The status of Drawer either open or not. */
2261
+ open: PropTypes.bool,
2262
+ /** The placement of Drawer on the screen either left or right. On mobile it will default to bottom. */
2263
+ position: PropTypes.oneOf(['left', 'right', 'bottom'])
2514
2264
  };
2515
- Card$1.defaultProps = {
2516
- 'aria-label': undefined,
2517
- as: 'div',
2265
+ Drawer.defaultProps = {
2518
2266
  children: null,
2519
- id: null,
2520
- className: null,
2521
- 'data-testid': null
2522
- };
2523
- var Card$2 = Card$1;
2524
-
2525
- const CheckboxButton = /*#__PURE__*/forwardRef(({
2526
- checked,
2527
- className,
2528
- disabled,
2529
- onChange,
2530
- ...rest
2531
- }, reference) => /*#__PURE__*/jsxs("span", {
2532
- className: classNames('np-checkbox-button', className, disabled && 'disabled'),
2533
- children: [/*#__PURE__*/jsx("input", {
2534
- ...rest,
2535
- ref: reference,
2536
- type: "checkbox",
2537
- disabled: disabled,
2538
- checked: checked,
2539
- onChange: onChange
2540
- }), /*#__PURE__*/jsx("span", {
2541
- className: "tw-checkbox-button",
2542
- children: /*#__PURE__*/jsx("span", {
2543
- className: "tw-checkbox-check"
2544
- })
2545
- })]
2546
- }));
2547
- var CheckboxButton$1 = CheckboxButton;
2548
-
2549
- const Checkbox = ({
2550
- id,
2551
- checked,
2552
- required,
2553
- disabled,
2554
- readOnly,
2555
- label,
2556
- className,
2557
- secondary,
2558
- onChange,
2559
- onFocus,
2560
- onBlur
2561
- }) => {
2562
- const {
2563
- isModern
2564
- } = useTheme();
2565
- const classList = classNames('np-checkbox', {
2566
- checkbox: true,
2567
- 'checkbox-lg': secondary,
2568
- disabled: isModern && disabled
2569
- }, className);
2570
- const innerDisabled = disabled || readOnly;
2571
- return /*#__PURE__*/jsx("div", {
2572
- id: id,
2573
- className: classList,
2574
- children: /*#__PURE__*/jsxs("label", {
2575
- className: classNames({
2576
- disabled
2577
- }),
2578
- children: [/*#__PURE__*/jsx(CheckboxButton$1, {
2579
- className: "p-r-2",
2580
- checked: checked,
2581
- disabled: innerDisabled,
2582
- required: !innerDisabled && required,
2583
- onFocus: onFocus,
2584
- onChange: () => onChange(!checked),
2585
- onBlur: onBlur
2586
- }), /*#__PURE__*/jsxs(Body, {
2587
- as: "span",
2588
- className: "np-checkbox__text",
2589
- type: secondary ? Typography.BODY_LARGE_BOLD : Typography.BODY_LARGE,
2590
- children: [/*#__PURE__*/jsx("span", {
2591
- className: required ? 'has-required' : undefined,
2592
- children: label
2593
- }), secondary && /*#__PURE__*/jsx(Body, {
2594
- as: "span",
2595
- className: classNames({
2596
- secondary: !isModern
2597
- }),
2598
- children: secondary
2599
- })]
2600
- })]
2601
- })
2602
- });
2603
- };
2604
- Checkbox.propTypes = {
2605
- id: PropTypes.string,
2606
- checked: PropTypes.bool,
2607
- required: PropTypes.bool,
2608
- disabled: PropTypes.bool,
2609
- readOnly: PropTypes.bool,
2610
- label: PropTypes.node.isRequired,
2611
- secondary: PropTypes.string,
2612
- onFocus: PropTypes.func,
2613
- onChange: PropTypes.func.isRequired,
2614
- onBlur: PropTypes.func,
2615
- className: PropTypes.string
2616
- };
2617
- Checkbox.defaultProps = {
2618
- id: null,
2619
- checked: false,
2620
- required: false,
2621
- disabled: false,
2622
- readOnly: false,
2623
- secondary: null,
2624
- onFocus: null,
2625
- onBlur: null,
2626
- className: undefined
2267
+ className: undefined,
2268
+ footerContent: null,
2269
+ headerTitle: null,
2270
+ onClose: null,
2271
+ open: false,
2272
+ position: Position.RIGHT
2627
2273
  };
2274
+ var Drawer$1 = Drawer;
2628
2275
 
2629
- const CheckboxOption = /*#__PURE__*/forwardRef(({
2630
- checked,
2631
- disabled,
2632
- onChange,
2633
- ...rest
2634
- }, reference) => {
2635
- return /*#__PURE__*/jsx(Option$2, {
2636
- ...rest,
2637
- ref: reference,
2638
- disabled: disabled,
2639
- button: /*#__PURE__*/jsx(CheckboxButton$1, {
2640
- checked: checked,
2641
- disabled: disabled,
2642
- onChange: () => onChange?.(!checked)
2643
- })
2276
+ const INITIAL_Y_POSITION = 0;
2277
+ const CONTENT_SCROLL_THRESHOLD = 1;
2278
+ const MOVE_OFFSET_THRESHOLD = 50;
2279
+ /**
2280
+ * Neptune: https://transferwise.github.io/neptune/components/bottom-sheet/
2281
+ *
2282
+ * Neptune Web: https://transferwise.github.io/neptune-web/components/overlays/BottomSheet
2283
+ *
2284
+ */
2285
+ const BottomSheet$1 = props => {
2286
+ const bottomSheetReference = useRef(null);
2287
+ const topBarReference = useRef(null);
2288
+ const contentReference = useRef(null);
2289
+ const [pressed, setPressed] = useState(false);
2290
+ /**
2291
+ * Used to track `requestAnimationFrame` requests
2292
+ *
2293
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame#return_value
2294
+ */
2295
+ const animationId = useRef(0);
2296
+ /**
2297
+ * Difference between initial coordinate ({@link initialYCoordinate})
2298
+ * and new position (when user moves component), it's get calculated on `onTouchMove` and `onMouseMove` events
2299
+ *
2300
+ * @see {@link calculateOffsetAfterMove}
2301
+ */
2302
+ const moveOffset = useRef(0);
2303
+ const initialYCoordinate = useRef(0);
2304
+ // apply shadow to the bottom of top-bar when scroll over content
2305
+ useConditionalListener({
2306
+ attachListener: props.open && !isServerSide(),
2307
+ callback: () => {
2308
+ if (topBarReference.current !== null) {
2309
+ const {
2310
+ classList
2311
+ } = topBarReference.current;
2312
+ if (!isContentScrollPositionAtTop()) {
2313
+ classList.add('np-bottom-sheet--top-bar--shadow');
2314
+ } else {
2315
+ classList.remove('np-bottom-sheet--top-bar--shadow');
2316
+ }
2317
+ }
2318
+ },
2319
+ eventType: 'scroll',
2320
+ parent: isServerSide() ? undefined : document
2644
2321
  });
2645
- });
2646
-
2647
- const Chip = ({
2648
- label,
2649
- value,
2650
- onRemove,
2651
- onClick,
2652
- onKeyPress,
2653
- className = undefined,
2654
- 'aria-label': ariaLabel,
2655
- 'aria-checked': ariaChecked,
2656
- role,
2657
- closeButton
2658
- }) => {
2659
- const isActionable = onClick || onKeyPress;
2660
- const defaultRole = isActionable ? 'button' : undefined;
2661
- const tabIndex = isActionable ? 0 : -1;
2662
- const {
2663
- isModern
2664
- } = useTheme();
2665
- const closeButtonReference = useRef(null);
2666
- const previousCloseButtonShown = useRef();
2667
- useEffect(() => {
2668
- if (closeButtonReference.current != null && previousCloseButtonShown.current === false) {
2669
- closeButtonReference.current?.focus();
2322
+ function move(newHeight) {
2323
+ if (bottomSheetReference.current !== null) {
2324
+ bottomSheetReference.current.style.transform = `translateY(${newHeight}px)`;
2670
2325
  }
2671
- previousCloseButtonShown.current = closeButtonReference.current != null;
2672
- }, [onRemove]);
2673
- return /*#__PURE__*/jsxs("div", {
2674
- role: role ?? defaultRole,
2675
- tabIndex: tabIndex,
2676
- "aria-label": ariaLabel,
2677
- "aria-checked": ariaChecked,
2678
- className: classNames('np-chip', 'd-flex', 'align-items-center', 'justify-content-between', onRemove ? 'np-chip--removable' : '', className),
2679
- ...(isActionable && {
2680
- onClick,
2681
- onKeyPress
2682
- }),
2683
- children: [isModern ? /*#__PURE__*/jsx(Body, {
2684
- "aria-hidden": !!onRemove,
2685
- type: Typography.BODY_DEFAULT_BOLD,
2686
- children: label
2687
- }) : /*#__PURE__*/jsx("span", {
2688
- "aria-hidden": "false",
2689
- className: "np-chip-label",
2690
- children: label
2691
- }), onRemove ? /*#__PURE__*/jsx(CloseButton, {
2692
- ref: closeButtonReference,
2693
- className: isModern ? `btn-unstyled` : `btn-unstyled m-l-1`,
2694
- "aria-label": closeButton && closeButton['aria-label'],
2695
- size: "sm",
2696
- filled: isModern,
2697
- onClick: onRemove
2698
- }) : null]
2699
- }, value);
2700
- };
2701
-
2702
- var messages$a = defineMessages({
2703
- ariaLabel: {
2704
- id: "neptune.Chips.ariaLabel"
2705
2326
  }
2706
- });
2707
-
2708
- const Chips = ({
2709
- chips,
2710
- onChange,
2711
- selected,
2712
- 'aria-label': ariaLabel,
2713
- className,
2714
- multiple
2715
- }) => {
2716
- const intl = useIntl();
2717
- const isSelected = value => Array.isArray(selected) ? selected.includes(value) : selected === value;
2718
- const handleOnChange = (selectedValue, isCurrentlyEnabled) => {
2719
- onChange({
2720
- isEnabled: !isCurrentlyEnabled,
2721
- selectedValue
2722
- });
2327
+ function close(event) {
2328
+ setPressed(false);
2329
+ moveOffset.current = INITIAL_Y_POSITION;
2330
+ if (bottomSheetReference.current !== null) {
2331
+ bottomSheetReference.current.style.removeProperty('transform');
2332
+ }
2333
+ if (props.onClose) {
2334
+ props.onClose(event);
2335
+ }
2336
+ }
2337
+ const onSwipeStart = event => {
2338
+ initialYCoordinate.current = ('touches' in event ? event.touches[0] : event).clientY;
2339
+ setPressed(true);
2723
2340
  };
2724
- return /*#__PURE__*/jsx("div", {
2725
- className: classNames('np-chips d-flex', className),
2726
- "aria-label": ariaLabel,
2727
- role: !multiple ? 'radiogroup' : undefined,
2728
- children: chips.map(chip => {
2729
- const chipSelected = isSelected(chip.value);
2730
- return /*#__PURE__*/jsx(Chip, {
2731
- value: chip.value,
2732
- label: chip.label,
2733
- closeButton: {
2734
- 'aria-label': intl.formatMessage(messages$a.ariaLabel, {
2735
- choice: chip.label
2736
- })
2737
- },
2738
- className: classNames('text-xs-nowrap', {
2739
- 'np-chip--selected': chipSelected,
2740
- 'p-r-1': multiple && chipSelected
2741
- }),
2742
- ...(multiple && chipSelected ? {
2743
- onRemove: () => handleOnChange(chip.value, chipSelected),
2744
- 'aria-label': chip.label
2745
- } : {
2746
- onClick: () => handleOnChange(chip.value, chipSelected),
2747
- onKeyPress: () => handleOnChange(chip.value, chipSelected),
2748
- role: !multiple ? 'radio' : undefined,
2749
- 'aria-checked': chipSelected
2750
- })
2751
- }, chip.value);
2341
+ const onSwipeMove = event => {
2342
+ if (pressed) {
2343
+ const {
2344
+ clientY
2345
+ } = 'touches' in event ? event.touches[0] : event;
2346
+ const offset = calculateOffsetAfterMove(clientY);
2347
+ // check whether move is to the bottom only and content scroll position is at the top
2348
+ if (offset > INITIAL_Y_POSITION && isContentScrollPositionAtTop()) {
2349
+ moveOffset.current = offset;
2350
+ animationId.current = requestAnimationFrame(() => {
2351
+ if (animationId.current !== undefined && bottomSheetReference.current !== null) {
2352
+ move(offset);
2353
+ }
2354
+ });
2355
+ }
2356
+ }
2357
+ };
2358
+ function onSwipeEnd(event) {
2359
+ // stop moving component
2360
+ cancelAnimationFrame(animationId.current);
2361
+ setPressed(false);
2362
+ // check whether move down is strong enough
2363
+ // and content scroll position is at the top to close the component
2364
+ if (moveOffset.current > MOVE_OFFSET_THRESHOLD && isContentScrollPositionAtTop()) {
2365
+ close(event);
2366
+ }
2367
+ // otherwise move component back to default (initial) position
2368
+ else {
2369
+ move(INITIAL_Y_POSITION);
2370
+ }
2371
+ moveOffset.current = INITIAL_Y_POSITION;
2372
+ }
2373
+ function isContentScrollPositionAtTop() {
2374
+ return contentReference?.current?.scrollTop !== undefined && contentReference.current.scrollTop <= CONTENT_SCROLL_THRESHOLD;
2375
+ }
2376
+ /**
2377
+ * Calculates how hard user moves component,
2378
+ * result value used to determine whether to hide component or re-position to default state
2379
+ *
2380
+ * @param afterMoveYCoordinate
2381
+ */
2382
+ function calculateOffsetAfterMove(afterMoveYCoordinate) {
2383
+ return afterMoveYCoordinate - initialYCoordinate.current;
2384
+ }
2385
+ /**
2386
+ * Set `max-height` for content part (in order to keep it scrollable for content overflow cases) of the component
2387
+ * and ensures space for safe zone (32px) at the top.
2388
+ */
2389
+ function setContentMaxHeight() {
2390
+ const safeZoneHeight = '64px';
2391
+ const topbarHeight = '32px';
2392
+ const windowHight = isServerSide() ? 0 : window.innerHeight;
2393
+ /**
2394
+ * Calculate _real_ height of the screen (taking into account parts of browser interface).
2395
+ *
2396
+ * See https://css-tricks.com/the-trick-to-viewport-units-on-mobile for more details.
2397
+ */
2398
+ const screenHeight = `${windowHight * 0.01 * 100}px`;
2399
+ return {
2400
+ maxHeight: `calc(${screenHeight} - ${safeZoneHeight} - ${topbarHeight})`
2401
+ };
2402
+ }
2403
+ const is400Zoom = useMedia(`(max-width: ${Breakpoint.ZOOM_400}px)`);
2404
+ return is400Zoom ? /*#__PURE__*/jsx(Drawer$1, {
2405
+ open: props.open,
2406
+ className: props.className,
2407
+ onClose: close,
2408
+ children: props.children
2409
+ }) : /*#__PURE__*/jsx(Dimmer$1, {
2410
+ open: props.open,
2411
+ fadeContentOnEnter: true,
2412
+ fadeContentOnExit: true,
2413
+ onClose: close,
2414
+ children: /*#__PURE__*/jsx(SlidingPanel, {
2415
+ ref: bottomSheetReference,
2416
+ open: props.open,
2417
+ position: Position.BOTTOM,
2418
+ className: classNames('np-bottom-sheet', props.className),
2419
+ children: /*#__PURE__*/jsxs("div", {
2420
+ role: "dialog",
2421
+ "aria-modal": true,
2422
+ onTouchStart: onSwipeStart,
2423
+ onTouchMove: onSwipeMove,
2424
+ onTouchEnd: onSwipeEnd,
2425
+ onMouseDown: onSwipeStart,
2426
+ onMouseMove: onSwipeMove,
2427
+ onMouseUp: onSwipeEnd,
2428
+ children: [/*#__PURE__*/jsxs("div", {
2429
+ ref: topBarReference,
2430
+ className: "np-bottom-sheet--top-bar",
2431
+ children: [/*#__PURE__*/jsx("div", {
2432
+ className: "np-bottom-sheet--handler"
2433
+ }), /*#__PURE__*/jsx(CloseButton, {
2434
+ size: "sm",
2435
+ className: "sr-only np-bottom-sheet--close-btn",
2436
+ onClick: close
2437
+ })]
2438
+ }), /*#__PURE__*/jsx("div", {
2439
+ ref: contentReference,
2440
+ style: setContentMaxHeight(),
2441
+ className: "np-bottom-sheet--content",
2442
+ children: props.children
2443
+ })]
2444
+ })
2752
2445
  })
2753
2446
  });
2754
2447
  };
2755
2448
 
2756
- const CircularButton = ({
2757
- className,
2758
- children,
2759
- disabled,
2760
- icon,
2761
- priority = Priority.PRIMARY,
2762
- type = ControlType.ACCENT,
2763
- ...rest
2764
- }) => {
2765
- const classes = classNames('btn np-btn', typeClassMap[type], priorityClassMap[priority]);
2766
- const iconElement = Number(icon.props.size) !== 24 ? /*#__PURE__*/cloneElement(icon, {
2767
- size: 24
2768
- }) : icon;
2769
- return /*#__PURE__*/jsxs("label", {
2770
- className: classNames('np-circular-btn', priority, type, disabled && 'disabled', className),
2771
- children: [/*#__PURE__*/jsx("input", {
2772
- type: "button",
2773
- "aria-label": children,
2774
- className: classes,
2775
- disabled: disabled,
2776
- ...rest
2777
- }), iconElement, /*#__PURE__*/jsx(Body, {
2778
- as: "span",
2779
- className: "np-circular-btn__label",
2780
- type: Typography.BODY_DEFAULT_BOLD,
2781
- children: children
2782
- })]
2783
- });
2784
- };
2785
-
2786
2449
  const Card = ({
2787
2450
  className,
2788
2451
  children = null,
@@ -3295,6 +2958,124 @@ function shouldPropagateOnBlur({
3295
2958
  return blurElementParent !== focusElementParent;
3296
2959
  }
3297
2960
 
2961
+ const POPOVER_OFFSET = [0, 16];
2962
+ // By default the flip positioning explores only the opposite alternative. So if left is passed and there's no enough space
2963
+ // the right one gets chosen. If there's no space on both sides popover goes back to the initially chosen one left.
2964
+ // This mapping forces popover to try the four available positions before going back to the initial chosen one.
2965
+ const fallbackPlacements = {
2966
+ [Position.TOP]: [Position.BOTTOM, Position.RIGHT, Position.LEFT],
2967
+ [Position.BOTTOM]: [Position.TOP, Position.RIGHT, Position.LEFT],
2968
+ [Position.LEFT]: [Position.RIGHT, Position.TOP, Position.BOTTOM],
2969
+ [Position.RIGHT]: [Position.LEFT, Position.TOP, Position.BOTTOM]
2970
+ };
2971
+ const Panel = /*#__PURE__*/forwardRef(({
2972
+ arrow = false,
2973
+ flip = true,
2974
+ altAxis = false,
2975
+ children,
2976
+ open = false,
2977
+ onClose,
2978
+ position = Position.BOTTOM,
2979
+ anchorRef,
2980
+ anchorWidth = false,
2981
+ ...rest
2982
+ }, reference) => {
2983
+ const [arrowElement, setArrowElement] = useState(null);
2984
+ const [popperElement, setPopperElement] = useState(null);
2985
+ const modifiers = [];
2986
+ if (altAxis) {
2987
+ modifiers.push({
2988
+ // https://popper.js.org/docs/v2/modifiers/prevent-overflow
2989
+ name: 'preventOverflow',
2990
+ options: {
2991
+ altAxis: true,
2992
+ tether: false
2993
+ }
2994
+ });
2995
+ }
2996
+ if (arrow) {
2997
+ modifiers.push({
2998
+ name: 'arrow',
2999
+ options: {
3000
+ element: arrowElement,
3001
+ options: {
3002
+ padding: 8 // 8px from the edges of the popper
3003
+ }
3004
+ }
3005
+ });
3006
+ // This lets you displace a popper element from its reference element.
3007
+ modifiers.push({
3008
+ name: 'offset',
3009
+ options: {
3010
+ offset: POPOVER_OFFSET
3011
+ }
3012
+ });
3013
+ }
3014
+ if (flip && fallbackPlacements[position]) {
3015
+ modifiers.push({
3016
+ name: 'flip',
3017
+ options: {
3018
+ fallbackPlacements: fallbackPlacements[position]
3019
+ }
3020
+ });
3021
+ }
3022
+ const {
3023
+ styles,
3024
+ attributes,
3025
+ forceUpdate
3026
+ } = usePopper(anchorRef.current, popperElement, {
3027
+ placement: position,
3028
+ modifiers
3029
+ });
3030
+ // If the trigger is not visible when the position is calculated, it will be incorrect. Because this can happen repeatedly (on resize for example),
3031
+ // it is most simple just to always position before opening
3032
+ useEffect(() => {
3033
+ if (open && forceUpdate) {
3034
+ forceUpdate();
3035
+ }
3036
+ }, [open]);
3037
+ const contentStyle = {
3038
+ ...(anchorWidth ? {
3039
+ width: anchorRef.current?.clientWidth
3040
+ } : undefined)
3041
+ };
3042
+ return /*#__PURE__*/jsx(Dimmer$1, {
3043
+ open: open,
3044
+ transparent: true,
3045
+ fadeContentOnEnter: true,
3046
+ fadeContentOnExit: true,
3047
+ onClose: onClose,
3048
+ children: /*#__PURE__*/jsx("div", {
3049
+ ...rest,
3050
+ ref: setPopperElement,
3051
+ role: "dialog"
3052
+ // eslint-disable-next-line react/forbid-dom-props
3053
+ ,
3054
+ style: {
3055
+ ...styles.popper
3056
+ },
3057
+ ...attributes.popper,
3058
+ className: classNames('np-panel', {
3059
+ 'np-panel--open': open
3060
+ }, rest['className']),
3061
+ children: /*#__PURE__*/jsxs("div", {
3062
+ ref: reference
3063
+ /* eslint-disable-next-line react/forbid-dom-props */,
3064
+ style: contentStyle,
3065
+ className: classNames('np-panel__content'),
3066
+ children: [children, arrow && /*#__PURE__*/jsx("div", {
3067
+ ref: setArrowElement,
3068
+ className: classNames('np-panel__arrow')
3069
+ // eslint-disable-next-line react/forbid-dom-props
3070
+ ,
3071
+ style: styles.arrow
3072
+ })]
3073
+ })
3074
+ })
3075
+ });
3076
+ });
3077
+ var Panel$1 = Panel;
3078
+
3298
3079
  const ResponsivePanel = /*#__PURE__*/forwardRef(({
3299
3080
  anchorRef,
3300
3081
  arrow = false,
@@ -3316,7 +3097,7 @@ const ResponsivePanel = /*#__PURE__*/forwardRef(({
3316
3097
  children: children
3317
3098
  }, "bottomSheet");
3318
3099
  }
3319
- return /*#__PURE__*/jsx(Panel, {
3100
+ return /*#__PURE__*/jsx(Panel$1, {
3320
3101
  ref: reference,
3321
3102
  flip: flip,
3322
3103
  arrow: arrow,
@@ -5214,6 +4995,73 @@ const Field = ({
5214
4995
  });
5215
4996
  };
5216
4997
 
4998
+ const HeaderAction = ({
4999
+ action
5000
+ }) => {
5001
+ const {
5002
+ isModern
5003
+ } = useTheme();
5004
+ const props = {
5005
+ 'aria-label': action['aria-label']
5006
+ };
5007
+ if ('href' in action) {
5008
+ return /*#__PURE__*/jsx(Link, {
5009
+ href: action.href,
5010
+ target: action.target,
5011
+ onClick: action.onClick,
5012
+ ...props,
5013
+ children: action.text
5014
+ });
5015
+ }
5016
+ return isModern ? /*#__PURE__*/jsx(Button, {
5017
+ className: "np-header__button",
5018
+ priority: "tertiary",
5019
+ size: "sm",
5020
+ onClick: action.onClick,
5021
+ ...props,
5022
+ children: action.text
5023
+ }) : /*#__PURE__*/jsx(ActionButton, {
5024
+ onClick: action.onClick,
5025
+ ...props,
5026
+ children: action.text
5027
+ });
5028
+ };
5029
+ /**
5030
+ *
5031
+ * Neptune Web: https://transferwise.github.io/neptune-web/components/content/Header
5032
+ *
5033
+ */
5034
+ const Header = ({
5035
+ action,
5036
+ as = 'h5',
5037
+ title,
5038
+ className
5039
+ }) => {
5040
+ if (!action) {
5041
+ return /*#__PURE__*/jsx(Title, {
5042
+ as: as,
5043
+ type: Typography.TITLE_GROUP,
5044
+ className: classNames('np-header', 'np-header__title', className),
5045
+ children: title
5046
+ });
5047
+ }
5048
+ if (as === 'legend') {
5049
+ // eslint-disable-next-line no-console
5050
+ console.warn('Legends should be the first child in a fieldset, and this is not possible when including an action');
5051
+ }
5052
+ return /*#__PURE__*/jsxs("div", {
5053
+ className: classNames('np-header', className),
5054
+ children: [/*#__PURE__*/jsx(Title, {
5055
+ as: as,
5056
+ type: Typography.TITLE_GROUP,
5057
+ className: "np-header__title",
5058
+ children: title
5059
+ }), /*#__PURE__*/jsx(HeaderAction, {
5060
+ action: action
5061
+ })]
5062
+ });
5063
+ };
5064
+
5217
5065
  const EmptyTransparentImage = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
5218
5066
  const Image = ({
5219
5067
  id,
@@ -9661,6 +9509,19 @@ function RadioOption({
9661
9509
  });
9662
9510
  }
9663
9511
 
9512
+ const Section = ({
9513
+ children,
9514
+ className,
9515
+ withHorizontalPadding = false
9516
+ }) => {
9517
+ return /*#__PURE__*/jsx("div", {
9518
+ className: classNames('np-section', className, {
9519
+ 'np-section--with-horizontal-padding': withHorizontalPadding
9520
+ }),
9521
+ children: children
9522
+ });
9523
+ };
9524
+
9664
9525
  const SegmentedControl = ({
9665
9526
  name,
9666
9527
  value,
@@ -10327,7 +10188,7 @@ function Select({
10327
10188
  children: renderOptionsList({
10328
10189
  className: isModern ? '' : 'p-a-1'
10329
10190
  })
10330
- }) : /*#__PURE__*/jsx(Panel, {
10191
+ }) : /*#__PURE__*/jsx(Panel$1, {
10331
10192
  open: open,
10332
10193
  flip: false,
10333
10194
  altAxis: true,
@@ -14612,5 +14473,5 @@ const translations = {
14612
14473
  'zh-HK': zhHK
14613
14474
  };
14614
14475
 
14615
- export { Accordion, ActionButton, ActionOption, Alert, AlertArrowPosition, Avatar, AvatarType, AvatarWrapper, Badge, Card as BaseCard, Body, BottomSheet$1 as BottomSheet, Breakpoint, Button, Card$2 as Card, Checkbox, CheckboxButton$1 as CheckboxButton, CheckboxOption, Chevron, Chip, Chips, CircularButton, ControlType, CriticalCommsBanner, DEFAULT_LANG, DEFAULT_LOCALE, DateInput, DateLookup$1 as DateLookup, DateMode, Decision, DecisionPresentation, DecisionType, DefinitionList$1 as DefinitionList, Dimmer$1 as Dimmer, Direction, DirectionProvider, Display, Drawer$1 as Drawer, DropFade, Emphasis, Field, FileType, FlowNavigation, Header, Image, Info, InfoPresentation, InlineAlert, Input, InputGroup, InputWithDisplayFormat, InstructionsList, Label, LanguageProvider, Layout, Link, ListItem$1 as ListItem, Loader, Logo$1 as Logo, LogoType, Markdown, MarkdownNodeType, Modal, Money, MoneyInput$1 as MoneyInput, MonthFormat, NavigationOption, NavigationOptionList as NavigationOptionsList, Nudge, Option$2 as Option, OverlayHeader$1 as OverlayHeader, PhoneNumberInput, Popover$1 as Popover, Position, Priority, ProcessIndicator$1 as ProcessIndicator, ProfileType, Progress, ProgressBar, PromoCard$1 as PromoCard, PromoCardGroup$1 as PromoCardGroup, Provider, RTL_LANGUAGES, Radio, RadioGroup, RadioOption, SUPPORTED_LANGUAGES, Scroll, SearchInput, Section, SegmentedControl, Select, SelectInput, SelectInputOptionContent, SelectInputTriggerButton, SelectOption, Sentiment, Size, SlidingPanel, SnackbarConsumer, SnackbarContext, SnackbarPortal, SnackbarProvider, Status, StatusIcon, Stepper, Sticky, Summary, Switch, SwitchOption, Tabs$1 as Tabs, TextArea, TextareaWithDisplayFormat, Theme, Title, Tooltip, Type, Typeahead, Typography, Upload$1 as Upload, UploadInput, UploadStep, Variant, Width, adjustLocale, getCountryFromLocale, getDirectionFromLocale, getLangFromLocale, isBrowser, isServerSide, translations, useDirection, useLayout, useScreenSize, useSnackbar };
14476
+ export { Accordion, ActionButton, ActionOption, Alert, AlertArrowPosition, Avatar, AvatarType, AvatarWrapper, Badge, Card as BaseCard, Body, BottomSheet$1 as BottomSheet, Breakpoint, Button, Card$2 as Card, Checkbox, CheckboxButton$1 as CheckboxButton, CheckboxOption, Chevron, Chip, Chips, CircularButton, ControlType, CriticalCommsBanner, DEFAULT_LANG, DEFAULT_LOCALE, DateInput, DateLookup$1 as DateLookup, DateMode, Decision, DecisionPresentation, DecisionType, DefinitionList$1 as DefinitionList, Dimmer$1 as Dimmer, Direction, DirectionProvider, Display, Drawer$1 as Drawer, DropFade, Emphasis, Field, FileType, FlowNavigation, Header, Image, Info, InfoPresentation, InlineAlert, Input, InputGroup, InputWithDisplayFormat, InstructionsList, Label, LanguageProvider, Layout, Link, ListItem$1 as ListItem, Loader, Logo$1 as Logo, LogoType, Markdown, MarkdownNodeType, Modal, Money, MoneyInput$1 as MoneyInput, MonthFormat, NavigationOption, NavigationOptionList as NavigationOptionsList, Nudge, Option$2 as Option, OverlayHeader$1 as OverlayHeader, PhoneNumberInput, Popover$1 as Popover, Position, Priority, ProcessIndicator$1 as ProcessIndicator, ProfileType, Progress, ProgressBar, PromoCard$1 as PromoCard, PromoCardGroup$1 as PromoCardGroup, Provider, RTL_LANGUAGES, Radio, RadioGroup, RadioOption, SUPPORTED_LANGUAGES, Scroll, SearchInput, Section, SegmentedControl, Select, SelectInput, SelectInputOptionContent, SelectInputTriggerButton, Sentiment, Size, SlidingPanel, SnackbarConsumer, SnackbarContext, SnackbarPortal, SnackbarProvider, Status, StatusIcon, Stepper, Sticky, Summary, Switch, SwitchOption, Tabs$1 as Tabs, TextArea, TextareaWithDisplayFormat, Theme, Title, Tooltip, Type, Typeahead, Typography, Upload$1 as Upload, UploadInput, UploadStep, Variant, Width, adjustLocale, getCountryFromLocale, getDirectionFromLocale, getLangFromLocale, isBrowser, isServerSide, translations, useDirection, useLayout, useScreenSize, useSnackbar };
14616
14477
  //# sourceMappingURL=index.mjs.map