Created a custom spending cap component (#15522)
parent
947f5299f8
commit
8e736e39d8
@ -0,0 +1,43 @@ |
||||
import React from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
import Box from '../../ui/box'; |
||||
import Typography from '../../ui/typography'; |
||||
import Tooltip from '../../ui/tooltip'; |
||||
import { |
||||
COLORS, |
||||
DISPLAY, |
||||
TYPOGRAPHY, |
||||
} from '../../../helpers/constants/design-system'; |
||||
|
||||
export const CustomSpendingCapTooltip = ({ |
||||
tooltipContentText, |
||||
tooltipIcon, |
||||
}) => ( |
||||
<Box display={DISPLAY.INLINE_BLOCK}> |
||||
<Tooltip |
||||
interactive |
||||
position="top" |
||||
html={ |
||||
<Typography |
||||
variant={TYPOGRAPHY.H7} |
||||
margin={3} |
||||
color={COLORS.TEXT_ALTERNATIVE} |
||||
className="form-field__heading-title__tooltip" |
||||
> |
||||
{tooltipContentText} |
||||
</Typography> |
||||
} |
||||
> |
||||
{tooltipIcon ? ( |
||||
<i className="fa fa-exclamation-triangle form-field__heading-title__tooltip__warning-icon" /> |
||||
) : ( |
||||
tooltipIcon !== '' && <i className="fa fa-question-circle" /> |
||||
)} |
||||
</Tooltip> |
||||
</Box> |
||||
); |
||||
|
||||
CustomSpendingCapTooltip.propTypes = { |
||||
tooltipContentText: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), |
||||
tooltipIcon: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), |
||||
}; |
@ -0,0 +1,207 @@ |
||||
import React, { useState, useContext } from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
import { I18nContext } from '../../../contexts/i18n'; |
||||
import Box from '../../ui/box'; |
||||
import FormField from '../../ui/form-field'; |
||||
import Typography from '../../ui/typography'; |
||||
import { |
||||
ALIGN_ITEMS, |
||||
COLORS, |
||||
DISPLAY, |
||||
FLEX_DIRECTION, |
||||
TEXT_ALIGN, |
||||
FONT_WEIGHT, |
||||
TYPOGRAPHY, |
||||
JUSTIFY_CONTENT, |
||||
SIZES, |
||||
} from '../../../helpers/constants/design-system'; |
||||
import { CustomSpendingCapTooltip } from './custom-spending-cap-tooltip'; |
||||
|
||||
export default function CustomSpendingCap({ |
||||
tokenName, |
||||
currentTokenBalance, |
||||
dappProposedValue, |
||||
siteOrigin, |
||||
onEdit, |
||||
}) { |
||||
const t = useContext(I18nContext); |
||||
const [value, setValue] = useState(''); |
||||
const [customSpendingCapText, setCustomSpendingCapText] = useState(''); |
||||
const [error, setError] = useState(''); |
||||
const inputLogicEmptyStateText = t('inputLogicEmptyState'); |
||||
|
||||
const getInputTextLogic = (inputNumber) => { |
||||
if (inputNumber <= currentTokenBalance) { |
||||
return { |
||||
className: 'custom-spending-cap__lowerValue', |
||||
description: t('inputLogicEqualOrSmallerNumber', [ |
||||
<Typography |
||||
key="custom-spending-cap" |
||||
variant={TYPOGRAPHY.H6} |
||||
fontWeight={FONT_WEIGHT.BOLD} |
||||
className="custom-spending-cap__input-value-and-token-name" |
||||
> |
||||
{inputNumber} {tokenName} |
||||
</Typography>, |
||||
]), |
||||
}; |
||||
} else if (inputNumber > currentTokenBalance) { |
||||
return { |
||||
className: 'custom-spending-cap__higherValue', |
||||
description: t('inputLogicHigherNumber'), |
||||
}; |
||||
} |
||||
return { |
||||
className: 'custom-spending-cap__emptyState', |
||||
description: t('inputLogicEmptyState'), |
||||
}; |
||||
}; |
||||
|
||||
const handleChange = (valueInput) => { |
||||
let spendingCapError = ''; |
||||
const inputTextLogic = getInputTextLogic(valueInput); |
||||
const inputTextLogicDescription = inputTextLogic.description; |
||||
|
||||
if (valueInput < 0 || isNaN(valueInput)) { |
||||
spendingCapError = t('spendingCapError'); |
||||
setCustomSpendingCapText(t('spendingCapErrorDescription', [siteOrigin])); |
||||
setError(spendingCapError); |
||||
} else { |
||||
setCustomSpendingCapText(inputTextLogicDescription); |
||||
setError(''); |
||||
} |
||||
|
||||
setValue(valueInput); |
||||
}; |
||||
|
||||
const chooseTooltipContentText = |
||||
value > currentTokenBalance |
||||
? t('warningTooltipText', [ |
||||
<Typography |
||||
key="tooltip-text" |
||||
variant={TYPOGRAPHY.H7} |
||||
fontWeight={FONT_WEIGHT.BOLD} |
||||
color={COLORS.ERROR_DEFAULT} |
||||
> |
||||
<i className="fa fa-exclamation-circle" /> {t('beCareful')} |
||||
</Typography>, |
||||
]) |
||||
: t('inputLogicEmptyState'); |
||||
|
||||
return ( |
||||
<> |
||||
<Box |
||||
textAlign={TEXT_ALIGN.END} |
||||
className="custom-spending-cap__max-button" |
||||
> |
||||
<button |
||||
className="custom-spending-cap__input--max-button" |
||||
type="link" |
||||
onClick={(e) => { |
||||
e.preventDefault(); |
||||
handleChange(currentTokenBalance); |
||||
setValue(currentTokenBalance); |
||||
}} |
||||
> |
||||
{t('max')} |
||||
</button> |
||||
</Box> |
||||
<Box |
||||
className="custom-spending-cap" |
||||
borderRadius={SIZES.SM} |
||||
paddingTop={2} |
||||
paddingRight={6} |
||||
paddingLeft={6} |
||||
display={DISPLAY.FLEX} |
||||
alignItems={ALIGN_ITEMS.FLEX_START} |
||||
flexDirection={FLEX_DIRECTION.COLUMN} |
||||
backgroundColor={COLORS.BACKGROUND_ALTERNATIVE} |
||||
gap={2} |
||||
> |
||||
<Box |
||||
justifyContent={JUSTIFY_CONTENT.CENTER} |
||||
display={DISPLAY.BLOCK} |
||||
className="custom-spending-cap__input" |
||||
> |
||||
<label |
||||
htmlFor={ |
||||
value > (currentTokenBalance || error) |
||||
? 'custom-spending-cap-input-value' |
||||
: 'custom-spending-cap' |
||||
} |
||||
> |
||||
<FormField |
||||
dataTestId="custom-spending-cap-input" |
||||
autoFocus |
||||
wrappingLabelProps={{ as: 'div' }} |
||||
id={ |
||||
value > (currentTokenBalance || error) |
||||
? 'custom-spending-cap-input-value' |
||||
: 'custom-spending-cap' |
||||
} |
||||
TooltipCustomComponent={ |
||||
<CustomSpendingCapTooltip |
||||
tooltipContentText={value ? chooseTooltipContentText : ''} |
||||
tooltipIcon={value ? value > currentTokenBalance : ''} |
||||
/> |
||||
} |
||||
onChange={handleChange} |
||||
titleText={t('customSpendingCap')} |
||||
placeholder={t('enterANumber')} |
||||
error={error} |
||||
value={value} |
||||
titleDetail={ |
||||
<button |
||||
className="custom-spending-cap__input--button" |
||||
type="link" |
||||
onClick={(e) => { |
||||
e.preventDefault(); |
||||
if (value <= currentTokenBalance || error) { |
||||
handleChange(dappProposedValue); |
||||
setValue(dappProposedValue); |
||||
} else { |
||||
onEdit(); |
||||
} |
||||
}} |
||||
> |
||||
{value > currentTokenBalance ? t('edit') : t('useDefault')} |
||||
</button> |
||||
} |
||||
titleDetailWrapperProps={{ marginBottom: 2, marginRight: 0 }} |
||||
/> |
||||
<Typography |
||||
color={COLORS.TEXT_DEFAULT} |
||||
variant={TYPOGRAPHY.H7} |
||||
boxProps={{ paddingTop: 2, paddingBottom: 2 }} |
||||
> |
||||
{value ? customSpendingCapText : inputLogicEmptyStateText} |
||||
</Typography> |
||||
</label> |
||||
</Box> |
||||
</Box> |
||||
</> |
||||
); |
||||
} |
||||
|
||||
CustomSpendingCap.propTypes = { |
||||
/** |
||||
* Displayed the token name currently tracked in description related to the input state |
||||
*/ |
||||
tokenName: PropTypes.string, |
||||
/** |
||||
* The current token balance of the token |
||||
*/ |
||||
currentTokenBalance: PropTypes.number, |
||||
/** |
||||
* The dapp suggested amount |
||||
*/ |
||||
dappProposedValue: PropTypes.number, |
||||
/** |
||||
* The origin of the site generally the URL |
||||
*/ |
||||
siteOrigin: PropTypes.string, |
||||
/** |
||||
* onClick handler for the Edit link |
||||
*/ |
||||
onEdit: PropTypes.func, |
||||
}; |
@ -0,0 +1,36 @@ |
||||
import React from 'react'; |
||||
import CustomSpendingCap from './custom-spending-cap'; |
||||
|
||||
export default { |
||||
title: 'Components/App/CustomSpendingCap', |
||||
id: __filename, |
||||
argTypes: { |
||||
tokenName: { |
||||
control: { type: 'text' }, |
||||
}, |
||||
currentTokenBalance: { |
||||
control: { type: 'number' }, |
||||
}, |
||||
dappProposedValue: { |
||||
control: { type: 'number' }, |
||||
}, |
||||
siteOrigin: { |
||||
control: { type: 'text' }, |
||||
}, |
||||
onEdit: { |
||||
action: 'onEdit', |
||||
}, |
||||
}, |
||||
args: { |
||||
tokenName: 'DAI', |
||||
currentTokenBalance: 200.12, |
||||
dappProposedValue: 7, |
||||
siteOrigin: 'Uniswap.org', |
||||
}, |
||||
}; |
||||
|
||||
export const DefaultStory = (args) => { |
||||
return <CustomSpendingCap {...args} />; |
||||
}; |
||||
|
||||
DefaultStory.storyName = 'Default'; |
@ -0,0 +1 @@ |
||||
export { default } from './custom-spending-cap'; |
@ -0,0 +1,31 @@ |
||||
.custom-spending-cap { |
||||
&__input-value-and-token-name { |
||||
display: contents; |
||||
} |
||||
|
||||
&__max-button { |
||||
position: relative; |
||||
} |
||||
|
||||
&__input { |
||||
width: 100%; |
||||
|
||||
&--button { |
||||
background: none; |
||||
color: var(--color-primary-default); |
||||
} |
||||
|
||||
&--max-button { |
||||
color: var(--color-text-alternative); |
||||
background: none; |
||||
position: absolute; |
||||
margin-top: 55px; |
||||
margin-inline-start: -75px; |
||||
} |
||||
} |
||||
|
||||
#custom-spending-cap-input-value { |
||||
color: var(--color-error-default); |
||||
padding-inline-end: 60px; |
||||
} |
||||
} |
Loading…
Reference in new issue