Repository: codypearce/material-bread (js)

Issue:
SheetsSide
**Describe your feature request**
"Side sheets are surfaces containing supplementary content that are anchored to the left or right edge of the screen."

**Context**
https://material.io/design/components/sheets-side.html
![image](https://user-images.githubusercontent.com/12564956/56484934-bfeebc00-6486-11e9-8238-852b0735f0a3.png)


- [ ] Create component
- [ ] Create storybook section
- [ ] Create doc page
- [ ] Create tests



Pull request:
113 sheetside
closes #113 

Patch:
diff --git a/docs/src/content/components/sheet-side/MainDemo.js b/docs/src/content/components/sheet-side/MainDemo.js
new file mode 100644
index 0000000..4cdd458
--- /dev/null
+++ b/docs/src/content/components/sheet-side/MainDemo.js
@@ -0,0 +1,78 @@
+import React from 'react';
+import { View, Image } from 'react-native';
+import { ComponentMainDemo, CodeInline } from '@components';
+import { SheetSide, Button, List, ListItem, Icon } from '../../../../../src/';
+
+export const code = `class MainDemo extends React.Component {
+  constructor(props) {
+    super(props)
+    this.state = {
+        visible: false,
+    }
+  }
+  render() {
+    return (
+      <View style={{ flex: 1}}>
+        <Button
+          text="Open Sheet"
+          onPress={() => {
+            this.setState({ visible: true });
+          }}
+        />
+        <SheetSide
+          visible={this.state.visible}
+          onBackdropPress={() => this.setState({ visible: false })}
+          onSwipeRight={() => this.setState({ visible: false })}>
+          <List>
+            <ListItem
+              text={'Attractions'}
+              icon={<Icon name={'local-movies'} size={24} />}
+            />
+            <ListItem
+              text={'Dining'}
+              icon={<Icon name={'local-dining'} size={24} />}
+            />
+            <ListItem
+              text={'Education'}
+              icon={<Icon name={'edit'} size={24} />}
+            />
+            <ListItem
+              text={'Health'}
+              icon={<Icon name={'favorite'} size={24} />}
+            />
+            <ListItem
+              text={'Family'}
+              icon={<Icon name={'group'} size={24} />}
+            />
+            <ListItem
+              text={'Office'}
+              icon={<Icon name={'content-cut'} size={24} />}
+            />
+            <ListItem
+              text={'Promotions'}
+              icon={<Icon name={'loyalty'} size={24} />}
+            />
+          </List>
+        </SheetSide>
+      </View>
+    );
+  }
+}`;
+
+const MainDemo = pageHref => (
+  <ComponentMainDemo
+    pageHref={pageHref}
+    description={
+      <div>
+        <CodeInline code="SheetSide" type="element" /> animates from the left or
+        right of the screen to show whatever you want. You can manually control
+        swipe left and right on the sheet by using{' '}
+        <CodeInline code="onSwipeRight" type="element" /> and{' '}
+        <CodeInline code="onSwipeLeft" type="element" />.
+      </div>
+    }
+    code={code}
+    scope={{ View, SheetSide, Button, List, ListItem, Icon, Image }}
+  />
+);
+export default MainDemo;
diff --git a/docs/src/content/components/sheet-side/Props.js b/docs/src/content/components/sheet-side/Props.js
new file mode 100644
index 0000000..8a8c941
--- /dev/null
+++ b/docs/src/content/components/sheet-side/Props.js
@@ -0,0 +1,8 @@
+import React from 'react';
+import { ComponentProps } from '@components';
+import propData from './propData';
+
+const Props = pageHref => (
+  <ComponentProps pageHref={pageHref} propData={propData} />
+);
+export default Props;
diff --git a/docs/src/content/components/sheet-side/Usage.js b/docs/src/content/components/sheet-side/Usage.js
new file mode 100644
index 0000000..f737bf3
--- /dev/null
+++ b/docs/src/content/components/sheet-side/Usage.js
@@ -0,0 +1,64 @@
+import React from 'react';
+import { ComponentUsage } from '@components';
+
+export const code = `import React, { Component } from 'react';
+import { SheetSide, Button, List, ListItem, Icon  } from 'material-bread';
+
+class SelectPage extends React.Component {
+  constructor(props) {
+    super(props)
+    this.state = {
+        visible: false
+    }
+  }
+  render() {
+    return (
+      <View style={{ flex: 1}}>
+        <Button
+          text="Open Sheet"
+          onPress={() => {
+            this.setState({ visible: true });
+          }}
+        />
+        <SheetSide
+          visible={this.state.visible}
+          onBackdropPress={() => this.setState({ visible: false })}
+          onSwipeRight={() => this.setState({ visible: false })}>
+          <List>
+            <ListItem
+              text={'Attractions'}
+              icon={<Icon name={'local-movies'} size={24} />}
+            />
+            <ListItem
+              text={'Dining'}
+              icon={<Icon name={'local-dining'} size={24} />}
+            />
+            <ListItem
+              text={'Education'}
+              icon={<Icon name={'edit'} size={24} />}
+            />
+            <ListItem
+              text={'Health'}
+              icon={<Icon name={'favorite'} size={24} />}
+            />
+            <ListItem
+              text={'Family'}
+              icon={<Icon name={'group'} size={24} />}
+            />
+            <ListItem
+              text={'Office'}
+              icon={<Icon name={'content-cut'} size={24} />}
+            />
+            <ListItem
+              text={'Promotions'}
+              icon={<Icon name={'loyalty'} size={24} />}
+            />
+          </List>
+        </SheetSide>
+      </View>
+    );
+  }
+}`;
+
+const Usage = pageHref => <ComponentUsage pageHref={pageHref} code={code} />;
+export default Usage;
diff --git a/docs/src/content/components/sheet-side/index.js b/docs/src/content/components/sheet-side/index.js
new file mode 100644
index 0000000..2506b96
--- /dev/null
+++ b/docs/src/content/components/sheet-side/index.js
@@ -0,0 +1,28 @@
+import React, { Component } from 'react';
+import ComponentPageLayout from '../../../components/ComponentPage/ComponentPageLayout';
+import MainDemo from './MainDemo';
+import Usage from './Usage';
+import Props from './Props';
+
+const sections = [{ name: 'Component' }, { name: 'Usage' }, { name: 'Props' }];
+
+export default class DropdownPage extends Component {
+  render() {
+    return (
+      <div>
+        <ComponentPageLayout
+          componentName={'sheet-side'}
+          title={'SheetSide'}
+          description={`Side sheets are surfaces containing supplementary content that are anchored to the left or right edge of the screen.
+          `}
+          importCode={`import { SheetSide } from 'material-bread';`}
+          docsLink={'https://material.io/design/components/sheets-side.html#'}
+          sideScrollMenuItems={sections}>
+          <MainDemo pageHref={'/components/sheet-side'} />
+          <Usage pageHref={'/components/sheet-side'} />
+          <Props pageHref={'/components/sheet-side'} />
+        </ComponentPageLayout>
+      </div>
+    );
+  }
+}
diff --git a/docs/src/content/components/sheet-side/propData.js b/docs/src/content/components/sheet-side/propData.js
new file mode 100644
index 0000000..d3275e0
--- /dev/null
+++ b/docs/src/content/components/sheet-side/propData.js
@@ -0,0 +1,54 @@
+import { createTableData } from '../../../utils/createPropData';
+const propData = [
+  [
+    'cardVerticalPadding',
+    'Padding vertical on the sheet, this used in calculating the position',
+    'number',
+    '20',
+  ],
+
+  ['duration', 'How long the hide animation takes', 'number', '200ms'],
+  [
+    'onBackdropPress',
+    'Callback when mask is pressed, usually to close sheet',
+    'func',
+    '',
+  ],
+  [
+    'onSwipeLeft',
+    'Callback on swiping left, usually to close sheet',
+    'func',
+    '',
+  ],
+  [
+    'onSwipeRight',
+    'Callback on swiping right, usually to close sheet',
+    'func',
+    '',
+  ],
+  [
+    'pageHeight',
+    "Manually control the height of the page, useful for demos or sheets that shouldn't take up the whole page",
+    'number',
+    'ScreenHeight',
+  ],
+  [
+    'pageWidth',
+    "Manually control the width of the page, useful for demos or sheets that shouldn't take up the whole page",
+    'number',
+    'ScreenHeight',
+  ],
+  ['side', 'Determines the side the sheet appears on', 'string', 'right'],
+  ['style', 'Styles sheet element', 'object', ''],
+
+  ['visible', 'Toggles Sheet animation', 'bool', ''],
+  [
+    'widthPercentage',
+    'Percentage of the screen the sheet should take up on mobile',
+    'number',
+    '.8',
+  ],
+  ['wrapperStyles', 'Styles wrapper around the whole page', 'object', ''],
+];
+
+export default createTableData(propData);
diff --git a/docs/src/navigation/componentPages.js b/docs/src/navigation/componentPages.js
index 3ecaae5..2a69421 100644
--- a/docs/src/navigation/componentPages.js
+++ b/docs/src/navigation/componentPages.js
@@ -196,6 +196,10 @@ const components = [
     title: 'SheetBottom',
     path: '/components/sheet-bottom',
   },
+  {
+    title: 'SheetSide',
+    path: '/components/sheet-side',
+  },
   {
     title: 'Snackbar',
     path: '/components/snackbar',
diff --git a/docs/src/pages/components/sheet-side.js b/docs/src/pages/components/sheet-side.js
new file mode 100644
index 0000000..519e8a9
--- /dev/null
+++ b/docs/src/pages/components/sheet-side.js
@@ -0,0 +1,1 @@
+export default from '../../content/components/sheet-side';
diff --git a/src/Components/SheetSide/SheetSide.js b/src/Components/SheetSide/SheetSide.js
new file mode 100644
index 0000000..d506ca4
--- /dev/null
+++ b/src/Components/SheetSide/SheetSide.js
@@ -0,0 +1,251 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import {
+  View,
+  TouchableOpacity,
+  Animated,
+  PanResponder,
+  Modal,
+  Platform,
+  Dimensions,
+  SafeAreaView,
+} from 'react-native';
+import styles from './SheetSide.styles';
+import WebModal from 'modal-enhanced-react-native-web';
+
+const needsSafeArea = Platform.OS === 'ios' && parseInt(Platform.Version, 10);
+
+class SheetSide extends Component {
+  constructor(props) {
+    super(props);
+    this.createPanResponder(this.props);
+  }
+
+  static propTypes = {
+    duration: PropTypes.number,
+    onBackdropPress: PropTypes.func,
+    onSwipeLeft: PropTypes.func,
+    onSwipeRight: PropTypes.func,
+    children: PropTypes.node,
+    wrapperStyles: PropTypes.object,
+    style: PropTypes.object,
+    visible: PropTypes.bool,
+    pageWidth: PropTypes.number,
+    pageHeight: PropTypes.number,
+    widthPercentage: PropTypes.number,
+    side: PropTypes.string,
+  };
+
+  static defaultProps = {
+    duration: 200,
+    widthPercentage: 0.8,
+    side: 'right',
+  };
+
+  state = {
+    internalVisible: false,
+    animatedPosition: new Animated.Value(0),
+    pan: new Animated.ValueXY(),
+    initialHeight: 0,
+    initialWidth: 0,
+    fullHeight: 0,
+    fullWidth: 0,
+  };
+
+  componentDidMount() {
+    const { visible, pageHeight, pageWidth, side } = this.props;
+    const { pan } = this.state;
+    if (visible) {
+      this._open();
+    }
+
+    this.createPanResponder(this.props);
+
+    const width = Dimensions.get('window').width;
+
+    pan.setValue({ x: side == 'left' ? -width : width, y: 0 });
+
+    this.setState({
+      fullHeight: pageHeight ? pageHeight : Dimensions.get('window').height,
+      fullWidth: pageWidth ? pageWidth : width,
+    });
+  }
+
+  componentDidUpdate(prevProps) {
+    const { visible } = this.props;
+
+    if (visible != prevProps.visible) {
+      if (visible) {
+        this._open();
+      } else {
+        this._close();
+      }
+    }
+  }
+
+  onMenuLayout = e => {
+    const { width, height } = e.nativeEvent.layout;
+
+    this.setState(
+      {
+        initialWidth: width,
+        initialHeight: height,
+      },
+      () => {
+        this.createPanResponder(this.props);
+      },
+    );
+  };
+
+  animateSheet(visible) {
+    const { duration, widthPercentage, side } = this.props;
+    const { fullWidth, pan } = this.state;
+
+    let openValue =
+      Platform.OS == 'web'
+        ? fullWidth - 320
+        : fullWidth * (1 - widthPercentage);
+
+    let closeValue = fullWidth;
+
+    if (side == 'left') {
+      openValue = 0;
+      closeValue = -closeValue;
+    }
+
+    if (visible) {
+      this.setState({ internalVisible: true }, () => {
+        Animated.timing(pan, {
+          toValue: {
+            x: openValue,
+            y: 0,
+          },
+          duration: duration,
+        }).start();
+      });
+    } else {
+      Animated.timing(pan, {
+        toValue: { x: closeValue, y: 0 },
+        duration: duration,
+      }).start(() => {
+        this.setState({ internalVisible: false });
+      });
+    }
+  }
+
+  createPanResponder = () => {
+    const { onSwipeLeft, onSwipeRight, widthPercentage } = this.props;
+    const { fullWidth } = this.state;
+
+    const sheetWidth = Platform.OS == 'web' ? 320 : fullWidth * widthPercentage;
+
+    this.panResponder = PanResponder.create({
+      onStartShouldSetPanResponder: () => true,
+
+      onPanResponderRelease: (e, gestureState) => {
+        if (sheetWidth / 4 - gestureState.dy < 0) {
+          if (onSwipeLeft) onSwipeLeft();
+        } else if (sheetWidth / 4 + gestureState.dy > 0) {
+          if (onSwipeRight) onSwipeRight();
+        }
+      },
+    });
+  };
+
+  _open() {
+    this.animateSheet(true);
+  }
+
+  _close = () => {
+    this.animateSheet(false);
+  };
+
+  _onBackdropPress = () => {
+    const { onBackdropPress } = this.props;
+
+    if (onBackdropPress) onBackdropPress();
+  };
+
+  _handlePlatform() {
+    const { internalVisible } = this.state;
+    if (Platform.OS == 'web') {
+      return (
+        <WebModal
+          transparent
+          animationType={'none'}
+          visible={internalVisible}
+          onRequestClose={this._close}
+          style={{ margin: 0 }}>
+          {this._handleSafeView()}
+        </WebModal>
+      );
+    } else {
+      return (
+        <Modal
+          transparent
+          animationType={'none'}
+          visible={internalVisible}
+          onRequestClose={this._close}>
+          {this._handleSafeView()}
+        </Modal>
+      );
+    }
+  }
+
+  _handleSafeView() {
+    const { fullWidth } = this.state;
+    if (needsSafeArea) {
+      return (
+        <SafeAreaView
+          style={[
+            styles.safeAreaView,
+            {
+              backgroundColor: 'transparent',
+              width: fullWidth,
+              height: '100%',
+            },
+          ]}>
+          {this._renderContent()}
+        </SafeAreaView>
+      );
+    } else {
+      return this._renderContent();
+    }
+  }
+
+  _renderContent() {
+    const { children, wrapperStyles, style, widthPercentage } = this.props;
+    const { pan, fullWidth } = this.state;
+
+    return (
+      <View style={[styles.wrapper, wrapperStyles]}>
+        <TouchableOpacity
+          style={styles.mask}
+          activeOpacity={1}
+          onPress={this._onBackdropPress}
+        />
+
+        <Animated.View
+          {...this.panResponder.panHandlers}
+          style={[
+            styles.container,
+            style,
+
+            {
+              height: '100%',
+              width: Platform.OS == 'web' ? 320 : fullWidth * widthPercentage,
+              transform: [{ translateX: pan.x }],
+            },
+          ]}>
+          <View onLayout={this.onMenuLayout}>{children}</View>
+        </Animated.View>
+      </View>
+    );
+  }
+
+  render() {
+    return this._handlePlatform();
+  }
+}
+
+export default SheetSide;
diff --git a/src/Components/SheetSide/SheetSide.stories.js b/src/Components/SheetSide/SheetSide.stories.js
new file mode 100644
index 0000000..38f84f2
--- /dev/null
+++ b/src/Components/SheetSide/SheetSide.stories.js
@@ -0,0 +1,122 @@
+import React from 'react';
+import { View, Platform } from 'react-native';
+
+import { SheetSide, Button, List, ListItem, Icon } from '../../';
+import { State, Store } from '@sambego/storybook-state';
+import Header from '../../storybook/components/Header';
+import Container from '../../storybook/components/Container';
+import { storiesOf } from '../../storybook/helpers/storiesOf';
+
+const store = new Store({
+  visible: false,
+});
+
+export default storiesOf('Components|SheetSide', module)
+  .addParameters({ jest: ['SheetSide'] })
+  .add('Modal Right', () => (
+    <Container>
+      <Header title={'SheetSide'} />
+      <State store={store} style={{ flex: 1 }}>
+        {state => (
+          <View style={{ flex: 1, height: 400 }}>
+            <Button
+              text="Open Sheet"
+              onPress={() => {
+                store.set({ visible: true });
+              }}
+            />
+            <SheetSide
+              visible={state.visible}
+              pageHeight={Platform.OS == 'web' ? 600 : null}
+              onBackdropPress={() => store.set({ visible: false })}
+              onSwipeRight={() => store.set({ visible: false })}>
+              <List>
+                <ListItem
+                  text={'Attractions'}
+                  icon={<Icon name={'local-movies'} size={24} />}
+                />
+                <ListItem
+                  text={'Dining'}
+                  icon={<Icon name={'local-dining'} size={24} />}
+                />
+                <ListItem
+                  text={'Education'}
+                  icon={<Icon name={'edit'} size={24} />}
+                />
+                <ListItem
+                  text={'Health'}
+                  icon={<Icon name={'favorite'} size={24} />}
+                />
+                <ListItem
+                  text={'Family'}
+                  icon={<Icon name={'group'} size={24} />}
+                />
+                <ListItem
+                  text={'Office'}
+                  icon={<Icon name={'content-cut'} size={24} />}
+                />
+                <ListItem
+                  text={'Promotions'}
+                  icon={<Icon name={'loyalty'} size={24} />}
+                />
+              </List>
+            </SheetSide>
+          </View>
+        )}
+      </State>
+    </Container>
+  ))
+  .add('Modal Left', () => (
+    <Container>
+      <Header title={'SheetSide'} />
+      <State store={store} style={{ flex: 1 }}>
+        {state => (
+          <View style={{ flex: 1, height: 400 }}>
+            <Button
+              text="Open Sheet"
+              onPress={() => {
+                store.set({ visible: true });
+              }}
+            />
+            <SheetSide
+              visible={state.visible}
+              side={'left'}
+              pageHeight={Platform.OS == 'web' ? 600 : null}
+              onBackdropPress={() => store.set({ visible: false })}
+              onSwipeLeft={() => store.set({ visible: false })}>
+              <List>
+                <ListItem
+                  text={'Attractions'}
+                  icon={<Icon name={'local-movies'} size={24} />}
+                />
+                <ListItem
+                  text={'Dining'}
+                  icon={<Icon name={'local-dining'} size={24} />}
+                />
+                <ListItem
+                  text={'Education'}
+                  icon={<Icon name={'edit'} size={24} />}
+                />
+                <ListItem
+                  text={'Health'}
+                  icon={<Icon name={'favorite'} size={24} />}
+                />
+                <ListItem
+                  text={'Family'}
+                  icon={<Icon name={'group'} size={24} />}
+                />
+                <ListItem
+                  text={'Office'}
+                  icon={<Icon name={'content-cut'} size={24} />}
+                />
+                <ListItem
+                  text={'Promotions'}
+                  icon={<Icon name={'loyalty'} size={24} />}
+                />
+              </List>
+            </SheetSide>
+          </View>
+        )}
+      </State>
+    </Container>
+  ));
diff --git a/src/Components/SheetSide/SheetSide.styles.js b/src/Components/SheetSide/SheetSide.styles.js
new file mode 100644
index 0000000..e89e870
--- /dev/null
+++ b/src/Components/SheetSide/SheetSide.styles.js
@@ -0,0 +1,25 @@
+import { StyleSheet } from 'react-native';
+
+const styles = StyleSheet.create({
+  wrapper: {
+    flex: 1,
+    backgroundColor: '#00000077',
+  },
+  mask: {
+    backgroundColor: 'transparent',
+    width: '100%',
+    height: '100%',
+    zIndex: -1,
+    position: 'absolute',
+    top: 0,
+    left: 0,
+  },
+  container: {
+    backgroundColor: '#fff',
+    overflow: 'hidden',
+    paddingVertical: 16,
+    zIndex: 1000,
+  },
+});
+
+export default styles;
diff --git a/src/index.js b/src/index.js
index eb8d23f..99c3d6f 100644
--- a/src/index.js
+++ b/src/index.js
@@ -86,6 +86,7 @@ export {
 } from './Components/TextField/Searchfield/Searchfield';
 export { default as Searchbar } from './Components/Appbar/Searchbar/Searchbar';
 export { default as SheetBottom } from './Components/SheetBottom/SheetBottom';
+export { default as SheetSide } from './Components/SheetSide/SheetSide';
 export { default as Snackbar } from './Components/Snackbar/Snackbar';
 export { default as Switch } from './Components/Switch/Switch';
 export { default as TextField } from './Components/TextField/TextField';
diff --git a/src/storybook/stories/index.js b/src/storybook/stories/index.js
index 423a4d1..981ca7c 100644
--- a/src/storybook/stories/index.js
+++ b/src/storybook/stories/index.js
@@ -118,6 +118,9 @@ export {
 export {
   default as SheetBottom,
 } from '../../Components/SheetBottom/SheetBottom.stories.js';
+export {
+  default as SheetSide,
+} from '../../Components/SheetSide/SheetSide.stories.js';
 export {
   default as Snackbar,
 } from '../../Components/Snackbar/Snackbar.stories.js';


Test patch:
diff --git a/src/Components/SheetSide/SheetSide.test.js b/src/Components/SheetSide/SheetSide.test.js
new file mode 100644
index 0000000..aaa9d9a
--- /dev/null
+++ b/src/Components/SheetSide/SheetSide.test.js
@@ -0,0 +1,10 @@
+import React from 'react';
+import { SheetSide } from '../../';
+
+import ShallowRenderer from 'react-test-renderer/shallow';
+const renderer = new ShallowRenderer();
+
+test('SheetSide Renders', () => {
+  const tree = renderer.render(<SheetSide />);
+  expect(tree).toMatchSnapshot();
+});
diff --git a/src/Components/SheetSide/__snapshots__/SheetSide.test.js.snap b/src/Components/SheetSide/__snapshots__/SheetSide.test.js.snap
new file mode 100644
index 0000000..d4003c3
--- /dev/null
+++ b/src/Components/SheetSide/__snapshots__/SheetSide.test.js.snap
@@ -0,0 +1,77 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`SheetSide Renders 1`] = `
+<Component
+  animationType="none"
+  hardwareAccelerated={false}
+  onRequestClose={[Function]}
+  transparent={true}
+  visible={false}
+>
+  <View
+    style={
+      Array [
+        Object {
+          "backgroundColor": "#00000077",
+          "flex": 1,
+        },
+        undefined,
+      ]
+    }
+  >
+    <TouchableOpacity
+      activeOpacity={1}
+      onPress={[Function]}
+      style={
+        Object {
+          "backgroundColor": "transparent",
+          "height": "100%",
+          "left": 0,
+          "position": "absolute",
+          "top": 0,
+          "width": "100%",
+          "zIndex": -1,
+        }
+      }
+    />
+    <AnimatedComponent
+      onMoveShouldSetResponder={[Function]}
+      onMoveShouldSetResponderCapture={[Function]}
+      onResponderEnd={[Function]}
+      onResponderGrant={[Function]}
+      onResponderMove={[Function]}
+      onResponderReject={[Function]}
+      onResponderRelease={[Function]}
+      onResponderStart={[Function]}
+      onResponderTerminate={[Function]}
+      onResponderTerminationRequest={[Function]}
+      onStartShouldSetResponder={[Function]}
+      onStartShouldSetResponderCapture={[Function]}
+      style={
+        Array [
+          Object {
+            "backgroundColor": "#fff",
+            "overflow": "hidden",
+            "paddingVertical": 16,
+            "zIndex": 1000,
+          },
+          undefined,
+          Object {
+            "height": "100%",
+            "transform": Array [
+              Object {
+                "translateX": 0,
+              },
+            ],
+            "width": 0,
+          },
+        ]
+      }
+    >
+      <View
+        onLayout={[Function]}
+      />
+    </AnimatedComponent>
+  </View>
+</Component>
+`;