mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 01:47:00 +01:00
UX Multichain: Added product tour component (#18571)
* adding product tour component * updated control for prevIcon * updated app-header and product tour * updated css * updated message strings * updated tests * removed console statement * added selector for product tour * updated test * updated test * updated state with steps * Update ui/components/multichain/product-tour-popover/product-tour-popover.js Co-authored-by: Garrett Bear <gwhisten@gmail.com> * Update ui/components/multichain/product-tour-popover/product-tour-popover.js Co-authored-by: Garrett Bear <gwhisten@gmail.com> * Update ui/components/multichain/product-tour-popover/product-tour-popover.js Co-authored-by: Garrett Bear <gwhisten@gmail.com> * Update ui/components/multichain/product-tour-popover/product-tour-popover.js Co-authored-by: Garrett Bear <gwhisten@gmail.com> * Update ui/components/multichain/product-tour-popover/product-tour-popover.js Co-authored-by: Garrett Bear <gwhisten@gmail.com> * Update ui/components/multichain/product-tour-popover/product-tour-popover.js Co-authored-by: Garrett Bear <gwhisten@gmail.com> * Update ui/components/multichain/product-tour-popover/product-tour-popover.js Co-authored-by: Garrett Bear <gwhisten@gmail.com> * Update ui/components/multichain/product-tour-popover/product-tour-popover.js Co-authored-by: Garrett Bear <gwhisten@gmail.com> * Update ui/components/multichain/product-tour-popover/product-tour-popover.js Co-authored-by: Garrett Bear <gwhisten@gmail.com> * Update ui/components/multichain/product-tour-popover/product-tour-popover.scss Co-authored-by: Garrett Bear <gwhisten@gmail.com> * fixed lint errors * updated lint error * added changes for rtl support * added changes for rtl support * fixed lint errors * Some suggestions (#18676) * updated messages and indentation * fixed popup close on my final step * updated rtl classname condition --------- Co-authored-by: Garrett Bear <gwhisten@gmail.com> Co-authored-by: George Marshall <george.marshall@consensys.net>
This commit is contained in:
parent
b467fbc07b
commit
0efd00b755
18
app/_locales/en/messages.json
generated
18
app/_locales/en/messages.json
generated
@ -1636,6 +1636,12 @@
|
||||
"general": {
|
||||
"message": "General"
|
||||
},
|
||||
"globalTitle": {
|
||||
"message": "Global menu"
|
||||
},
|
||||
"globalTourDescription": {
|
||||
"message": "See your portfolio, connected sites, settings, and more"
|
||||
},
|
||||
"goBack": {
|
||||
"message": "Go back"
|
||||
},
|
||||
@ -3052,6 +3058,12 @@
|
||||
"permissions": {
|
||||
"message": "Permissions"
|
||||
},
|
||||
"permissionsTitle": {
|
||||
"message": "Permissions"
|
||||
},
|
||||
"permissionsTourDescription": {
|
||||
"message": "Find your connected accounts and manage permissions here"
|
||||
},
|
||||
"personalAddressDetected": {
|
||||
"message": "Personal address detected. Input the token contract address."
|
||||
},
|
||||
@ -4279,6 +4291,12 @@
|
||||
"switchedTo": {
|
||||
"message": "You have switched to"
|
||||
},
|
||||
"switcherTitle": {
|
||||
"message": "Network switcher"
|
||||
},
|
||||
"switcherTourDescription": {
|
||||
"message": "Click the icon to switch networks or add a new network"
|
||||
},
|
||||
"switchingNetworksCancelsPendingConfirmations": {
|
||||
"message": "Switching networks will cancel all pending confirmations"
|
||||
},
|
||||
|
@ -46,6 +46,7 @@ export default class AppStateController extends EventEmitter {
|
||||
nftsDetectionNoticeDismissed: false,
|
||||
showTestnetMessageInDropdown: true,
|
||||
showBetaHeader: isBeta(),
|
||||
showProductTour: true,
|
||||
trezorModel: null,
|
||||
currentPopupId: undefined,
|
||||
...initState,
|
||||
@ -331,6 +332,15 @@ export default class AppStateController extends EventEmitter {
|
||||
this.store.updateState({ showBetaHeader });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the product tour should be shown
|
||||
*
|
||||
* @param showProductTour
|
||||
*/
|
||||
setShowProductTour(showProductTour) {
|
||||
this.store.updateState({ showProductTour });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a property indicating the model of the user's Trezor hardware wallet
|
||||
*
|
||||
|
@ -2061,6 +2061,8 @@ export default class MetamaskController extends EventEmitter {
|
||||
),
|
||||
setShowBetaHeader:
|
||||
appStateController.setShowBetaHeader.bind(appStateController),
|
||||
setShowProductTour:
|
||||
appStateController.setShowProductTour.bind(appStateController),
|
||||
updateNftDropDownState:
|
||||
appStateController.updateNftDropDownState.bind(appStateController),
|
||||
setFirstTimeUsedNetwork:
|
||||
|
@ -19,44 +19,51 @@ import { Text } from '../text';
|
||||
|
||||
import { AVATAR_BASE_SIZES } from './avatar-base.constants';
|
||||
|
||||
export const AvatarBase = ({
|
||||
size = AVATAR_BASE_SIZES.MD,
|
||||
children,
|
||||
backgroundColor = BackgroundColor.backgroundAlternative,
|
||||
borderColor = BorderColor.borderDefault,
|
||||
color = TextColor.textDefault,
|
||||
className,
|
||||
...props
|
||||
}) => {
|
||||
let fallbackTextVariant;
|
||||
export const AvatarBase = React.forwardRef(
|
||||
(
|
||||
{
|
||||
size = AVATAR_BASE_SIZES.MD,
|
||||
children,
|
||||
backgroundColor = BackgroundColor.backgroundAlternative,
|
||||
borderColor = BorderColor.borderDefault,
|
||||
color = TextColor.textDefault,
|
||||
className,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
let fallbackTextVariant;
|
||||
|
||||
if (size === AVATAR_BASE_SIZES.LG || size === AVATAR_BASE_SIZES.XL) {
|
||||
fallbackTextVariant = TextVariant.bodyLgMedium;
|
||||
} else if (size === AVATAR_BASE_SIZES.SM || size === AVATAR_BASE_SIZES.MD) {
|
||||
fallbackTextVariant = TextVariant.bodySm;
|
||||
} else {
|
||||
fallbackTextVariant = TextVariant.bodyXs;
|
||||
}
|
||||
return (
|
||||
<Text
|
||||
className={classnames(
|
||||
'mm-avatar-base',
|
||||
`mm-avatar-base--size-${size}`,
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
as="div"
|
||||
display={DISPLAY.FLEX}
|
||||
justifyContent={JustifyContent.center}
|
||||
alignItems={AlignItems.center}
|
||||
borderRadius={BorderRadius.full}
|
||||
variant={fallbackTextVariant}
|
||||
textTransform={TEXT_TRANSFORM.UPPERCASE}
|
||||
{...{ backgroundColor, borderColor, color, ...props }}
|
||||
>
|
||||
{children}
|
||||
</Text>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (size === AVATAR_BASE_SIZES.LG || size === AVATAR_BASE_SIZES.XL) {
|
||||
fallbackTextVariant = TextVariant.bodyLgMedium;
|
||||
} else if (size === AVATAR_BASE_SIZES.SM || size === AVATAR_BASE_SIZES.MD) {
|
||||
fallbackTextVariant = TextVariant.bodySm;
|
||||
} else {
|
||||
fallbackTextVariant = TextVariant.bodyXs;
|
||||
}
|
||||
return (
|
||||
<Text
|
||||
className={classnames(
|
||||
'mm-avatar-base',
|
||||
`mm-avatar-base--size-${size}`,
|
||||
className,
|
||||
)}
|
||||
as="div"
|
||||
display={DISPLAY.FLEX}
|
||||
justifyContent={JustifyContent.center}
|
||||
alignItems={AlignItems.center}
|
||||
borderRadius={BorderRadius.full}
|
||||
variant={fallbackTextVariant}
|
||||
textTransform={TEXT_TRANSFORM.UPPERCASE}
|
||||
{...{ backgroundColor, borderColor, color, ...props }}
|
||||
>
|
||||
{children}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
AvatarBase.propTypes = {
|
||||
/**
|
||||
* The size of the AvatarBase.
|
||||
@ -95,3 +102,5 @@ AvatarBase.propTypes = {
|
||||
*/
|
||||
...Text.propTypes,
|
||||
};
|
||||
|
||||
AvatarBase.displayName = 'AvatarBase';
|
||||
|
@ -14,70 +14,76 @@ import {
|
||||
} from '../../../helpers/constants/design-system';
|
||||
import { AVATAR_NETWORK_SIZES } from './avatar-network.constants';
|
||||
|
||||
export const AvatarNetwork = ({
|
||||
size = Size.MD,
|
||||
name,
|
||||
src,
|
||||
showHalo,
|
||||
color = TextColor.textDefault,
|
||||
backgroundColor = BackgroundColor.backgroundAlternative,
|
||||
borderColor = BorderColor.transparent,
|
||||
className,
|
||||
...props
|
||||
}) => {
|
||||
const [showFallback, setShowFallback] = useState(false);
|
||||
export const AvatarNetwork = React.forwardRef(
|
||||
(
|
||||
{
|
||||
size = Size.MD,
|
||||
name,
|
||||
src,
|
||||
showHalo,
|
||||
color = TextColor.textDefault,
|
||||
backgroundColor = BackgroundColor.backgroundAlternative,
|
||||
borderColor = BorderColor.transparent,
|
||||
className,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const [showFallback, setShowFallback] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setShowFallback(!src);
|
||||
}, [src]);
|
||||
useEffect(() => {
|
||||
setShowFallback(!src);
|
||||
}, [src]);
|
||||
|
||||
const fallbackString = name && name[0] ? name[0] : '?';
|
||||
const fallbackString = name && name[0] ? name[0] : '?';
|
||||
|
||||
const handleOnError = () => {
|
||||
setShowFallback(true);
|
||||
};
|
||||
const handleOnError = () => {
|
||||
setShowFallback(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<AvatarBase
|
||||
size={size}
|
||||
display={DISPLAY.FLEX}
|
||||
alignItems={AlignItems.center}
|
||||
justifyContent={JustifyContent.center}
|
||||
className={classnames(
|
||||
'mm-avatar-network',
|
||||
showHalo && 'mm-avatar-network--with-halo',
|
||||
className,
|
||||
)}
|
||||
{...{ backgroundColor, borderColor, color, ...props }}
|
||||
>
|
||||
{showFallback ? (
|
||||
fallbackString
|
||||
) : (
|
||||
<>
|
||||
{showHalo && (
|
||||
return (
|
||||
<AvatarBase
|
||||
ref={ref}
|
||||
size={size}
|
||||
display={DISPLAY.FLEX}
|
||||
alignItems={AlignItems.center}
|
||||
justifyContent={JustifyContent.center}
|
||||
className={classnames(
|
||||
'mm-avatar-network',
|
||||
showHalo && 'mm-avatar-network--with-halo',
|
||||
className,
|
||||
)}
|
||||
{...{ backgroundColor, borderColor, color, ...props }}
|
||||
>
|
||||
{showFallback ? (
|
||||
fallbackString
|
||||
) : (
|
||||
<>
|
||||
{showHalo && (
|
||||
<img
|
||||
src={src}
|
||||
className={
|
||||
showHalo ? 'mm-avatar-network__network-image--blurred' : ''
|
||||
}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
<img
|
||||
src={src}
|
||||
className={
|
||||
showHalo ? 'mm-avatar-network__network-image--blurred' : ''
|
||||
showHalo
|
||||
? 'mm-avatar-network__network-image--size-reduced'
|
||||
: 'mm-avatar-network__network-image'
|
||||
}
|
||||
aria-hidden="true"
|
||||
onError={handleOnError}
|
||||
src={src}
|
||||
alt={`${name} logo` || 'network logo'}
|
||||
/>
|
||||
)}
|
||||
<img
|
||||
className={
|
||||
showHalo
|
||||
? 'mm-avatar-network__network-image--size-reduced'
|
||||
: 'mm-avatar-network__network-image'
|
||||
}
|
||||
onError={handleOnError}
|
||||
src={src}
|
||||
alt={`${name} logo` || 'network logo'}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</AvatarBase>
|
||||
);
|
||||
};
|
||||
</>
|
||||
)}
|
||||
</AvatarBase>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
AvatarNetwork.propTypes = {
|
||||
/**
|
||||
@ -123,3 +129,5 @@ AvatarNetwork.propTypes = {
|
||||
*/
|
||||
...Box.propTypes,
|
||||
};
|
||||
|
||||
AvatarNetwork.displayName = 'AvatarNetwork';
|
||||
|
@ -23,7 +23,6 @@ import {
|
||||
} from '../../../helpers/constants/design-system';
|
||||
import {
|
||||
AvatarNetwork,
|
||||
Button,
|
||||
ButtonIcon,
|
||||
IconName,
|
||||
PickerNetwork,
|
||||
@ -31,29 +30,41 @@ import {
|
||||
|
||||
import {
|
||||
getCurrentNetwork,
|
||||
getOnboardedInThisUISession,
|
||||
getOriginOfCurrentTab,
|
||||
getSelectedIdentity,
|
||||
getShowProductTour,
|
||||
} from '../../../selectors';
|
||||
import { GlobalMenu, AccountPicker } from '..';
|
||||
import { GlobalMenu, ProductTour, AccountPicker } from '..';
|
||||
|
||||
import Box from '../../ui/box/box';
|
||||
import { toggleAccountMenu, toggleNetworkMenu } from '../../../store/actions';
|
||||
import {
|
||||
hideProductTour,
|
||||
toggleAccountMenu,
|
||||
toggleNetworkMenu,
|
||||
} from '../../../store/actions';
|
||||
import MetafoxLogo from '../../ui/metafox-logo';
|
||||
import { getEnvironmentType } from '../../../../app/scripts/lib/util';
|
||||
import { ENVIRONMENT_TYPE_POPUP } from '../../../../shared/constants/app';
|
||||
import ConnectedStatusIndicator from '../../app/connected-status-indicator';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
import { getCompletedOnboarding } from '../../../ducks/metamask/metamask';
|
||||
|
||||
export const AppHeader = ({ onClick }) => {
|
||||
const trackEvent = useContext(MetaMetricsContext);
|
||||
const [accountOptionsMenuOpen, setAccountOptionsMenuOpen] = useState(false);
|
||||
const [multichainProductTourStep, setMultichainProductTourStep] = useState(1);
|
||||
const menuRef = useRef(false);
|
||||
const origin = useSelector(getOriginOfCurrentTab);
|
||||
const history = useHistory();
|
||||
const isUnlocked = useSelector((state) => state.metamask.isUnlocked);
|
||||
|
||||
const t = useI18nContext();
|
||||
// Used for account picker
|
||||
const identity = useSelector(getSelectedIdentity);
|
||||
const dispatch = useDispatch();
|
||||
const completedOnboarding = useSelector(getCompletedOnboarding);
|
||||
const onboardedInThisUISession = useSelector(getOnboardedInThisUISession);
|
||||
const showProductTourPopup = useSelector(getShowProductTour);
|
||||
|
||||
// Used for network icon / dropdown
|
||||
const currentNetwork = useSelector(getCurrentNetwork);
|
||||
@ -64,12 +75,17 @@ export const AppHeader = ({ onClick }) => {
|
||||
getEnvironmentType() === ENVIRONMENT_TYPE_POPUP &&
|
||||
origin &&
|
||||
origin !== browser.runtime.id;
|
||||
const showProductTour =
|
||||
completedOnboarding && !onboardedInThisUISession && showProductTourPopup;
|
||||
const productTourDirection = document
|
||||
.querySelector('[dir]')
|
||||
?.getAttribute('dir');
|
||||
|
||||
return (
|
||||
<>
|
||||
{isUnlocked && !popupStatus ? (
|
||||
<Box
|
||||
display={DISPLAY.FLEX}
|
||||
display={[DISPLAY.NONE, DISPLAY.FLEX]}
|
||||
alignItems={AlignItems.center}
|
||||
margin={2}
|
||||
className="multichain-app-header-logo"
|
||||
@ -112,25 +128,43 @@ export const AppHeader = ({ onClick }) => {
|
||||
padding={2}
|
||||
gap={2}
|
||||
>
|
||||
{popupStatus ? (
|
||||
<Button
|
||||
className="multichain-app-header__contents--avatar-network"
|
||||
justifyContent={JustifyContent.flexStart}
|
||||
>
|
||||
<AvatarNetwork
|
||||
name={currentNetwork?.nickname}
|
||||
src={currentNetwork?.rpcPrefs?.imageUrl}
|
||||
size={Size.SM}
|
||||
onClick={() => dispatch(toggleNetworkMenu())}
|
||||
/>
|
||||
</Button>
|
||||
) : (
|
||||
<PickerNetwork
|
||||
label={currentNetwork?.nickname}
|
||||
src={currentNetwork?.rpcPrefs?.imageUrl}
|
||||
onClick={() => dispatch(toggleNetworkMenu())}
|
||||
<AvatarNetwork
|
||||
margin={2}
|
||||
className="multichain-app-header__contents--avatar-network"
|
||||
ref={menuRef}
|
||||
as="button"
|
||||
aria-label="Network Menu" // TODO: needs locale
|
||||
padding={0}
|
||||
name={currentNetwork?.nickname}
|
||||
src={currentNetwork?.rpcPrefs?.imageUrl}
|
||||
size={Size.SM}
|
||||
onClick={() => dispatch(toggleNetworkMenu())}
|
||||
display={[DISPLAY.FLEX, DISPLAY.NONE]} // show on popover hide on desktop
|
||||
/>
|
||||
<PickerNetwork
|
||||
margin={2}
|
||||
label={currentNetwork?.nickname}
|
||||
src={currentNetwork?.rpcPrefs?.imageUrl}
|
||||
onClick={() => dispatch(toggleNetworkMenu())}
|
||||
display={[DISPLAY.NONE, DISPLAY.FLEX]} // show on desktop hide on popover
|
||||
/>
|
||||
{showProductTour &&
|
||||
popupStatus &&
|
||||
multichainProductTourStep === 1 ? (
|
||||
<ProductTour
|
||||
className="multichain-app-header__product-tour"
|
||||
anchorElement={menuRef.current}
|
||||
title={t('switcherTitle')}
|
||||
description={t('switcherTourDescription')}
|
||||
currentStep="1"
|
||||
totalSteps="3"
|
||||
onClick={() =>
|
||||
setMultichainProductTourStep(multichainProductTourStep + 1)
|
||||
}
|
||||
positionObj={productTourDirection === 'rtl' ? '0%' : '88%'}
|
||||
productTourDirection={productTourDirection}
|
||||
/>
|
||||
)}
|
||||
) : null}
|
||||
|
||||
<AccountPicker
|
||||
address={identity.address}
|
||||
@ -143,8 +177,34 @@ export const AppHeader = ({ onClick }) => {
|
||||
justifyContent={JustifyContent.spaceBetween}
|
||||
>
|
||||
{showStatus ? (
|
||||
<ConnectedStatusIndicator
|
||||
onClick={() => history.push(CONNECTED_ACCOUNTS_ROUTE)}
|
||||
<Box ref={menuRef}>
|
||||
<ConnectedStatusIndicator
|
||||
onClick={() => history.push(CONNECTED_ACCOUNTS_ROUTE)}
|
||||
/>
|
||||
</Box>
|
||||
) : null}{' '}
|
||||
{popupStatus && multichainProductTourStep === 2 ? (
|
||||
<ProductTour
|
||||
className="multichain-app-header__product-tour"
|
||||
anchorElement={menuRef.current}
|
||||
closeMenu={() => setAccountOptionsMenuOpen(false)}
|
||||
prevIcon
|
||||
title={t('permissionsTitle')}
|
||||
description={t('permissionsTourDescription')}
|
||||
currentStep="2"
|
||||
totalSteps="3"
|
||||
prevClick={() =>
|
||||
setMultichainProductTourStep(
|
||||
multichainProductTourStep - 1,
|
||||
)
|
||||
}
|
||||
onClick={() =>
|
||||
setMultichainProductTourStep(
|
||||
multichainProductTourStep + 1,
|
||||
)
|
||||
}
|
||||
positionObj={productTourDirection === 'rtl' ? '74%' : '12%'}
|
||||
productTourDirection={productTourDirection}
|
||||
/>
|
||||
) : null}
|
||||
<Box
|
||||
@ -176,6 +236,28 @@ export const AppHeader = ({ onClick }) => {
|
||||
closeMenu={() => setAccountOptionsMenuOpen(false)}
|
||||
/>
|
||||
) : null}
|
||||
{showProductTour &&
|
||||
popupStatus &&
|
||||
multichainProductTourStep === 3 ? (
|
||||
<ProductTour
|
||||
className="multichain-app-header__product-tour"
|
||||
anchorElement={menuRef.current}
|
||||
closeMenu={() => setAccountOptionsMenuOpen(false)}
|
||||
prevIcon
|
||||
title={t('globalTitle')}
|
||||
description={t('globalTourDescription')}
|
||||
currentStep="3"
|
||||
totalSteps="3"
|
||||
prevClick={() =>
|
||||
setMultichainProductTourStep(multichainProductTourStep - 1)
|
||||
}
|
||||
onClick={() => {
|
||||
hideProductTour();
|
||||
}}
|
||||
positionObj={productTourDirection === 'rtl' ? '89%' : '0%'}
|
||||
productTourDirection={productTourDirection}
|
||||
/>
|
||||
) : null}
|
||||
</Box>
|
||||
) : (
|
||||
<Box
|
||||
|
@ -30,15 +30,7 @@
|
||||
}
|
||||
|
||||
&--avatar-network {
|
||||
background-color: transparent;
|
||||
width: min-content;
|
||||
padding: 8px;
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
box-shadow: none;
|
||||
background: transparent;
|
||||
}
|
||||
padding: 0; // TODO: Remove once https://github.com/MetaMask/metamask-extension/pull/17006 is merged
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,6 +97,9 @@ describe('App Header', () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
appState: {
|
||||
onboardedInThisUISession: false,
|
||||
},
|
||||
};
|
||||
|
||||
const mockStore = configureStore();
|
||||
|
@ -11,3 +11,4 @@ export { AddressCopyButton } from './address-copy-button';
|
||||
export { MultichainConnectedSiteMenu } from './multichain-connected-site-menu';
|
||||
export { NetworkListItem } from './network-list-item';
|
||||
export { NetworkListMenu } from './network-list-menu';
|
||||
export { ProductTour } from './product-tour-popover';
|
||||
|
@ -14,3 +14,4 @@
|
||||
@import 'multichain-token-list-item/multichain-token-list-item';
|
||||
@import 'network-list-item/';
|
||||
@import 'network-list-menu/';
|
||||
@import 'product-tour-popover/product-tour-popover';
|
||||
|
1
ui/components/multichain/product-tour-popover/index.js
Normal file
1
ui/components/multichain/product-tour-popover/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { ProductTour } from './product-tour-popover';
|
@ -0,0 +1,179 @@
|
||||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import Box from '../../ui/box/box';
|
||||
import {
|
||||
AlignItems,
|
||||
BLOCK_SIZES,
|
||||
BorderRadius,
|
||||
BackgroundColor,
|
||||
DISPLAY,
|
||||
IconColor,
|
||||
JustifyContent,
|
||||
Size,
|
||||
TextColor,
|
||||
TextVariant,
|
||||
TextAlign,
|
||||
} from '../../../helpers/constants/design-system';
|
||||
import {
|
||||
ButtonBase,
|
||||
ButtonIcon,
|
||||
IconName,
|
||||
Text,
|
||||
} from '../../component-library';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
import { Menu } from '../../ui/menu';
|
||||
|
||||
export const ProductTour = ({
|
||||
className,
|
||||
prevIcon,
|
||||
title,
|
||||
description,
|
||||
currentStep,
|
||||
totalSteps,
|
||||
positionObj = '5%',
|
||||
closeMenu,
|
||||
anchorElement,
|
||||
onClick,
|
||||
prevClick,
|
||||
productTourDirection,
|
||||
...props
|
||||
}) => {
|
||||
const t = useI18nContext();
|
||||
return (
|
||||
<Menu
|
||||
className={classnames(
|
||||
'multichain-product-tour-menu',
|
||||
{
|
||||
'multichain-product-tour-menu--rtl': productTourDirection === 'rtl',
|
||||
},
|
||||
className,
|
||||
)}
|
||||
anchorElement={anchorElement}
|
||||
onHide={closeMenu}
|
||||
data-testid="multichain-product-tour-menu-popover"
|
||||
{...props}
|
||||
>
|
||||
<Box
|
||||
className="multichain-product-tour-menu__container"
|
||||
backgroundColor={BackgroundColor.infoDefault}
|
||||
borderRadius={BorderRadius.LG}
|
||||
padding={4}
|
||||
>
|
||||
<Box
|
||||
borderWidth={1}
|
||||
className="multichain-product-tour-menu__arrow"
|
||||
display={DISPLAY.FLEX}
|
||||
justifyContent={JustifyContent.center}
|
||||
alignItems={AlignItems.center}
|
||||
style={{ right: positionObj }}
|
||||
/>
|
||||
<Box
|
||||
display={DISPLAY.FLEX}
|
||||
alignItems={AlignItems.center}
|
||||
className="multichain-product-tour-menu__header"
|
||||
>
|
||||
{prevIcon ? (
|
||||
<ButtonIcon
|
||||
iconName={IconName.ArrowLeft}
|
||||
size={Size.SM}
|
||||
color={IconColor.infoInverse}
|
||||
onClick={prevClick}
|
||||
className="multichain-product-tour-menu__previous-icon"
|
||||
data-testid="multichain-product-tour-menu-popover-prevIcon"
|
||||
/>
|
||||
) : null}
|
||||
<Text
|
||||
textAlign={TextAlign.Center}
|
||||
variant={TextVariant.headingSm}
|
||||
width={BLOCK_SIZES.FULL}
|
||||
color={TextColor.infoInverse}
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
</Box>
|
||||
<Text
|
||||
paddingBottom={2}
|
||||
paddingTop={2}
|
||||
color={TextColor.infoInverse}
|
||||
variant={TextVariant.bodyMd}
|
||||
>
|
||||
{description}
|
||||
</Text>
|
||||
<Box
|
||||
display={DISPLAY.FLEX}
|
||||
alignItems={AlignItems.center}
|
||||
justifyContent={JustifyContent.spaceBetween}
|
||||
>
|
||||
<Text
|
||||
paddingBottom={2}
|
||||
paddingTop={2}
|
||||
color={TextColor.infoInverse}
|
||||
variant={TextVariant.bodyMd}
|
||||
>
|
||||
{currentStep}/{totalSteps}
|
||||
</Text>
|
||||
<ButtonBase
|
||||
backgroundColor={BackgroundColor.primaryInverse}
|
||||
color={TextColor.primaryDefault}
|
||||
className="multichain-product-tour-menu__button"
|
||||
onClick={onClick}
|
||||
>
|
||||
{t('recoveryPhraseReminderConfirm')}
|
||||
</ButtonBase>
|
||||
</Box>
|
||||
</Box>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
ProductTour.propTypes = {
|
||||
/**
|
||||
* The element that the menu should display next to
|
||||
*/
|
||||
anchorElement: PropTypes.instanceOf(window.Element),
|
||||
/**
|
||||
* Function that closes this menu
|
||||
*/
|
||||
closeMenu: PropTypes.func.isRequired,
|
||||
/**
|
||||
* Additional classNames to be added
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
/**
|
||||
* Boolean to decide whether to show prevIcon or not
|
||||
*/
|
||||
prevIcon: PropTypes.bool,
|
||||
/**
|
||||
* Title of the popover
|
||||
*/
|
||||
title: PropTypes.string,
|
||||
/**
|
||||
* Description of the popover
|
||||
*/
|
||||
description: PropTypes.string,
|
||||
/**
|
||||
* Current step in the product tour
|
||||
*/
|
||||
currentStep: PropTypes.string,
|
||||
/**
|
||||
* Total steps in the product tour
|
||||
*/
|
||||
totalSteps: PropTypes.string,
|
||||
/**
|
||||
* PositionObj to decide the position of the popover tip
|
||||
*/
|
||||
positionObj: PropTypes.string,
|
||||
/**
|
||||
* The onClick handler to be passed
|
||||
*/
|
||||
onClick: PropTypes.func,
|
||||
/**
|
||||
* The handler to be passed to prevIcon
|
||||
*/
|
||||
prevClick: PropTypes.func,
|
||||
/**
|
||||
* Direction to determine the css for menu component
|
||||
*/
|
||||
productTourDirection: PropTypes.string,
|
||||
};
|
@ -0,0 +1,57 @@
|
||||
.multichain-product-tour-menu {
|
||||
width: 344px;
|
||||
box-shadow: none;
|
||||
left: -7px !important;
|
||||
top: 10px !important; //important is required here since Menu has absolute position added via inline style in base component.
|
||||
|
||||
&--rtl {
|
||||
left: 6px !important;
|
||||
right: 6px !important;
|
||||
}
|
||||
|
||||
&__arrow,
|
||||
&__arrow::before {
|
||||
position: absolute;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
&__arrow {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
visibility: hidden;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&__arrow::before {
|
||||
display: block;
|
||||
background-color: inherit;
|
||||
visibility: visible;
|
||||
content: '';
|
||||
transform: rotate(45deg);
|
||||
border-radius: 2px 0 0 0;
|
||||
top: -7px;
|
||||
}
|
||||
|
||||
&__header {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&__previous-icon {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&__button {
|
||||
&:hover {
|
||||
color: var(--color-primary-default);
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.5;
|
||||
background-color: var(--color-primary-inverse);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
import React from 'react';
|
||||
import { ProductTour } from './product-tour-popover';
|
||||
|
||||
export default {
|
||||
title: 'Components/Multichain/ProductTour',
|
||||
component: ProductTour,
|
||||
argTypes: {
|
||||
prevIcon: {
|
||||
control: 'boolean',
|
||||
},
|
||||
title: {
|
||||
control: 'text',
|
||||
},
|
||||
description: {
|
||||
control: 'text',
|
||||
},
|
||||
currentStep: {
|
||||
control: 'text',
|
||||
},
|
||||
totalSteps: {
|
||||
control: 'text',
|
||||
},
|
||||
positionObj: {
|
||||
control: 'text',
|
||||
},
|
||||
onClick: {
|
||||
action: 'onClick',
|
||||
},
|
||||
onHide: {
|
||||
action: 'onHide',
|
||||
},
|
||||
closeMenu: {
|
||||
action: 'closeMenu',
|
||||
},
|
||||
},
|
||||
args: {
|
||||
prevIcon: true,
|
||||
title: 'Permissions',
|
||||
description: 'Find your connected accounts and manage permissions here.',
|
||||
currentStep: '1',
|
||||
totalSteps: '3',
|
||||
},
|
||||
};
|
||||
|
||||
const Template = (args) => {
|
||||
return <ProductTour {...args} />;
|
||||
};
|
||||
|
||||
export const DefaultStory = Template.bind({});
|
||||
|
||||
export const CustomPopoverTipPosition = Template.bind({});
|
||||
CustomPopoverTipPosition.args = {
|
||||
positionObj: '80%',
|
||||
};
|
@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import { ProductTour } from './product-tour-popover';
|
||||
|
||||
describe('DetectedTokensBanner', () => {
|
||||
const props = {
|
||||
title: 'Permissions',
|
||||
description: 'Find your connected accounts and manage permissions here.',
|
||||
currentStep: '1',
|
||||
totalSteps: '3',
|
||||
};
|
||||
it('should render correctly', () => {
|
||||
const { getByTestId } = render(
|
||||
<ProductTour anchorElement={document.body} {...props} />,
|
||||
);
|
||||
const menuContainer = getByTestId('multichain-product-tour-menu-popover');
|
||||
expect(menuContainer).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render prev Icon', () => {
|
||||
const { getByTestId } = render(
|
||||
<ProductTour anchorElement={document.body} {...props} prevIcon />,
|
||||
);
|
||||
const prevIcon = getByTestId(
|
||||
'multichain-product-tour-menu-popover-prevIcon',
|
||||
);
|
||||
expect(prevIcon).toBeInTheDocument();
|
||||
});
|
||||
});
|
@ -1041,6 +1041,9 @@ export function getShowBetaHeader(state) {
|
||||
return state.metamask.showBetaHeader;
|
||||
}
|
||||
|
||||
export function getShowProductTour(state) {
|
||||
return state.metamask.showProductTour;
|
||||
}
|
||||
/**
|
||||
* To get the useTokenDetection flag which determines whether a static or dynamic token list is used
|
||||
*
|
||||
@ -1431,6 +1434,10 @@ export function getCustomTokenAmount(state) {
|
||||
return state.appState.customTokenAmount;
|
||||
}
|
||||
|
||||
export function getOnboardedInThisUISession(state) {
|
||||
return state.appState.onboardedInThisUISession;
|
||||
}
|
||||
|
||||
/**
|
||||
* To get the useCurrencyRateCheck flag which to check if the user prefers currency conversion
|
||||
*
|
||||
|
@ -4560,6 +4560,10 @@ export function hideBetaHeader() {
|
||||
return submitRequestToBackground('setShowBetaHeader', [false]);
|
||||
}
|
||||
|
||||
export function hideProductTour() {
|
||||
return submitRequestToBackground('setShowProductTour', [false]);
|
||||
}
|
||||
|
||||
// TODO: codeword NOT_A_THUNK @brad-decker
|
||||
export function setTransactionSecurityCheckEnabled(
|
||||
transactionSecurityCheckEnabled: boolean,
|
||||
|
Loading…
Reference in New Issue
Block a user