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:
parent
6a89261f28
commit
96933b3faf
@ -0,0 +1 @@
|
|||||||
|
export { default } from './metamask-template-renderer'
|
@ -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)
|
||||||
|
})
|
@ -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',
|
||||||
|
},
|
||||||
|
])}
|
||||||
|
/>
|
||||||
|
)
|
@ -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,
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user