1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

add Callout component (#10309)

This commit is contained in:
Brad Decker 2021-02-02 09:21:56 -06:00 committed by GitHub
parent f9b5b7ee37
commit 12161bb0c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 235 additions and 0 deletions

View File

@ -0,0 +1,65 @@
import React, { useEffect, useState } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import InfoIconInverted from '../icon/info-icon-inverted.component'
import { SEVERITIES } from '../../../helpers/constants/design-system'
export default function Callout({
severity,
children,
dismiss,
isFirst,
isLast,
isMultiple,
}) {
const [removed, setRemoved] = useState(false)
const calloutClassName = classnames('callout', `callout--${severity}`, {
'callout--dismissed': removed === true,
'callout--multiple': isMultiple === true,
'callout--dismissible': Boolean(dismiss),
'callout--first': isFirst === true || isMultiple !== true,
'callout--last': isLast === true || isMultiple !== true,
})
// Clicking the close button will set removed state, which will trigger this
// effect to refire due to changing dependencies. When that happens, after a
// half of a second we fire the dismiss method from the parent. The
// consuming component is responsible for modifying state and then removing
// the element from the DOM.
useEffect(() => {
if (removed) {
setTimeout(() => {
dismiss()
}, 500)
}
}, [removed, dismiss])
return (
<div className={calloutClassName}>
<InfoIconInverted severity={severity} />
<div className="callout__content">{children}</div>
{dismiss && (
<i
onClick={() => {
setRemoved(true)
}}
onKeyUp={(event) => {
if (event.key === 'Enter') {
setRemoved(true)
}
}}
role="button"
tabIndex={0}
className="fas fa-times callout__close-button"
/>
)}
</div>
)
}
Callout.propTypes = {
severity: PropTypes.oneOf(Object.values(SEVERITIES)).isRequired,
children: PropTypes.node.isRequired,
dismiss: PropTypes.func,
isFirst: PropTypes.bool,
isLast: PropTypes.bool,
isMultiple: PropTypes.bool,
}

View File

@ -0,0 +1,61 @@
.callout {
$self: &;
@include H7;
padding: 16px;
display: grid;
grid-template-columns: minmax(0, auto) 1fr minmax(0, auto);
grid-template-rows: 1fr;
transition: opacity 0.75s 0s;
&--dismissible {
&#{$self}--first {
box-shadow: 0 -5px 5px -5px rgba(0, 0, 0, 0.18);
}
}
&--multiple {
padding-top: 8px;
padding-bottom: 8px;
&#{$self}--first {
padding-top: 16px;
}
&#{$self}--last {
padding-bottom: 16px;
}
}
&--dismissed {
opacity: 0;
}
&--warning {
border-left: 2px solid $alert-1;
}
&--danger {
border-left: 2px solid $error-1;
}
&--info {
border-left: 2px solid $primary-1;
}
&--success {
border-left: 2px solid $success-1;
}
& .info-icon {
margin: unset;
margin-right: 10px;
}
&__close-button {
margin-left: 8px;
background: unset;
cursor: pointer;
}
}

View File

@ -0,0 +1,107 @@
import { select } from '@storybook/addon-knobs'
import React, { useState } from 'react'
import {
COLORS,
SEVERITIES,
TYPOGRAPHY,
} from '../../../helpers/constants/design-system'
import Box from '../box'
import Typography from '../typography'
import Callout from './callout'
export default {
title: 'Callout',
}
export const persistentCallout = () => (
<Box borderColor={COLORS.UI2} padding={[8, 0, 0, 0]}>
<Box margin={2}>
<Typography variant={TYPOGRAPHY.H4}>This is your private key:</Typography>
<Typography variant={TYPOGRAPHY.H6}>
some seed words that are super important and probably deserve a callout
</Typography>
</Box>
<Callout severity={select('severity', SEVERITIES, SEVERITIES.WARNING)}>
Always back up your private key!
</Callout>
</Box>
)
export const DismissibleCallout = () => {
const [dismissed, setDismissed] = useState(false)
return (
<Box borderColor={COLORS.UI2} padding={[8, 0, 0, 0]}>
<Box margin={2}>
<Typography variant={TYPOGRAPHY.H4}>
This is your private key:
</Typography>
<Typography variant={TYPOGRAPHY.H6}>
some seed words that are super important and probably deserve a
callout
</Typography>
</Box>
{!dismissed && (
<Callout
severity={select('severity', SEVERITIES, SEVERITIES.WARNING)}
dismiss={() => setDismissed(true)}
>
Always back up your private key!
</Callout>
)}
</Box>
)
}
const MULTIPLE_CALLOUTS = {
WARN: {
severity: SEVERITIES.WARNING,
content: 'Always back up your private key!',
dismissed: false,
},
DANGER: {
severity: SEVERITIES.DANGER,
content: 'Never give your private key out, it will lead to loss of funds!',
dismissed: false,
},
}
export const MultipleDismissibleCallouts = () => {
const [calloutState, setCalloutState] = useState(MULTIPLE_CALLOUTS)
const dismiss = (id) => {
setCalloutState((prevState) => ({
...prevState,
[id]: {
...prevState[id],
dismissed: true,
},
}))
}
return (
<Box borderColor={COLORS.UI2} padding={[8, 0, 0, 0]}>
<Box margin={2}>
<Typography variant={TYPOGRAPHY.H4}>
This is your private key:
</Typography>
<Typography variant={TYPOGRAPHY.H6}>
some seed words that are super important and probably deserve a
callout
</Typography>
</Box>
{Object.entries(calloutState)
.filter(([_, callout]) => callout.dismissed === false)
.map(([id, callout], idx, filtered) => (
<Callout
key={id}
severity={callout.severity}
dismiss={() => dismiss(id)}
isFirst={idx === 0}
isLast={idx + 1 === filtered.length}
isMultiple={filtered.length > 1}
>
{callout.content}
</Callout>
))}
</Box>
)
}

View File

@ -0,0 +1 @@
export { default } from './callout'

View File

@ -6,6 +6,7 @@
@import 'breadcrumbs/index';
@import 'button-group/index';
@import 'button/buttons';
@import 'callout/callout';
@import 'card/index';
@import 'check-box/index';
@import 'chip/chip';