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

add MetaMask Template Renderer (#10307)

* add MetaMask Template Renderer

* add areEqual fn and change acc var name

* use key
This commit is contained in:
Brad Decker 2021-02-02 12:14:04 -06:00 committed by GitHub
parent 6a89261f28
commit 96933b3faf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 230 additions and 0 deletions

View File

@ -0,0 +1 @@
export { default } from './metamask-template-renderer'

View File

@ -0,0 +1,102 @@
import React, { memo } from 'react'
import PropTypes from 'prop-types'
import { isEqual } from 'lodash'
import { safeComponentList } from './safe-component-list'
function getElement(section) {
const { element } = section
const Element = safeComponentList[element]
if (!Element) {
throw new Error(
`${element} is not in the safe component list for MetaMask template renderer`,
)
}
return Element
}
const MetaMaskTemplateRenderer = ({ sections }) => {
if (!sections) {
// If sections is null eject early by returning null
return null
} else if (typeof sections === 'string') {
// React can render strings directly, so return the string
return sections
} else if (
sections &&
typeof sections === 'object' &&
!Array.isArray(sections)
) {
// If dealing with a single entry, then render a single object without key
const Element = getElement(sections)
return (
<Element {...sections.props}>
{typeof sections.children === 'object' ? (
<MetaMaskTemplateRenderer sections={sections.children} />
) : (
sections?.children
)}
</Element>
)
}
// The last case is dealing with an array of objects
return (
<>
{sections.reduce((allChildren, child) => {
if (typeof child === 'string') {
// React can render strings directly, so push them into the accumulator
allChildren.push(child)
} else {
// If the entry in array is not a string, then it must be a Section.
// Sections are handled by the main function, but must
// be provided a key when a part of an array.
if (!child.key) {
throw new Error(
'When using array syntax in MetaMask Template Language, you must specify a key for each child of the array',
)
}
if (typeof child?.children === 'object') {
// If this child has its own children, check if children is an
// object, and in that case use recursion to render.
allChildren.push(
<MetaMaskTemplateRenderer sections={child} key={child.key} />,
)
} else {
// Otherwise render the element.
const Element = getElement(child)
allChildren.push(
<Element key={child.key} {...child.props}>
{child?.children}
</Element>,
)
}
}
return allChildren
}, [])}
</>
)
}
const SectionShape = {
props: PropTypes.object,
element: PropTypes.oneOf(Object.keys(safeComponentList)).isRequired,
key: PropTypes.string,
}
const ValidChildren = PropTypes.oneOfType([
PropTypes.string,
PropTypes.shape(SectionShape),
PropTypes.arrayOf(
PropTypes.oneOfType([PropTypes.shape(SectionShape), PropTypes.string]),
),
])
SectionShape.children = ValidChildren
MetaMaskTemplateRenderer.propTypes = {
sections: ValidChildren,
}
export default memo(MetaMaskTemplateRenderer, (prevProps, nextProps) => {
return isEqual(prevProps.sections, nextProps.sections)
})

View File

@ -0,0 +1,106 @@
import React from 'react'
import { object } from '@storybook/addon-knobs'
import { COLORS, TYPOGRAPHY } from '../../../helpers/constants/design-system'
import MetaMaskTemplateRenderer from '.'
export default {
title: 'MetaMask Template Renderer',
}
const SECTIONS = {
element: 'Box',
props: {
margin: 4,
padding: 8,
borderColor: COLORS.PRIMARY1,
borderWidth: 2,
},
children: [
{
element: 'Typography',
key: 'A Test String',
children: 'A Test String',
props: {
color: COLORS.UI3,
variant: TYPOGRAPHY.H2,
},
},
{
element: 'Typography',
key: 'Some more text',
children: 'Some more text as a paragraph',
props: {
color: COLORS.UI4,
variant: TYPOGRAPHY.Paragraph,
},
},
{
element: 'TruncatedDefinitionList',
key: 'TDL',
props: {
dictionary: {
term:
'a word or phrase used to describe a thing or to express a concept, especially in a particular kind of language or branch of study.',
definition:
'a statement of the exact meaning of a word, especially in a dictionary.',
dl: 'HTML tag denoting a definition list',
dt: 'HTML tag denoting a definition list term',
dd: 'HTML tag denoting a definition list definition',
},
title: 'Full list',
prefaceKeys: ['term', 'definition'],
},
},
{
element: 'Box',
key: 'ActionsBox',
children: [
{
element: 'Button',
children: 'Cancel',
key: 'cancel-button',
props: {
rounded: true,
type: 'outlined',
style: {
width: '45%',
},
},
},
{
element: 'Button',
children: 'OK',
key: 'ok-button',
props: {
rounded: true,
type: 'primary',
style: {
width: '45%',
},
},
},
],
props: { justifyContent: 'space-between', padding: [0, 4] },
},
],
}
export const metaMaskTemplateRenderer = () => (
<MetaMaskTemplateRenderer sections={object('sections', SECTIONS)} />
)
export const withInvalidElement = () => (
<MetaMaskTemplateRenderer
sections={object('sections', [
{
...SECTIONS,
key: 'safe-tree',
},
{
element: 'Unsafe',
key: 'unsafe-tree',
children:
'I should be displayed, but I wont be due to unsafe component',
},
])}
/>
)

View File

@ -0,0 +1,21 @@
import Button from '../../ui/button'
import Chip from '../../ui/chip'
import DefinitionList from '../../ui/definition-list'
import TruncatedDefinitionList from '../../ui/truncated-definition-list'
import Popover from '../../ui/popover'
import Typography from '../../ui/typography'
import Box from '../../ui/box'
export const safeComponentList = {
b: 'b',
p: 'p',
div: 'div',
span: 'span',
Typography,
Chip,
DefinitionList,
TruncatedDefinitionList,
Button,
Popover,
Box,
}