@nypl/design-system-react-components 1.2.0-rc → 1.2.0-rc-3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,7 @@ export interface TooltipProps {
3
3
  /** Any child node passed to the component. */
4
4
  children: React.ReactNode;
5
5
  /** Value used to populate the tooltip content. */
6
- content: string | React.ReactNode;
6
+ content: string | number | React.ReactNode;
7
7
  /** A class name for the Tooltip parent div. */
8
8
  className?: string;
9
9
  /** ID that other components can cross reference for accessibility purposes. */
@@ -2464,7 +2464,7 @@ var AlphabetFilter = /*#__PURE__*/react.chakra( /*#__PURE__*/React.forwardRef(fu
2464
2464
  id: "filter-" + item.value,
2465
2465
  isDisabled: isButtonDisabled,
2466
2466
  key: item.value,
2467
- __css: buttonStyles,
2467
+ sx: buttonStyles,
2468
2468
  onClick: function onClick(e) {
2469
2469
  handleOnClick(e, item.value);
2470
2470
  }
@@ -2514,33 +2514,49 @@ var AudioPlayer = /*#__PURE__*/react.chakra( /*#__PURE__*/React.forwardRef(funct
2514
2514
  id = props.id,
2515
2515
  _props$iframeTitle = props.iframeTitle,
2516
2516
  iframeTitle = _props$iframeTitle === void 0 ? null : _props$iframeTitle,
2517
- rest = _objectWithoutPropertiesLoose(props, _excluded$Z); // The root iframe object generated from the embedCode.
2517
+ rest = _objectWithoutPropertiesLoose(props, _excluded$Z);
2518
2518
 
2519
+ var _useState = React.useState(false),
2520
+ invalidEmbed = _useState[0],
2521
+ setInvalidEmbed = _useState[1];
2519
2522
 
2520
- var iframeDoc = parseIframeEmbedCode(embedCode); // when no embedCode or it was a broken code.
2523
+ var _useState2 = React.useState(undefined),
2524
+ iframeDoc = _useState2[0],
2525
+ setIframeDoc = _useState2[1];
2526
+ /**
2527
+ * Main hooks to check the embedCode structure.
2528
+ */
2521
2529
 
2522
- var isInvalidEmbed = !embedCode || !iframeDoc || !isValidEmbedCode(iframeDoc);
2523
- var errorMessage = "<strong>Error: </strong>This audio player has not been configured properly. Please contact the site administrator.";
2524
2530
 
2525
- function isValidEmbedCode(doc) {
2526
- var _doc$src;
2531
+ React.useEffect(function () {
2532
+ // The root iframe object generated from the embedCode.
2533
+ var iframe = parseIframeEmbedCode(embedCode); // when no embedCode or it was a broken code.
2527
2534
 
2528
- return audioType !== "file" && (doc == null ? void 0 : (_doc$src = doc.src) == null ? void 0 : _doc$src.includes(audioType + ".com"));
2529
- } // Only set the iframe title if it doesn't already have it in the iframe.
2535
+ var isInvalidEmbed = !embedCode || !iframe || !isValidEmbedCode(audioType, iframe); // Only set the iframe title if it doesn't already have it in the iframe.
2530
2536
 
2537
+ if (iframe && !iframe.title) {
2538
+ iframe.title = iframeTitle ? iframeTitle : "Embedded audio player";
2539
+ }
2531
2540
 
2532
- if (iframeDoc && !iframeDoc.title) {
2533
- iframeDoc.title = iframeTitle ? iframeTitle : "Embedded audio player";
2534
- }
2541
+ var isThirdPartyService = !!thirdPartyServices.find(function (service) {
2542
+ return service === audioType;
2543
+ });
2544
+ var isThirdPartyWithoutCode = isThirdPartyService && !embedCode;
2535
2545
 
2536
- var isThirdPartyService = !!thirdPartyServices.find(function (service) {
2537
- return service === audioType;
2538
- });
2539
- var isThirdPartyWithoutCode = isThirdPartyService && !embedCode;
2546
+ if (isThirdPartyWithoutCode) {
2547
+ console.warn("NYPL Reservoir AudioPlayer: The `embedCode` prop is required when using a 3rd party streaming service.");
2548
+ isInvalidEmbed = true;
2549
+ }
2550
+
2551
+ setInvalidEmbed(isInvalidEmbed);
2552
+ setIframeDoc(iframe);
2553
+ }, [embedCode, audioType, iframeTitle]);
2554
+ var errorMessage = "<strong>Error: </strong>This audio player has not been configured properly. Please contact the site administrator.";
2555
+
2556
+ function isValidEmbedCode(audioType, doc) {
2557
+ var _doc$src;
2540
2558
 
2541
- if (isThirdPartyWithoutCode) {
2542
- console.warn("NYPL Reservoir AudioPlayer: The `embedCode` prop is required when using a 3rd party streaming service.");
2543
- isInvalidEmbed = true;
2559
+ return audioType !== "file" && (doc == null ? void 0 : (_doc$src = doc.src) == null ? void 0 : _doc$src.includes(audioType + ".com"));
2544
2560
  }
2545
2561
 
2546
2562
  var styles = react.useMultiStyleConfig("AudioPlayer", {});
@@ -2567,7 +2583,7 @@ var AudioPlayer = /*#__PURE__*/react.chakra( /*#__PURE__*/React.forwardRef(funct
2567
2583
  "data-testid": "audio-player-component",
2568
2584
  ref: ref,
2569
2585
  __css: styles
2570
- }, rest), isInvalidEmbed ? React__default.createElement(react.Box, {
2586
+ }, rest), invalidEmbed ? React__default.createElement(react.Box, {
2571
2587
  dangerouslySetInnerHTML: {
2572
2588
  __html: errorMessage
2573
2589
  },
@@ -5195,7 +5211,8 @@ var AlphabetFilter$1 = {
5195
5211
  }
5196
5212
  },
5197
5213
  _disabled: {
5198
- color: "ui.gray.medium"
5214
+ color: "ui.gray.medium",
5215
+ cursor: "not-allowed"
5199
5216
  }
5200
5217
  }
5201
5218
  }
@@ -9373,6 +9390,7 @@ var theme = /*#__PURE__*/react.extendTheme( /*#__PURE__*/_extends({
9373
9390
  components: /*#__PURE__*/_extends({
9374
9391
  Accordion: Accordion$1,
9375
9392
  AlphabetFilter: AlphabetFilter$1,
9393
+ AudioPlayer: AudioPlayer$1,
9376
9394
  Breadcrumb: Breadcrumb,
9377
9395
  Button: Button$1,
9378
9396
  ButtonGroup: ButtonGroup$1
@@ -9425,8 +9443,7 @@ var theme = /*#__PURE__*/react.extendTheme( /*#__PURE__*/_extends({
9425
9443
  TextInput: TextInput$1
9426
9444
  }, Toggle$1, {
9427
9445
  Tooltip: Tooltip,
9428
- VideoPlayer: VideoPlayer,
9429
- AudioPlayer: AudioPlayer$1
9446
+ VideoPlayer: VideoPlayer
9430
9447
  }),
9431
9448
  config: {
9432
9449
  // Use `cssVarPrefix` to set the prefix used on the CSS vars produced by
@@ -15584,7 +15601,224 @@ var Tabs = /*#__PURE__*/react.chakra( /*#__PURE__*/React.forwardRef(function (pr
15584
15601
  }, React__default.createElement(react.Box, Object.assign({}, carouselStyle), tabs)), nextButton), panels);
15585
15602
  })); // Tabs is also exported above so the props can display in Storybook.
15586
15603
 
15587
- var _excluded$2d = ["aboveHeader", "breakout", "contentId", "contentBottom", "contentPrimary", "contentSidebar", "contentTop", "footer", "header", "sidebar", "renderFooterElement", "renderHeaderElement", "renderSkipNavigation"];
15604
+ var _excluded$2d = ["children", "className", "content", "id", "isDisabled", "shouldWrapChildren"];
15605
+ var Tooltip$1 = /*#__PURE__*/react.chakra( /*#__PURE__*/React.forwardRef(function (props, ref) {
15606
+ var children = props.children,
15607
+ content = props.content,
15608
+ isDisabled = props.isDisabled,
15609
+ shouldWrapChildren = props.shouldWrapChildren,
15610
+ rest = _objectWithoutPropertiesLoose(props, _excluded$2d);
15611
+
15612
+ if (typeof content !== "string" && typeof content !== "number") {
15613
+ React__default.Children.map(content, function (contentChild) {
15614
+ if (contentChild.type !== Icon || contentChild.type !== Image) {
15615
+ console.warn("NYPL Reservoir Tooltip: Pass in a string, number, DS Icon, or DS Image into the 'content' prop.");
15616
+ }
15617
+ });
15618
+ }
15619
+
15620
+ var newChildren = shouldWrapChildren ? React__default.createElement(ComponentWrapper, {
15621
+ width: "fit-content"
15622
+ }, children) : children;
15623
+ var styles = react.useMultiStyleConfig("Tooltip", {});
15624
+ return React__default.createElement(react.Tooltip, Object.assign({
15625
+ hasArrow: true,
15626
+ "aria-label": typeof content !== "string" ? "Tooltip" : undefined,
15627
+ label: content,
15628
+ isDisabled: isDisabled,
15629
+ placement: "top",
15630
+ openDelay: 500,
15631
+ ref: ref,
15632
+ __css: styles
15633
+ }, rest), newChildren);
15634
+ }));
15635
+
15636
+ /**
15637
+ * The "explore" `TagSet` variant will always display the tags passed as data.
15638
+ * The `label` property in the `tagSetData` prop should be set to a link-type
15639
+ * JSX component for linking to specific content.
15640
+ */
15641
+
15642
+ var TagSetExplore$1 = /*#__PURE__*/react.chakra(function (props) {
15643
+ var id = props.id,
15644
+ _props$tagSetData = props.tagSetData,
15645
+ tagSetData = _props$tagSetData === void 0 ? [] : _props$tagSetData;
15646
+ var styles = react.useStyleConfig("TagSetExplore");
15647
+ return React__default.createElement(React__default.Fragment, null, tagSetData.map(function (tagSet, key) {
15648
+ if (typeof tagSet.label === "string") {
15649
+ console.warn("NYPL Reservoir TagSet: Explore tags require all `label` props to be React components.");
15650
+ }
15651
+
15652
+ return React__default.createElement(TooltipWrapper, {
15653
+ key: key,
15654
+ label: tagSet.label
15655
+ }, React__default.createElement(react.Box, {
15656
+ "data-testid": "explore-tags",
15657
+ id: "ts-explore-" + id + "-" + key,
15658
+ __css: styles
15659
+ }, tagSet.iconName ? React__default.createElement(Icon, {
15660
+ align: "left",
15661
+ "data-testid": "ts-icon",
15662
+ name: tagSet.iconName,
15663
+ size: "small"
15664
+ }) : null, React__default.createElement("span", null, tagSet.label)));
15665
+ }));
15666
+ });
15667
+
15668
+ /**
15669
+ * The "filter" `TagSet` variant will display tags that can be removed when
15670
+ * `isDismissible` is true and they are clicked.
15671
+ */
15672
+
15673
+ var TagSetFilter$1 = /*#__PURE__*/react.chakra(function (props) {
15674
+ var id = props.id,
15675
+ _props$isDismissible = props.isDismissible,
15676
+ isDismissible = _props$isDismissible === void 0 ? false : _props$isDismissible,
15677
+ onClick = props.onClick,
15678
+ _props$tagSetData = props.tagSetData,
15679
+ tagSetData = _props$tagSetData === void 0 ? [] : _props$tagSetData;
15680
+
15681
+ var _React$useState = React__default.useState(tagSetData),
15682
+ filters = _React$useState[0],
15683
+ setFilters = _React$useState[1];
15684
+
15685
+ var styles = react.useMultiStyleConfig("TagSetFilter", {
15686
+ isDismissible: isDismissible
15687
+ });
15688
+
15689
+ var finalOnClick = function finalOnClick(tagLabel) {
15690
+ onClick && onClick(tagLabel);
15691
+ }; // This expects that the consuming app passes in a new set of data
15692
+ // whenever the current list of tags needs to be updated.
15693
+
15694
+
15695
+ React.useEffect(function () {
15696
+ setFilters(tagSetData);
15697
+ }, [tagSetData, setFilters]);
15698
+ return React__default.createElement(React__default.Fragment, null, filters.map(function (tagSet, key) {
15699
+ if (typeof tagSet.label !== "string") {
15700
+ console.warn("NYPL Reservoir TagSet: Filter tags require all `label` props to be strings.");
15701
+ }
15702
+
15703
+ if (isDismissible && tagSet.iconName) {
15704
+ console.warn("NYPL Reservoir TagSet: Filter tags will not render icons when `isDismissible` is set to true.");
15705
+ }
15706
+
15707
+ return React__default.createElement(TooltipWrapper, {
15708
+ key: key,
15709
+ label: tagSet.label
15710
+ }, React__default.createElement(Button, {
15711
+ "data-testid": "filter-tags",
15712
+ id: "ts-filter-" + id + "-" + key,
15713
+ onClick: isDismissible ? function () {
15714
+ return finalOnClick(tagSet.label);
15715
+ } : undefined,
15716
+ sx: styles
15717
+ }, !isDismissible && tagSet.iconName ? React__default.createElement(Icon, {
15718
+ align: "left",
15719
+ "data-testid": "ts-icon",
15720
+ name: tagSet.iconName,
15721
+ size: "small"
15722
+ }) : null, React__default.createElement("span", null, tagSet.label), isDismissible ? React__default.createElement(Icon, {
15723
+ "data-testid": "filter-close-icon",
15724
+ align: "right",
15725
+ name: "close",
15726
+ size: "small",
15727
+ color: "ui.gray.x-dark",
15728
+ width: "12px"
15729
+ }) : null));
15730
+ }), filters.length > 1 && isDismissible ? React__default.createElement(Button, {
15731
+ buttonType: "link",
15732
+ "data-testid": "filter-clear-all",
15733
+ id: "ts-filter-clear-all-" + id,
15734
+ onClick: function onClick() {
15735
+ return finalOnClick("clearFilters");
15736
+ },
15737
+ __css: styles.clearAll
15738
+ }, "Clear Filters") : null);
15739
+ });
15740
+
15741
+ var _excluded$2e = ["className", "id", "isDismissible", "onClick", "tagSetData", "type"];
15742
+
15743
+ function isFilterType(type) {
15744
+ return type === "filter";
15745
+ }
15746
+ /**
15747
+ * This helper component wrapper renders a DS `Tooltip` component if the text is
15748
+ * long or a React fragment. This assumes that the `label` prop is a rather
15749
+ * simple single root JSX element, such as `<Link ...>....</Link>`.
15750
+ */
15751
+
15752
+ var TooltipWrapper = function TooltipWrapper(_ref) {
15753
+ var label = _ref.label,
15754
+ children = _ref.children;
15755
+ var maxCharLengthToShow = 20;
15756
+ var labelText = typeof label === "string" ? label : label.props.children;
15757
+
15758
+ if (labelText.length > maxCharLengthToShow && typeof labelText === "string") {
15759
+ return React__default.createElement(Tooltip$1, {
15760
+ content: labelText
15761
+ }, children);
15762
+ }
15763
+
15764
+ return React__default.createElement(React__default.Fragment, null, children);
15765
+ };
15766
+ /**
15767
+ * The `TagSet` component renders a group of individual tags which have two
15768
+ * variants: "explore" and "filter". The "explore" tags are meant to be used for
15769
+ * exploratory linkable elements, whereas the "filter" tags are used to display
15770
+ * the filter values that were selected through another UI. Only "filter" tags
15771
+ * can be dismissible.
15772
+ *
15773
+ * The width of a single tag will never be greater than 200px. If necessary, a
15774
+ * tag’s label text will be truncated to keep a tag’s width at or below 200px.
15775
+ * The full label text will be automatically revealed when the tag is hovered
15776
+ * with a DS `Tooltip` component.
15777
+ */
15778
+
15779
+ var TagSet$1 = /*#__PURE__*/react.chakra( /*#__PURE__*/React.forwardRef(function (props, ref) {
15780
+ var className = props.className,
15781
+ id = props.id,
15782
+ _props$isDismissible = props.isDismissible,
15783
+ isDismissible = _props$isDismissible === void 0 ? false : _props$isDismissible,
15784
+ onClick = props.onClick,
15785
+ _props$tagSetData = props.tagSetData,
15786
+ tagSetData = _props$tagSetData === void 0 ? [] : _props$tagSetData,
15787
+ _props$type = props.type,
15788
+ type = _props$type === void 0 ? "filter" : _props$type,
15789
+ rest = _objectWithoutPropertiesLoose(props, _excluded$2e);
15790
+
15791
+ var styles = react.useStyleConfig("TagSet", {});
15792
+
15793
+ if (!isFilterType(type)) {
15794
+ if (isDismissible) {
15795
+ console.warn("NYPL Reservoir TagSet: The `isDismissible` prop will be ignored when the `type` prop is set to 'explore'.");
15796
+ }
15797
+
15798
+ if (onClick) {
15799
+ console.warn("NYPL Reservoir TagSet: The `onClick` prop will be ignored when the `type` prop is set to 'explore'.");
15800
+ }
15801
+ }
15802
+
15803
+ return React__default.createElement(react.Flex, Object.assign({
15804
+ className: className,
15805
+ id: id,
15806
+ ref: ref,
15807
+ __css: styles
15808
+ }, rest), !isFilterType(type) && React__default.createElement(TagSetExplore$1, {
15809
+ id: id,
15810
+ tagSetData: tagSetData,
15811
+ type: type
15812
+ }), isFilterType(type) && React__default.createElement(TagSetFilter$1, {
15813
+ id: id,
15814
+ isDismissible: isDismissible,
15815
+ onClick: onClick,
15816
+ tagSetData: tagSetData,
15817
+ type: type
15818
+ }));
15819
+ }));
15820
+
15821
+ var _excluded$2f = ["aboveHeader", "breakout", "contentId", "contentBottom", "contentPrimary", "contentSidebar", "contentTop", "footer", "header", "sidebar", "renderFooterElement", "renderHeaderElement", "renderSkipNavigation"];
15588
15822
  /**
15589
15823
  * The main top-level parent component that wraps all template-related
15590
15824
  * components.
@@ -15834,7 +16068,7 @@ var TemplateAppContainer = /*#__PURE__*/react.chakra( /*#__PURE__*/React.forward
15834
16068
  renderHeaderElement = _props$renderHeaderEl === void 0 ? true : _props$renderHeaderEl,
15835
16069
  _props$renderSkipNavi = props.renderSkipNavigation,
15836
16070
  renderSkipNavigation = _props$renderSkipNavi === void 0 ? false : _props$renderSkipNavi,
15837
- rest = _objectWithoutPropertiesLoose(props, _excluded$2d);
16071
+ rest = _objectWithoutPropertiesLoose(props, _excluded$2f);
15838
16072
 
15839
16073
  var aboveHeaderElem = aboveHeader && React__default.createElement(TemplateAboveHeader, null, aboveHeader);
15840
16074
  var contentTopElem = contentTop && React__default.createElement(TemplateContentTop, null, contentTop);
@@ -15853,7 +16087,7 @@ var TemplateAppContainer = /*#__PURE__*/react.chakra( /*#__PURE__*/React.forward
15853
16087
  }, footer)));
15854
16088
  }));
15855
16089
 
15856
- var _excluded$2e = ["defaultChecked", "helperText", "id", "invalidText", "isChecked", "isDisabled", "isInvalid", "isRequired", "labelText", "name", "onChange", "size"];
16090
+ var _excluded$2g = ["defaultChecked", "helperText", "id", "invalidText", "isChecked", "isDisabled", "isInvalid", "isRequired", "labelText", "name", "onChange", "size"];
15857
16091
  var onChangeDefault = function onChangeDefault() {
15858
16092
  return;
15859
16093
  };
@@ -15880,7 +16114,7 @@ var Toggle$2 = /*#__PURE__*/react.chakra( /*#__PURE__*/React.forwardRef(function
15880
16114
  onChange = _props$onChange === void 0 ? onChangeDefault : _props$onChange,
15881
16115
  _props$size = props.size,
15882
16116
  size = _props$size === void 0 ? "default" : _props$size,
15883
- rest = _objectWithoutPropertiesLoose(props, _excluded$2e);
16117
+ rest = _objectWithoutPropertiesLoose(props, _excluded$2g);
15884
16118
 
15885
16119
  var styles = react.useMultiStyleConfig("Toggle", {
15886
16120
  isDisabled: isDisabled,
@@ -15929,7 +16163,7 @@ var Toggle$2 = /*#__PURE__*/react.chakra( /*#__PURE__*/React.forwardRef(function
15929
16163
  }), labelText)));
15930
16164
  }));
15931
16165
 
15932
- var _excluded$2f = ["aspectRatio", "className", "descriptionText", "embedCode", "headingText", "helperText", "id", "iframeTitle", "showHelperInvalidText", "videoId", "videoType"];
16166
+ var _excluded$2h = ["aspectRatio", "className", "descriptionText", "embedCode", "headingText", "helperText", "id", "iframeTitle", "showHelperInvalidText", "videoId", "videoType"];
15933
16167
  var VideoPlayer$1 = /*#__PURE__*/react.chakra( /*#__PURE__*/React.forwardRef(function (props, ref) {
15934
16168
  var aspectRatio = props.aspectRatio,
15935
16169
  className = props.className,
@@ -15943,7 +16177,7 @@ var VideoPlayer$1 = /*#__PURE__*/react.chakra( /*#__PURE__*/React.forwardRef(fun
15943
16177
  showHelperInvalidText = _props$showHelperInva === void 0 ? true : _props$showHelperInva,
15944
16178
  videoId = props.videoId,
15945
16179
  videoType = props.videoType,
15946
- rest = _objectWithoutPropertiesLoose(props, _excluded$2f);
16180
+ rest = _objectWithoutPropertiesLoose(props, _excluded$2h);
15947
16181
 
15948
16182
  var iframeTitleFinal = videoType === "vimeo" ? iframeTitle || "Vimeo video player" : iframeTitle || "YouTube video player";
15949
16183
  var videoSrc = videoType === "vimeo" ? "https://player.vimeo.com/video/" + videoId + "?autoplay=0&loop=0" : "https://www.youtube.com/embed/" + videoId + "?disablekb=1&autoplay=0&fs=1&modestbranding=0";
@@ -16013,38 +16247,6 @@ var VideoPlayer$1 = /*#__PURE__*/react.chakra( /*#__PURE__*/React.forwardRef(fun
16013
16247
  }, embedElement)));
16014
16248
  }));
16015
16249
 
16016
- var _excluded$2g = ["children", "className", "content", "id", "isDisabled", "shouldWrapChildren"];
16017
- var Tooltip$1 = /*#__PURE__*/react.chakra( /*#__PURE__*/React.forwardRef(function (props, ref) {
16018
- var children = props.children,
16019
- content = props.content,
16020
- isDisabled = props.isDisabled,
16021
- shouldWrapChildren = props.shouldWrapChildren,
16022
- rest = _objectWithoutPropertiesLoose(props, _excluded$2g);
16023
-
16024
- if (typeof content !== "string") {
16025
- React__default.Children.map(content, function (contentChild) {
16026
- if (contentChild.type !== Icon || contentChild.type !== Image) {
16027
- console.warn("NYPL Reservoir Tooltip: Pass in a string, DS Icon, or DS Image into the 'content' prop.");
16028
- }
16029
- });
16030
- }
16031
-
16032
- var newChildren = shouldWrapChildren ? React__default.createElement(ComponentWrapper, {
16033
- width: "fit-content"
16034
- }, children) : children;
16035
- var styles = react.useMultiStyleConfig("Tooltip", {});
16036
- return React__default.createElement(react.Tooltip, Object.assign({
16037
- hasArrow: true,
16038
- "aria-label": typeof content !== "string" ? "Tooltip" : undefined,
16039
- label: content,
16040
- isDisabled: isDisabled,
16041
- placement: "top",
16042
- openDelay: 500,
16043
- ref: ref,
16044
- __css: styles
16045
- }, rest), newChildren);
16046
- }));
16047
-
16048
16250
  Object.defineProperty(exports, 'Box', {
16049
16251
  enumerable: true,
16050
16252
  get: function () {
@@ -16212,6 +16414,7 @@ exports.StatusBadge = StatusBadge$1;
16212
16414
  exports.StructuredContent = StructuredContent$1;
16213
16415
  exports.Table = Table;
16214
16416
  exports.Tabs = Tabs;
16417
+ exports.TagSet = TagSet$1;
16215
16418
  exports.Template = Template$1;
16216
16419
  exports.TemplateAboveHeader = TemplateAboveHeader;
16217
16420
  exports.TemplateAppContainer = TemplateAppContainer;