commit
74719a8102
@ -0,0 +1,13 @@ |
||||
#!/usr/bin/env bash |
||||
|
||||
set -e |
||||
set -u |
||||
set -o pipefail |
||||
|
||||
# Generate LavaMoat policies for the extension background script for each build |
||||
# type. |
||||
# ATTN: This may tax your device when running it locally. |
||||
concurrently --kill-others-on-fail -n main,beta,flask \ |
||||
"WRITE_AUTO_POLICY=1 yarn dist" \ |
||||
"WRITE_AUTO_POLICY=1 yarn dist --build-type beta" \ |
||||
"WRITE_AUTO_POLICY=1 yarn dist --build-type flask" |
@ -0,0 +1,55 @@ |
||||
{ |
||||
"resources": { |
||||
"browser-resolve": { |
||||
"packages": { |
||||
"core-js": true |
||||
} |
||||
}, |
||||
"babel-runtime": { |
||||
"packages": { |
||||
"@babel/runtime": true |
||||
} |
||||
}, |
||||
"node-fetch": { |
||||
"globals": { |
||||
"fetch": true |
||||
} |
||||
}, |
||||
"lodash": { |
||||
"globals": { |
||||
"setTimeout": true, |
||||
"clearTimeout": true |
||||
} |
||||
}, |
||||
"@ethersproject/random": { |
||||
"globals": { |
||||
"crypto.getRandomValues": true |
||||
} |
||||
}, |
||||
"browser-passworder": { |
||||
"globals": { |
||||
"crypto": true |
||||
} |
||||
}, |
||||
"randombytes": { |
||||
"globals": { |
||||
"crypto.getRandomValues": true |
||||
} |
||||
}, |
||||
"extensionizer": { |
||||
"globals": { |
||||
"console": true |
||||
} |
||||
}, |
||||
"web3": { |
||||
"globals": { |
||||
"XMLHttpRequest": true |
||||
} |
||||
}, |
||||
"storage": { |
||||
"globals": { |
||||
"localStorage": true |
||||
} |
||||
} |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,55 @@ |
||||
{ |
||||
"resources": { |
||||
"browser-resolve": { |
||||
"packages": { |
||||
"core-js": true |
||||
} |
||||
}, |
||||
"babel-runtime": { |
||||
"packages": { |
||||
"@babel/runtime": true |
||||
} |
||||
}, |
||||
"node-fetch": { |
||||
"globals": { |
||||
"fetch": true |
||||
} |
||||
}, |
||||
"lodash": { |
||||
"globals": { |
||||
"setTimeout": true, |
||||
"clearTimeout": true |
||||
} |
||||
}, |
||||
"@ethersproject/random": { |
||||
"globals": { |
||||
"crypto.getRandomValues": true |
||||
} |
||||
}, |
||||
"browser-passworder": { |
||||
"globals": { |
||||
"crypto": true |
||||
} |
||||
}, |
||||
"randombytes": { |
||||
"globals": { |
||||
"crypto.getRandomValues": true |
||||
} |
||||
}, |
||||
"extensionizer": { |
||||
"globals": { |
||||
"console": true |
||||
} |
||||
}, |
||||
"web3": { |
||||
"globals": { |
||||
"XMLHttpRequest": true |
||||
} |
||||
}, |
||||
"storage": { |
||||
"globals": { |
||||
"localStorage": true |
||||
} |
||||
} |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,124 @@ |
||||
import { fireEvent } from '@testing-library/react'; |
||||
import React from 'react'; |
||||
import configureMockStore from 'redux-mock-store'; |
||||
import { renderWithProvider } from '../../../../../test/lib/render-helpers'; |
||||
import { TRANSACTION_ERROR_KEY } from '../../../../helpers/constants/error-keys'; |
||||
import ConfirmPageContainerContent from './confirm-page-container-content.component'; |
||||
|
||||
describe('Confirm Page Container Content', () => { |
||||
const mockStore = { |
||||
metamask: { |
||||
provider: { |
||||
type: 'test', |
||||
}, |
||||
}, |
||||
}; |
||||
|
||||
const store = configureMockStore()(mockStore); |
||||
|
||||
let props = {}; |
||||
|
||||
beforeEach(() => { |
||||
const mockOnCancel = jest.fn(); |
||||
const mockOnCancelAll = jest.fn(); |
||||
const mockOnSubmit = jest.fn(); |
||||
const mockOnConfirmAnyways = jest.fn(); |
||||
props = { |
||||
action: ' Withdraw Stake', |
||||
errorMessage: null, |
||||
errorKey: null, |
||||
hasSimulationError: true, |
||||
onCancelAll: mockOnCancelAll, |
||||
onCancel: mockOnCancel, |
||||
cancelText: 'Reject', |
||||
onSubmit: mockOnSubmit, |
||||
onConfirmAnyways: mockOnConfirmAnyways, |
||||
submitText: 'Confirm', |
||||
disabled: true, |
||||
origin: 'http://localhost:4200', |
||||
hideTitle: false, |
||||
}; |
||||
}); |
||||
|
||||
it('render ConfirmPageContainer component with simulation error', async () => { |
||||
const { queryByText, getByText } = renderWithProvider( |
||||
<ConfirmPageContainerContent {...props} />, |
||||
store, |
||||
); |
||||
|
||||
expect( |
||||
queryByText('Transaction Error. Exception thrown in contract code.'), |
||||
).not.toBeInTheDocument(); |
||||
expect( |
||||
queryByText( |
||||
'This transaction is expected to fail. Trying to execute it is expected to be expensive but fail, and is not recommended.', |
||||
), |
||||
).toBeInTheDocument(); |
||||
expect(queryByText('I will try anyway')).toBeInTheDocument(); |
||||
|
||||
const confirmButton = getByText('Confirm'); |
||||
expect(getByText('Confirm').closest('button')).toBeDisabled(); |
||||
fireEvent.click(confirmButton); |
||||
expect(props.onSubmit).toHaveBeenCalledTimes(0); |
||||
|
||||
const iWillTryButton = getByText('I will try anyway'); |
||||
fireEvent.click(iWillTryButton); |
||||
expect(props.onConfirmAnyways).toHaveBeenCalledTimes(1); |
||||
|
||||
const cancelButton = getByText('Reject'); |
||||
fireEvent.click(cancelButton); |
||||
expect(props.onCancel).toHaveBeenCalledTimes(1); |
||||
}); |
||||
|
||||
it('render ConfirmPageContainer component with another error', async () => { |
||||
props.hasSimulationError = false; |
||||
props.disabled = true; |
||||
props.errorKey = TRANSACTION_ERROR_KEY; |
||||
const { queryByText, getByText } = renderWithProvider( |
||||
<ConfirmPageContainerContent {...props} />, |
||||
store, |
||||
); |
||||
|
||||
expect( |
||||
queryByText( |
||||
'This transaction is expected to fail. Trying to execute it is expected to be expensive but fail, and is not recommended.', |
||||
), |
||||
).not.toBeInTheDocument(); |
||||
expect(queryByText('I will try anyway')).not.toBeInTheDocument(); |
||||
expect(getByText('Confirm').closest('button')).toBeDisabled(); |
||||
expect( |
||||
getByText('Transaction Error. Exception thrown in contract code.'), |
||||
).toBeInTheDocument(); |
||||
|
||||
const cancelButton = getByText('Reject'); |
||||
fireEvent.click(cancelButton); |
||||
expect(props.onCancel).toHaveBeenCalledTimes(1); |
||||
}); |
||||
|
||||
it('render ConfirmPageContainer component with no errors', async () => { |
||||
props.hasSimulationError = false; |
||||
props.disabled = false; |
||||
const { queryByText, getByText } = renderWithProvider( |
||||
<ConfirmPageContainerContent {...props} />, |
||||
store, |
||||
); |
||||
|
||||
expect( |
||||
queryByText( |
||||
'This transaction is expected to fail. Trying to execute it is expected to be expensive but fail, and is not recommended.', |
||||
), |
||||
).not.toBeInTheDocument(); |
||||
expect( |
||||
queryByText('Transaction Error. Exception thrown in contract code.'), |
||||
).not.toBeInTheDocument(); |
||||
expect(queryByText('I will try anyway')).not.toBeInTheDocument(); |
||||
|
||||
const confirmButton = getByText('Confirm'); |
||||
fireEvent.click(confirmButton); |
||||
expect(props.onSubmit).toHaveBeenCalledTimes(1); |
||||
|
||||
const cancelButton = getByText('Reject'); |
||||
fireEvent.click(cancelButton); |
||||
expect(props.onCancel).toHaveBeenCalledTimes(1); |
||||
}); |
||||
}); |
@ -0,0 +1,68 @@ |
||||
import React from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
|
||||
import { PRIORITY_LEVELS } from '../../../../shared/constants/gas'; |
||||
import { useI18nContext } from '../../../hooks/useI18nContext'; |
||||
import Popover from '../../ui/popover'; |
||||
import I18nValue from '../../ui/i18n-value'; |
||||
import LoadingHeartBeat from '../../ui/loading-heartbeat'; |
||||
|
||||
import EditGasItem from './edit-gas-item'; |
||||
|
||||
const EditGasFeePopover = ({ onClose }) => { |
||||
const t = useI18nContext(); |
||||
|
||||
return ( |
||||
<Popover |
||||
title={t('editGasFeeModalTitle')} |
||||
onClose={onClose} |
||||
className="edit-gas-fee-popover" |
||||
> |
||||
<> |
||||
{process.env.IN_TEST === 'true' ? null : <LoadingHeartBeat />} |
||||
<div className="edit-gas-fee-popover__wrapper"> |
||||
<div className="edit-gas-fee-popover__content"> |
||||
<div className="edit-gas-fee-popover__content__header"> |
||||
<span className="edit-gas-fee-popover__content__header-option"> |
||||
<I18nValue messageKey="gasOption" /> |
||||
</span> |
||||
<span className="edit-gas-fee-popover__content__header-time"> |
||||
<I18nValue messageKey="time" /> |
||||
</span> |
||||
<span className="edit-gas-fee-popover__content__header-max-fee"> |
||||
<I18nValue messageKey="maxFee" /> |
||||
</span> |
||||
</div> |
||||
<EditGasItem |
||||
priorityLevel={PRIORITY_LEVELS.LOW} |
||||
onClose={onClose} |
||||
/> |
||||
<EditGasItem |
||||
priorityLevel={PRIORITY_LEVELS.MEDIUM} |
||||
onClose={onClose} |
||||
/> |
||||
<EditGasItem |
||||
priorityLevel={PRIORITY_LEVELS.HIGH} |
||||
onClose={onClose} |
||||
/> |
||||
<div className="edit-gas-fee-popover__content__separator" /> |
||||
<EditGasItem |
||||
priorityLevel={PRIORITY_LEVELS.DAPP_SUGGESTED} |
||||
onClose={onClose} |
||||
/> |
||||
<EditGasItem |
||||
priorityLevel={PRIORITY_LEVELS.CUSTOM} |
||||
onClose={onClose} |
||||
/> |
||||
</div> |
||||
</div> |
||||
</> |
||||
</Popover> |
||||
); |
||||
}; |
||||
|
||||
EditGasFeePopover.propTypes = { |
||||
onClose: PropTypes.func, |
||||
}; |
||||
|
||||
export default EditGasFeePopover; |
@ -0,0 +1,95 @@ |
||||
import React from 'react'; |
||||
import { screen } from '@testing-library/react'; |
||||
|
||||
import { renderWithProvider } from '../../../../test/lib/render-helpers'; |
||||
import { ETH } from '../../../helpers/constants/common'; |
||||
import configureStore from '../../../store/store'; |
||||
import { GasFeeContextProvider } from '../../../contexts/gasFee'; |
||||
|
||||
import EditGasFeePopover from './edit-gas-fee-popover'; |
||||
|
||||
jest.mock('../../../store/actions', () => ({ |
||||
disconnectGasFeeEstimatePoller: jest.fn(), |
||||
getGasFeeEstimatesAndStartPolling: jest |
||||
.fn() |
||||
.mockImplementation(() => Promise.resolve()), |
||||
addPollingTokenToAppState: jest.fn(), |
||||
})); |
||||
|
||||
const MOCK_FEE_ESTIMATE = { |
||||
low: { |
||||
minWaitTimeEstimate: 360000, |
||||
maxWaitTimeEstimate: 300000, |
||||
suggestedMaxPriorityFeePerGas: '3', |
||||
suggestedMaxFeePerGas: '53', |
||||
}, |
||||
medium: { |
||||
minWaitTimeEstimate: 30000, |
||||
maxWaitTimeEstimate: 60000, |
||||
suggestedMaxPriorityFeePerGas: '7', |
||||
suggestedMaxFeePerGas: '70', |
||||
}, |
||||
high: { |
||||
minWaitTimeEstimate: 15000, |
||||
maxWaitTimeEstimate: 15000, |
||||
suggestedMaxPriorityFeePerGas: '10', |
||||
suggestedMaxFeePerGas: '100', |
||||
}, |
||||
estimatedBaseFee: '50', |
||||
}; |
||||
|
||||
const renderComponent = () => { |
||||
const store = configureStore({ |
||||
metamask: { |
||||
nativeCurrency: ETH, |
||||
provider: {}, |
||||
cachedBalances: {}, |
||||
accounts: { |
||||
'0xAddress': { |
||||
address: '0xAddress', |
||||
balance: '0x176e5b6f173ebe66', |
||||
}, |
||||
}, |
||||
selectedAddress: '0xAddress', |
||||
featureFlags: { advancedInlineGas: true }, |
||||
gasFeeEstimates: MOCK_FEE_ESTIMATE, |
||||
}, |
||||
}); |
||||
|
||||
return renderWithProvider( |
||||
<GasFeeContextProvider transaction={{ txParams: { gas: '0x5208' } }}> |
||||
<EditGasFeePopover /> |
||||
</GasFeeContextProvider>, |
||||
store, |
||||
); |
||||
}; |
||||
|
||||
describe('EditGasFeePopover', () => { |
||||
it('should renders low / medium / high options', () => { |
||||
renderComponent(); |
||||
|
||||
expect(screen.queryByText('🐢')).toBeInTheDocument(); |
||||
expect(screen.queryByText('🦊')).toBeInTheDocument(); |
||||
expect(screen.queryByText('🦍')).toBeInTheDocument(); |
||||
expect(screen.queryByText('🌐')).toBeInTheDocument(); |
||||
expect(screen.queryByText('⚙')).toBeInTheDocument(); |
||||
expect(screen.queryByText('Low')).toBeInTheDocument(); |
||||
expect(screen.queryByText('Market')).toBeInTheDocument(); |
||||
expect(screen.queryByText('Aggressive')).toBeInTheDocument(); |
||||
expect(screen.queryByText('Site')).toBeInTheDocument(); |
||||
expect(screen.queryByText('Advanced')).toBeInTheDocument(); |
||||
}); |
||||
|
||||
it('should show time estimates', () => { |
||||
renderComponent(); |
||||
expect(screen.queryAllByText('5 min')).toHaveLength(2); |
||||
expect(screen.queryByText('15 sec')).toBeInTheDocument(); |
||||
}); |
||||
|
||||
it('should show gas fee estimates', () => { |
||||
renderComponent(); |
||||
expect(screen.queryByTitle('0.001113 ETH')).toBeInTheDocument(); |
||||
expect(screen.queryByTitle('0.00147 ETH')).toBeInTheDocument(); |
||||
expect(screen.queryByTitle('0.0021 ETH')).toBeInTheDocument(); |
||||
}); |
||||
}); |
@ -0,0 +1,150 @@ |
||||
import React from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
import classNames from 'classnames'; |
||||
import { useSelector } from 'react-redux'; |
||||
|
||||
import { getMaximumGasTotalInHexWei } from '../../../../../shared/modules/gas.utils'; |
||||
import { PRIORITY_LEVELS } from '../../../../../shared/constants/gas'; |
||||
import { PRIORITY_LEVEL_ICON_MAP } from '../../../../helpers/constants/gas'; |
||||
import { PRIMARY } from '../../../../helpers/constants/common'; |
||||
import { |
||||
decGWEIToHexWEI, |
||||
decimalToHex, |
||||
hexWEIToDecGWEI, |
||||
} from '../../../../helpers/utils/conversions.util'; |
||||
import { getAdvancedGasFeeValues } from '../../../../selectors'; |
||||
import { toHumanReadableTime } from '../../../../helpers/utils/util'; |
||||
import { useGasFeeContext } from '../../../../contexts/gasFee'; |
||||
import { useI18nContext } from '../../../../hooks/useI18nContext'; |
||||
import I18nValue from '../../../ui/i18n-value'; |
||||
import InfoTooltip from '../../../ui/info-tooltip'; |
||||
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'; |
||||
|
||||
import { useCustomTimeEstimate } from './useCustomTimeEstimate'; |
||||
|
||||
const EditGasItem = ({ priorityLevel, onClose }) => { |
||||
const { |
||||
estimateUsed, |
||||
gasFeeEstimates, |
||||
gasLimit, |
||||
maxFeePerGas: maxFeePerGasValue, |
||||
maxPriorityFeePerGas: maxPriorityFeePerGasValue, |
||||
updateTransactionUsingGasFeeEstimates, |
||||
transaction: { dappSuggestedGasFees }, |
||||
} = useGasFeeContext(); |
||||
const t = useI18nContext(); |
||||
const advancedGasFeeValues = useSelector(getAdvancedGasFeeValues); |
||||
let maxFeePerGas; |
||||
let maxPriorityFeePerGas; |
||||
let minWaitTime; |
||||
|
||||
if (gasFeeEstimates[priorityLevel]) { |
||||
maxFeePerGas = gasFeeEstimates[priorityLevel].suggestedMaxFeePerGas; |
||||
} else if ( |
||||
priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED && |
||||
dappSuggestedGasFees |
||||
) { |
||||
maxFeePerGas = hexWEIToDecGWEI(dappSuggestedGasFees.maxFeePerGas); |
||||
maxPriorityFeePerGas = hexWEIToDecGWEI( |
||||
dappSuggestedGasFees.maxPriorityFeePerGas, |
||||
); |
||||
} else if (priorityLevel === PRIORITY_LEVELS.CUSTOM) { |
||||
if (estimateUsed === PRIORITY_LEVELS.CUSTOM) { |
||||
maxFeePerGas = maxFeePerGasValue; |
||||
maxPriorityFeePerGas = maxPriorityFeePerGasValue; |
||||
} else if (advancedGasFeeValues) { |
||||
maxFeePerGas = |
||||
gasFeeEstimates.estimatedBaseFee * |
||||
parseFloat(advancedGasFeeValues.maxBaseFee); |
||||
maxPriorityFeePerGas = advancedGasFeeValues.priorityFee; |
||||
} |
||||
} |
||||
|
||||
const { waitTimeEstimate } = useCustomTimeEstimate({ |
||||
gasFeeEstimates, |
||||
maxFeePerGas, |
||||
maxPriorityFeePerGas, |
||||
}); |
||||
|
||||
if (gasFeeEstimates[priorityLevel]) { |
||||
minWaitTime = |
||||
priorityLevel === PRIORITY_LEVELS.HIGH |
||||
? gasFeeEstimates?.high.minWaitTimeEstimate |
||||
: gasFeeEstimates?.low.maxWaitTimeEstimate; |
||||
} else { |
||||
minWaitTime = waitTimeEstimate; |
||||
} |
||||
|
||||
const hexMaximumTransactionFee = maxFeePerGas |
||||
? getMaximumGasTotalInHexWei({ |
||||
gasLimit: decimalToHex(gasLimit), |
||||
maxFeePerGas: decGWEIToHexWEI(maxFeePerGas), |
||||
}) |
||||
: null; |
||||
|
||||
const onOptionSelect = () => { |
||||
if (priorityLevel !== PRIORITY_LEVELS.CUSTOM) { |
||||
updateTransactionUsingGasFeeEstimates(priorityLevel); |
||||
} |
||||
// todo: open advance modal if priorityLevel is custom
|
||||
onClose(); |
||||
}; |
||||
|
||||
return ( |
||||
<div |
||||
className={classNames('edit-gas-item', { |
||||
'edit-gas-item-selected': priorityLevel === estimateUsed, |
||||
'edit-gas-item-disabled': |
||||
priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED && |
||||
!dappSuggestedGasFees, |
||||
})} |
||||
role="button" |
||||
onClick={onOptionSelect} |
||||
> |
||||
<span className="edit-gas-item__name"> |
||||
<span |
||||
className={`edit-gas-item__icon edit-gas-item__icon-${priorityLevel}`} |
||||
> |
||||
{PRIORITY_LEVEL_ICON_MAP[priorityLevel]} |
||||
</span> |
||||
<I18nValue |
||||
messageKey={ |
||||
priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED |
||||
? 'dappSuggestedShortLabel' |
||||
: priorityLevel |
||||
} |
||||
/> |
||||
</span> |
||||
<span |
||||
className={`edit-gas-item__time-estimate edit-gas-item__time-estimate-${priorityLevel}`} |
||||
> |
||||
{minWaitTime |
||||
? minWaitTime && toHumanReadableTime(t, minWaitTime) |
||||
: '--'} |
||||
</span> |
||||
<span |
||||
className={`edit-gas-item__fee-estimate edit-gas-item__fee-estimate-${priorityLevel}`} |
||||
> |
||||
{hexMaximumTransactionFee ? ( |
||||
<UserPreferencedCurrencyDisplay |
||||
key="editGasSubTextFeeAmount" |
||||
type={PRIMARY} |
||||
value={hexMaximumTransactionFee} |
||||
/> |
||||
) : ( |
||||
'--' |
||||
)} |
||||
</span> |
||||
<span className="edit-gas-item__tooltip"> |
||||
<InfoTooltip position="top" /> |
||||
</span> |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
EditGasItem.propTypes = { |
||||
priorityLevel: PropTypes.string, |
||||
onClose: PropTypes.func, |
||||
}; |
||||
|
||||
export default EditGasItem; |
@ -0,0 +1,138 @@ |
||||
import React from 'react'; |
||||
import { screen } from '@testing-library/react'; |
||||
|
||||
import { renderWithProvider } from '../../../../../test/lib/render-helpers'; |
||||
import { ETH } from '../../../../helpers/constants/common'; |
||||
import configureStore from '../../../../store/store'; |
||||
import { GasFeeContextProvider } from '../../../../contexts/gasFee'; |
||||
|
||||
import EditGasItem from './edit-gas-item'; |
||||
|
||||
jest.mock('../../../../store/actions', () => ({ |
||||
disconnectGasFeeEstimatePoller: jest.fn(), |
||||
getGasFeeEstimatesAndStartPolling: jest |
||||
.fn() |
||||
.mockImplementation(() => Promise.resolve()), |
||||
addPollingTokenToAppState: jest.fn(), |
||||
getGasFeeTimeEstimate: jest |
||||
.fn() |
||||
.mockImplementation(() => Promise.resolve('unknown')), |
||||
})); |
||||
|
||||
const MOCK_FEE_ESTIMATE = { |
||||
low: { |
||||
minWaitTimeEstimate: 360000, |
||||
maxWaitTimeEstimate: 300000, |
||||
suggestedMaxPriorityFeePerGas: '3', |
||||
suggestedMaxFeePerGas: '53', |
||||
}, |
||||
medium: { |
||||
minWaitTimeEstimate: 30000, |
||||
maxWaitTimeEstimate: 60000, |
||||
suggestedMaxPriorityFeePerGas: '7', |
||||
suggestedMaxFeePerGas: '70', |
||||
}, |
||||
high: { |
||||
minWaitTimeEstimate: 15000, |
||||
maxWaitTimeEstimate: 15000, |
||||
suggestedMaxPriorityFeePerGas: '10', |
||||
suggestedMaxFeePerGas: '100', |
||||
}, |
||||
estimatedBaseFee: '50', |
||||
}; |
||||
|
||||
const DAPP_SUGGESTED_ESTIMATE = { |
||||
maxFeePerGas: '0x59682f10', |
||||
maxPriorityFeePerGas: '0x59682f00', |
||||
}; |
||||
|
||||
const renderComponent = (props, transactionProps, gasFeeContextProps) => { |
||||
const store = configureStore({ |
||||
metamask: { |
||||
nativeCurrency: ETH, |
||||
provider: {}, |
||||
cachedBalances: {}, |
||||
accounts: { |
||||
'0xAddress': { |
||||
address: '0xAddress', |
||||
balance: '0x176e5b6f173ebe66', |
||||
}, |
||||
}, |
||||
selectedAddress: '0xAddress', |
||||
featureFlags: { advancedInlineGas: true }, |
||||
gasFeeEstimates: MOCK_FEE_ESTIMATE, |
||||
advancedGasFee: { |
||||
maxBaseFee: '1.5', |
||||
priorityFee: '2', |
||||
}, |
||||
}, |
||||
}); |
||||
|
||||
return renderWithProvider( |
||||
<GasFeeContextProvider |
||||
transaction={{ txParams: { gas: '0x5208' }, ...transactionProps }} |
||||
{...gasFeeContextProps} |
||||
> |
||||
<EditGasItem priorityLevel="low" {...props} /> |
||||
</GasFeeContextProvider>, |
||||
store, |
||||
); |
||||
}; |
||||
|
||||
describe('EditGasItem', () => { |
||||
it('should renders low gas estimate option for priorityLevel low', () => { |
||||
renderComponent({ priorityLevel: 'low' }); |
||||
expect(screen.queryByText('🐢')).toBeInTheDocument(); |
||||
expect(screen.queryByText('Low')).toBeInTheDocument(); |
||||
expect(screen.queryByText('5 min')).toBeInTheDocument(); |
||||
expect(screen.queryByTitle('0.001113 ETH')).toBeInTheDocument(); |
||||
}); |
||||
|
||||
it('should renders market gas estimate option for priorityLevel medium', () => { |
||||
renderComponent({ priorityLevel: 'medium' }); |
||||
expect(screen.queryByText('🦊')).toBeInTheDocument(); |
||||
expect(screen.queryByText('Market')).toBeInTheDocument(); |
||||
expect(screen.queryByText('5 min')).toBeInTheDocument(); |
||||
expect(screen.queryByTitle('0.00147 ETH')).toBeInTheDocument(); |
||||
}); |
||||
|
||||
it('should renders aggressive gas estimate option for priorityLevel high', () => { |
||||
renderComponent({ priorityLevel: 'high' }); |
||||
expect(screen.queryByText('🦍')).toBeInTheDocument(); |
||||
expect(screen.queryByText('Aggressive')).toBeInTheDocument(); |
||||
expect(screen.queryByText('15 sec')).toBeInTheDocument(); |
||||
expect(screen.queryByTitle('0.0021 ETH')).toBeInTheDocument(); |
||||
}); |
||||
|
||||
it('should highlight option is priorityLevel is currently selected', () => { |
||||
renderComponent({ priorityLevel: 'high' }, { userFeeLevel: 'high' }); |
||||
expect( |
||||
document.getElementsByClassName('edit-gas-item-selected'), |
||||
).toHaveLength(1); |
||||
}); |
||||
|
||||
it('should renders site gas estimate option for priorityLevel dappSuggested', () => { |
||||
renderComponent( |
||||
{ priorityLevel: 'dappSuggested' }, |
||||
{ dappSuggestedGasFees: DAPP_SUGGESTED_ESTIMATE }, |
||||
); |
||||
expect(screen.queryByText('🌐')).toBeInTheDocument(); |
||||
expect(screen.queryByText('Site')).toBeInTheDocument(); |
||||
expect(screen.queryByTitle('0.0000315 ETH')).toBeInTheDocument(); |
||||
}); |
||||
|
||||
it('should disable site gas estimate option for is transaction does not have dappSuggestedGasFees', async () => { |
||||
renderComponent({ priorityLevel: 'dappSuggested' }); |
||||
expect( |
||||
document.getElementsByClassName('edit-gas-item-disabled'), |
||||
).toHaveLength(1); |
||||
}); |
||||
|
||||
it('should renders advance gas estimate option for priorityLevel custom', () => { |
||||
renderComponent({ priorityLevel: 'custom' }); |
||||
expect(screen.queryByText('⚙')).toBeInTheDocument(); |
||||
expect(screen.queryByText('Advanced')).toBeInTheDocument(); |
||||
// below value of custom gas fee estimate is default obtained from state.metamask.advancedGasFee
|
||||
expect(screen.queryByTitle('0.001575 ETH')).toBeInTheDocument(); |
||||
}); |
||||
}); |
@ -0,0 +1 @@ |
||||
export { default } from './edit-gas-item'; |
@ -0,0 +1,68 @@ |
||||
.edit-gas-item { |
||||
border-radius: 24px; |
||||
color: $ui-4; |
||||
cursor: pointer; |
||||
font-size: 12px; |
||||
display: flex; |
||||
align-items: center; |
||||
margin: 12px 0; |
||||
padding: 4px 12px; |
||||
height: 32px; |
||||
|
||||
&--selected { |
||||
background-color: $ui-1; |
||||
} |
||||
|
||||
&-disabled { |
||||
cursor: default; |
||||
} |
||||
|
||||
&__name { |
||||
display: inline-flex; |
||||
align-items: center; |
||||
color: $ui-black; |
||||
font-size: 12px; |
||||
font-weight: bold; |
||||
width: 40%; |
||||
} |
||||
|
||||
&__icon { |
||||
margin-right: 4px; |
||||
|
||||
&-custom { |
||||
font-size: 20px; |
||||
line-height: 0; |
||||
} |
||||
} |
||||
|
||||
&__time-estimate { |
||||
display: inline-block; |
||||
width: 20%; |
||||
} |
||||
|
||||
&__fee-estimate { |
||||
display: inline-block; |
||||
width: 30%; |
||||
white-space: nowrap; |
||||
} |
||||
|
||||
&__tooltip { |
||||
display: inline-block; |
||||
text-align: right; |
||||
width: 10%; |
||||
|
||||
.info-tooltip { |
||||
display: inline-block; |
||||
} |
||||
} |
||||
|
||||
&__time-estimate-low, |
||||
&__fee-estimate-high { |
||||
color: $secondary-1; |
||||
} |
||||
|
||||
&__time-estimate-medium, |
||||
&__time-estimate-high { |
||||
color: $success-3; |
||||
} |
||||
} |
@ -0,0 +1,83 @@ |
||||
import { useEffect, useState } from 'react'; |
||||
import { useSelector } from 'react-redux'; |
||||
import BigNumber from 'bignumber.js'; |
||||
|
||||
import { GAS_ESTIMATE_TYPES } from '../../../../../shared/constants/gas'; |
||||
import { |
||||
getGasEstimateType, |
||||
getIsGasEstimatesLoading, |
||||
} from '../../../../ducks/metamask/metamask'; |
||||
import { getGasFeeTimeEstimate } from '../../../../store/actions'; |
||||
|
||||
export const useCustomTimeEstimate = ({ |
||||
gasFeeEstimates, |
||||
maxFeePerGas, |
||||
maxPriorityFeePerGas, |
||||
}) => { |
||||
const gasEstimateType = useSelector(getGasEstimateType); |
||||
const isGasEstimatesLoading = useSelector(getIsGasEstimatesLoading); |
||||
|
||||
const [customEstimatedTime, setCustomEstimatedTime] = useState(null); |
||||
|
||||
const returnNoEstimates = |
||||
isGasEstimatesLoading || |
||||
gasEstimateType !== GAS_ESTIMATE_TYPES.FEE_MARKET || |
||||
!maxPriorityFeePerGas; |
||||
|
||||
// If the user has chosen a value lower than the low gas fee estimate,
|
||||
// We'll need to use the useEffect hook below to make a call to calculate
|
||||
// the time to show
|
||||
const isUnknownLow = |
||||
gasFeeEstimates?.low && |
||||
Number(maxPriorityFeePerGas) < |
||||
Number(gasFeeEstimates.low.suggestedMaxPriorityFeePerGas); |
||||
|
||||
useEffect(() => { |
||||
if ( |
||||
isGasEstimatesLoading || |
||||
gasEstimateType !== GAS_ESTIMATE_TYPES.FEE_MARKET || |
||||
!maxPriorityFeePerGas |
||||
) |
||||
return; |
||||
if (isUnknownLow) { |
||||
// getGasFeeTimeEstimate requires parameters in string format
|
||||
getGasFeeTimeEstimate( |
||||
new BigNumber(maxPriorityFeePerGas, 10).toString(10), |
||||
new BigNumber(maxFeePerGas, 10).toString(10), |
||||
).then((result) => { |
||||
setCustomEstimatedTime(result); |
||||
}); |
||||
} |
||||
}, [ |
||||
gasEstimateType, |
||||
isUnknownLow, |
||||
isGasEstimatesLoading, |
||||
maxFeePerGas, |
||||
maxPriorityFeePerGas, |
||||
returnNoEstimates, |
||||
]); |
||||
|
||||
if (returnNoEstimates) { |
||||
return {}; |
||||
} |
||||
|
||||
const { low = {}, medium = {}, high = {} } = gasFeeEstimates; |
||||
let waitTimeEstimate = ''; |
||||
|
||||
if ( |
||||
isUnknownLow && |
||||
customEstimatedTime && |
||||
customEstimatedTime !== 'unknown' && |
||||
customEstimatedTime?.upperTimeBound !== 'unknown' |
||||
) { |
||||
waitTimeEstimate = Number(customEstimatedTime?.upperTimeBound); |
||||
} else if ( |
||||
Number(maxPriorityFeePerGas) >= Number(medium.suggestedMaxPriorityFeePerGas) |
||||
) { |
||||
waitTimeEstimate = high.minWaitTimeEstimate; |
||||
} else { |
||||
waitTimeEstimate = low.maxWaitTimeEstimate; |
||||
} |
||||
|
||||
return { waitTimeEstimate }; |
||||
}; |
@ -0,0 +1 @@ |
||||
export { default } from './edit-gas-fee-popover'; |
@ -0,0 +1,40 @@ |
||||
.edit-gas-fee-popover { |
||||
@media screen and (min-width: $break-large) { |
||||
max-height: 84vh; |
||||
} |
||||
|
||||
&__wrapper { |
||||
border-top: 1px solid $ui-grey; |
||||
} |
||||
|
||||
&__content { |
||||
padding: 16px 12px; |
||||
|
||||
&__header { |
||||
color: $ui-4; |
||||
font-size: 10px; |
||||
font-weight: 700; |
||||
margin: 0 12px; |
||||
|
||||
&-option { |
||||
display: inline-block; |
||||
width: 40%; |
||||
} |
||||
|
||||
&-time { |
||||
display: inline-block; |
||||
width: 20%; |
||||
} |
||||
|
||||
&-max-fee { |
||||
display: inline-block; |
||||
width: 30%; |
||||
} |
||||
} |
||||
|
||||
&__separator { |
||||
border-top: 1px solid $ui-grey; |
||||
margin: 8px 12px; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1 @@ |
||||
export { default } from './new-collectibles-notice.component'; |
@ -0,0 +1,56 @@ |
||||
import React from 'react'; |
||||
import Box from '../../ui/box'; |
||||
import Dialog from '../../ui/dialog'; |
||||
import Typography from '../../ui/typography/typography'; |
||||
import { |
||||
COLORS, |
||||
TYPOGRAPHY, |
||||
TEXT_ALIGN, |
||||
FONT_WEIGHT, |
||||
DISPLAY, |
||||
} from '../../../helpers/constants/design-system'; |
||||
import { useI18nContext } from '../../../hooks/useI18nContext'; |
||||
|
||||
export default function NewCollectiblesNotice() { |
||||
const t = useI18nContext(); |
||||
|
||||
return ( |
||||
<Box marginBottom={8}> |
||||
<Dialog type="message"> |
||||
<Box display={DISPLAY.FLEX}> |
||||
<Box paddingTop={2}> |
||||
<i style={{ fontSize: '1rem' }} className="fa fa-info-circle" /> |
||||
</Box> |
||||
<Box paddingLeft={4}> |
||||
<Typography |
||||
color={COLORS.BLACK} |
||||
align={TEXT_ALIGN.LEFT} |
||||
variant={TYPOGRAPHY.Paragraph} |
||||
fontWeight={FONT_WEIGHT.BOLD} |
||||
> |
||||
{t('newNFTsDetected')} |
||||
</Typography> |
||||
<Typography |
||||
color={COLORS.BLACK} |
||||
align={TEXT_ALIGN.LEFT} |
||||
variant={TYPOGRAPHY.Paragraph} |
||||
boxProps={{ marginBottom: 4 }} |
||||
> |
||||
{t('newNFTsDetectedInfo')} |
||||
</Typography> |
||||
<a |
||||
href="#" |
||||
onClick={(e) => { |
||||
e.preventDefault(); |
||||
console.log('show preference popover'); |
||||
}} |
||||
style={{ fontSize: '.9rem' }} |
||||
> |
||||
{t('selectNFTPrivacyPreference')} |
||||
</a> |
||||
</Box> |
||||
</Box> |
||||
</Dialog> |
||||
</Box> |
||||
); |
||||
} |
@ -0,0 +1,94 @@ |
||||
import React from 'react'; |
||||
import { screen } from '@testing-library/react'; |
||||
|
||||
import { ETH } from '../../../helpers/constants/common'; |
||||
import { GasFeeContextProvider } from '../../../contexts/gasFee'; |
||||
import { renderWithProvider } from '../../../../test/jest'; |
||||
import configureStore from '../../../store/store'; |
||||
|
||||
import TransactionDetail from './transaction-detail.component'; |
||||
|
||||
jest.mock('../../../store/actions', () => ({ |
||||
disconnectGasFeeEstimatePoller: jest.fn(), |
||||
getGasFeeEstimatesAndStartPolling: jest |
||||
.fn() |
||||
.mockImplementation(() => Promise.resolve()), |
||||
addPollingTokenToAppState: jest.fn(), |
||||
})); |
||||
|
||||
const render = (props) => { |
||||
const store = configureStore({ |
||||
metamask: { |
||||
nativeCurrency: ETH, |
||||
preferences: { |
||||
useNativeCurrencyAsPrimaryCurrency: true, |
||||
}, |
||||
provider: {}, |
||||
cachedBalances: {}, |
||||
accounts: { |
||||
'0xAddress': { |
||||
address: '0xAddress', |
||||
balance: '0x176e5b6f173ebe66', |
||||
}, |
||||
}, |
||||
selectedAddress: '0xAddress', |
||||
featureFlags: { advancedInlineGas: true }, |
||||
}, |
||||
}); |
||||
|
||||
return renderWithProvider( |
||||
<GasFeeContextProvider {...props}> |
||||
<TransactionDetail |
||||
onEdit={() => { |
||||
console.log('on edit'); |
||||
}} |
||||
rows={[]} |
||||
{...props} |
||||
/> |
||||
</GasFeeContextProvider>, |
||||
store, |
||||
); |
||||
}; |
||||
|
||||
describe('TransactionDetail', () => { |
||||
beforeEach(() => { |
||||
process.env.EIP_1559_V2 = true; |
||||
}); |
||||
afterEach(() => { |
||||
process.env.EIP_1559_V2 = false; |
||||
}); |
||||
it('should render edit link with text low if low gas estimates are selected', () => { |
||||
render({ transaction: { userFeeLevel: 'low' } }); |
||||
expect(screen.queryByText('🐢')).toBeInTheDocument(); |
||||
expect(screen.queryByText('Low')).toBeInTheDocument(); |
||||
}); |
||||
it('should render edit link with text markey if medium gas estimates are selected', () => { |
||||
render({ transaction: { userFeeLevel: 'medium' } }); |
||||
expect(screen.queryByText('🦊')).toBeInTheDocument(); |
||||
expect(screen.queryByText('Market')).toBeInTheDocument(); |
||||
}); |
||||
it('should render edit link with text agressive if high gas estimates are selected', () => { |
||||
render({ transaction: { userFeeLevel: 'high' } }); |
||||
expect(screen.queryByText('🦍')).toBeInTheDocument(); |
||||
expect(screen.queryByText('Aggressive')).toBeInTheDocument(); |
||||
}); |
||||
it('should render edit link with text Site suggested if site suggested estimated are used', () => { |
||||
render({ |
||||
transaction: { |
||||
dappSuggestedGasFees: { maxFeePerGas: 1, maxPriorityFeePerGas: 1 }, |
||||
txParams: { maxFeePerGas: 1, maxPriorityFeePerGas: 1 }, |
||||
}, |
||||
}); |
||||
expect(screen.queryByText('🌐')).toBeInTheDocument(); |
||||
expect(screen.queryByText('Site suggested')).toBeInTheDocument(); |
||||
expect(document.getElementsByClassName('info-tooltip')).toHaveLength(1); |
||||
}); |
||||
it('should render edit link with text advance if custom gas estimates are used', () => { |
||||
render({ |
||||
defaultEstimateToUse: 'custom', |
||||
}); |
||||
expect(screen.queryByText('⚙')).toBeInTheDocument(); |
||||
expect(screen.queryByText('Advanced')).toBeInTheDocument(); |
||||
expect(screen.queryByText('Edit')).toBeInTheDocument(); |
||||
}); |
||||
}); |
@ -0,0 +1,42 @@ |
||||
import { Story, Canvas, ArgsTable } from '@storybook/addon-docs'; |
||||
|
||||
import Card from '.'; |
||||
|
||||
# Card |
||||
|
||||
Cards are used to group related content or actions together. |
||||
|
||||
<Canvas> |
||||
<Story id="ui-components-ui-card-card-stories-js--default-story" /> |
||||
</Canvas> |
||||
|
||||
## Component API |
||||
|
||||
The `Card` component extends the `Box` component. See the `Box` component for an extended list of props. |
||||
|
||||
<ArgsTable of={Card} /> |
||||
|
||||
## Usage |
||||
|
||||
The following describes the props and example usage for this component. |
||||
|
||||
### Padding, Border and Background Color |
||||
|
||||
The Card component has a set of default props that should meet most card use cases. There is a strong recommendation to not overwrite these to ensure our cards stay consistent across the app. |
||||
|
||||
That being said all props can be overwritten if necessary. |
||||
|
||||
```jsx |
||||
import { COLORS } from '../../../helpers/constants/design-system'; |
||||
|
||||
// To remove the border |
||||
<Card border={false} /> |
||||
// All border related props of the Box component will work |
||||
|
||||
// To remove or change padding |
||||
<Card padding={0} /> |
||||
// All padding related props of the Box component will work |
||||
|
||||
// To change the background color |
||||
<Card backgroundColor={COLORS.UI4} /> |
||||
``` |
@ -1,23 +0,0 @@ |
||||
import React, { PureComponent } from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
import classnames from 'classnames'; |
||||
|
||||
export default class Card extends PureComponent { |
||||
static propTypes = { |
||||
className: PropTypes.string, |
||||
overrideClassName: PropTypes.bool, |
||||
title: PropTypes.string, |
||||
children: PropTypes.node, |
||||
}; |
||||
|
||||
render() { |
||||
const { className, overrideClassName, title } = this.props; |
||||
|
||||
return ( |
||||
<div className={classnames({ card: !overrideClassName }, className)}> |
||||
<div className="card__title">{title}</div> |
||||
{this.props.children} |
||||
</div> |
||||
); |
||||
} |
||||
} |
@ -1,21 +0,0 @@ |
||||
import React from 'react'; |
||||
import { shallow } from 'enzyme'; |
||||
import Card from './card.component'; |
||||
|
||||
describe('Card Component', () => { |
||||
it('should render a card with a title and child element', () => { |
||||
const wrapper = shallow( |
||||
<Card title="Test" className="card-test-class"> |
||||
<div className="child-test-class">Child</div> |
||||
</Card>, |
||||
); |
||||
|
||||
expect(wrapper.hasClass('card-test-class')).toStrictEqual(true); |
||||
const title = wrapper.find('.card__title'); |
||||
expect(title).toHaveLength(1); |
||||
expect(title.text()).toStrictEqual('Test'); |
||||
const child = wrapper.find('.child-test-class'); |
||||
expect(child).toHaveLength(1); |
||||
expect(child.text()).toStrictEqual('Child'); |
||||
}); |
||||
}); |
@ -0,0 +1,60 @@ |
||||
import React from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
|
||||
import Box from '../box'; |
||||
import { |
||||
BORDER_STYLE, |
||||
COLORS, |
||||
SIZES, |
||||
} from '../../../helpers/constants/design-system'; |
||||
|
||||
const Card = ({ |
||||
border = true, |
||||
padding = 4, |
||||
backgroundColor = COLORS.WHITE, |
||||
children, |
||||
...props |
||||
}) => { |
||||
const defaultBorderProps = { |
||||
borderColor: border && COLORS.UI2, |
||||
borderRadius: border && SIZES.MD, |
||||
borderStyle: border && BORDER_STYLE.SOLID, |
||||
}; |
||||
|
||||
return ( |
||||
<Box |
||||
{...{ |
||||
padding, |
||||
backgroundColor, |
||||
...defaultBorderProps, |
||||
...props, |
||||
}} |
||||
> |
||||
{children} |
||||
</Box> |
||||
); |
||||
}; |
||||
|
||||
Card.propTypes = { |
||||
/** |
||||
* Whether the Card has a border or not. |
||||
* Defaults to true |
||||
*/ |
||||
border: PropTypes.bool, |
||||
/** |
||||
* Padding of the Card component accepts number or an array of 2 numbers. |
||||
* Defaults to 4 (16px) |
||||
*/ |
||||
padding: Box.propTypes.padding, |
||||
/** |
||||
* The background color of the card |
||||
* Defaults to COLORS.WHITE |
||||
*/ |
||||
backgroundColor: Box.propTypes.backgroundColor, |
||||
/** |
||||
* The Card component accepts all Box component props |
||||
*/ |
||||
...Box.propTypes, |
||||
}; |
||||
|
||||
export default Card; |
@ -0,0 +1,169 @@ |
||||
import React from 'react'; |
||||
import { |
||||
ALIGN_ITEMS, |
||||
BLOCK_SIZES, |
||||
BORDER_STYLE, |
||||
COLORS, |
||||
DISPLAY, |
||||
JUSTIFY_CONTENT, |
||||
TEXT_ALIGN, |
||||
} from '../../../helpers/constants/design-system'; |
||||
|
||||
import README from './README.mdx'; |
||||
import Card from '.'; |
||||
|
||||
const sizeOptions = [undefined, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; |
||||
|
||||
export default { |
||||
title: 'UI/Card', |
||||
id: __filename, |
||||
component: Card, |
||||
parameters: { |
||||
docs: { |
||||
page: README, |
||||
}, |
||||
}, |
||||
argTypes: { |
||||
children: { control: 'text' }, |
||||
border: { |
||||
control: 'boolean', |
||||
}, |
||||
borderStyle: { |
||||
control: { |
||||
type: 'select', |
||||
}, |
||||
options: Object.values(BORDER_STYLE), |
||||
}, |
||||
borderWidth: { |
||||
control: { |
||||
type: 'select', |
||||
}, |
||||
options: [...sizeOptions], |
||||
}, |
||||
borderColor: { |
||||
control: { |
||||
type: 'select', |
||||
}, |
||||
options: Object.values(COLORS), |
||||
}, |
||||
backgroundColor: { |
||||
control: { |
||||
type: 'select', |
||||
}, |
||||
options: Object.values(COLORS), |
||||
}, |
||||
width: { |
||||
control: { |
||||
type: 'select', |
||||
}, |
||||
options: Object.values(BLOCK_SIZES), |
||||
}, |
||||
height: { |
||||
control: { |
||||
type: 'select', |
||||
}, |
||||
options: Object.values(BLOCK_SIZES), |
||||
}, |
||||
textAlign: { |
||||
control: { |
||||
type: 'select', |
||||
}, |
||||
options: Object.values(TEXT_ALIGN), |
||||
}, |
||||
margin: { |
||||
control: { |
||||
type: 'select', |
||||
}, |
||||
options: [...sizeOptions], |
||||
}, |
||||
marginTop: { |
||||
control: { |
||||
type: 'select', |
||||
}, |
||||
options: [...sizeOptions], |
||||
}, |
||||
marginRight: { |
||||
control: { |
||||
type: 'select', |
||||
}, |
||||
options: [...sizeOptions], |
||||
}, |
||||
marginBottom: { |
||||
control: { |
||||
type: 'select', |
||||
}, |
||||
options: [...sizeOptions], |
||||
}, |
||||
marginLeft: { |
||||
control: { |
||||
type: 'select', |
||||
}, |
||||
options: [...sizeOptions], |
||||
}, |
||||
padding: { |
||||
control: { |
||||
type: 'select', |
||||
}, |
||||
options: [...sizeOptions], |
||||
}, |
||||
paddingTop: { |
||||
control: { |
||||
type: 'select', |
||||
}, |
||||
options: [...sizeOptions], |
||||
}, |
||||
paddingRight: { |
||||
control: { |
||||
type: 'select', |
||||
}, |
||||
options: [...sizeOptions], |
||||
}, |
||||
paddingBottom: { |
||||
control: { |
||||
type: 'select', |
||||
}, |
||||
options: [...sizeOptions], |
||||
}, |
||||
paddingLeft: { |
||||
control: { |
||||
type: 'select', |
||||
}, |
||||
options: [...sizeOptions], |
||||
}, |
||||
display: { |
||||
control: { |
||||
type: 'select', |
||||
}, |
||||
options: Object.values(DISPLAY), |
||||
}, |
||||
justifyContent: { |
||||
control: { |
||||
type: 'select', |
||||
}, |
||||
options: Object.values(JUSTIFY_CONTENT), |
||||
}, |
||||
alignItems: { |
||||
control: { |
||||
type: 'select', |
||||
}, |
||||
options: Object.values(ALIGN_ITEMS), |
||||
}, |
||||
}, |
||||
args: { |
||||
children: 'Card children', |
||||
}, |
||||
}; |
||||
|
||||
export const DefaultStory = (args) => <Card {...args}>{args.children}</Card>; |
||||
|
||||
DefaultStory.storyName = 'Default'; |
||||
|
||||
DefaultStory.args = { |
||||
padding: 4, |
||||
border: true, |
||||
borderWidth: 1, |
||||
borderColor: COLORS.UI2, |
||||
borderStyle: BORDER_STYLE.SOLID, |
||||
backgroundColor: COLORS.WHITE, |
||||
display: DISPLAY.BLOCK, |
||||
}; |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue