diff --git a/src/rendering/Box/index.js b/src/rendering/Box/index.js
new file mode 100644
index 0000000..9fc4498
--- /dev/null
+++ b/src/rendering/Box/index.js
@@ -0,0 +1,85 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { getBBox } from 'layout';
+
+import * as style from './style';
+
+const Box = ({
+ theme,
+ radius,
+ label,
+ width,
+ height,
+ rectTransform,
+ labelTransform,
+ contentTransform,
+ children
+}) => {
+ const rectProps = {
+ style: style[theme],
+ width,
+ height,
+ rx: radius,
+ ry: radius,
+ transform: rectTransform
+ };
+ const textProps = {
+ transform: labelTransform,
+ style: style.infoText
+ };
+
+ return <>
+
+ { label && { label } }
+
+ { children }
+
+ >;
+};
+
+Box.defaultProps = {
+ padding: 5,
+ radius: 3
+};
+
+Box.propTypes = {
+ theme: PropTypes.string,
+ radius: PropTypes.number,
+ padding: PropTypes.number,
+ label: PropTypes.string,
+ width: PropTypes.number,
+ height: PropTypes.number,
+ rectTransform: PropTypes.string,
+ labelTransform: PropTypes.string,
+ contentTransform: PropTypes.string,
+ children: PropTypes.node
+};
+
+const layout = data => {
+ const { label, padding } = {
+ ...Box.defaultProps,
+ ...data.props
+ };
+ const childBox = data.children[0].box;
+ const labelBox = label ?
+ getBBox({ label }) :
+ { width: 0, height: 0 };
+
+ data.box = {
+ width: Math.max(childBox.width + 2 * padding, labelBox.width),
+ height: childBox.height + 2 * padding + labelBox.height
+ };
+ data.props = {
+ ...data.props,
+ width: data.box.width,
+ height: childBox.height + 2 * padding,
+ rectTransform: `translate(0 ${ labelBox.height })`,
+ labelTransform: `translate(0 ${ labelBox.height })`,
+ contentTransform: `translate(${ padding } ${ padding + labelBox.height })`
+ };
+ return data;
+};
+
+export default Box;
+export { layout };
diff --git a/src/rendering/Box/style.js b/src/rendering/Box/style.js
new file mode 100644
index 0000000..ada161b
--- /dev/null
+++ b/src/rendering/Box/style.js
@@ -0,0 +1,38 @@
+import {
+ green,
+ brown,
+ tan,
+ grey,
+ blue,
+ fontFamily,
+ fontSizeSmall,
+ strokeBase
+} from 'rendering/style';
+
+export const literal = {
+ fill: blue,
+ strokeWidth: '1px',
+ stroke: brown
+};
+export const escape = {
+ fill: green,
+ strokeWidth: '1px',
+ stroke: brown
+};
+export const charClass = {
+ fill: tan
+};
+export const capture = {
+ fillOpacity: 0,
+ ...strokeBase,
+ stroke: grey,
+ strokeDasharray: '6,2'
+};
+export const anchor = {
+ fill: brown
+};
+export const infoText = {
+ fontSize: fontSizeSmall,
+ fontFamily: fontFamily,
+ dominantBaseline: 'text-after-edge'
+};
diff --git a/src/rendering/types.js b/src/rendering/types.js
index 88beaeb..da7e963 100644
--- a/src/rendering/types.js
+++ b/src/rendering/types.js
@@ -1,7 +1,9 @@
import * as SVG from 'rendering/SVG';
import * as Text from 'rendering/Text';
+import * as Box from 'rendering/Box';
export default {
SVG,
- Text
+ Text,
+ Box
};
diff --git a/src/syntax/js.js b/src/syntax/js.js
index a3c68bd..02e0478 100644
--- a/src/syntax/js.js
+++ b/src/syntax/js.js
@@ -6,12 +6,20 @@ const parse = expr => {
type: 'SVG',
children: [
{
- type: 'Text',
+ type: 'Box',
props: {
- quoted: true
+ theme: 'literal'
},
children: [
- `JS => ${ expr }`
+ {
+ type: 'Text',
+ props: {
+ quoted: true
+ },
+ children: [
+ `JS => ${ expr }`
+ ]
+ }
]
}
]
diff --git a/src/syntax/pcre.js b/src/syntax/pcre.js
index 997046a..12b2fa4 100644
--- a/src/syntax/pcre.js
+++ b/src/syntax/pcre.js
@@ -6,12 +6,20 @@ const parse = expr => {
type: 'SVG',
children: [
{
- type: 'Text',
+ type: 'Box',
props: {
- quoted: true
+ theme: 'literal'
},
children: [
- `PCRE => ${ expr }`
+ {
+ type: 'Text',
+ props: {
+ quoted: true
+ },
+ children: [
+ `PCRE => ${ expr }`
+ ]
+ }
]
}
]