@blaze-cms/react-page-builder 0.146.0-node18-core-styles-tooltips.20 → 0.146.0-node18-tooltips.19

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.
Files changed (72) hide show
  1. package/CHANGELOG.md +25 -63
  2. package/lib/components/ContentGroup/ContentGroup.js +41 -10
  3. package/lib/components/ContentGroup/ContentGroup.js.map +1 -1
  4. package/lib/components/ContentGroup/ContentGroupAccordion.js +175 -0
  5. package/lib/components/ContentGroup/ContentGroupAccordion.js.map +1 -0
  6. package/lib/components/ContentGroup/constants.js +13 -1
  7. package/lib/components/ContentGroup/constants.js.map +1 -1
  8. package/lib/components/ContentGroup/helpers/get-structured-data-properties.js +40 -0
  9. package/lib/components/ContentGroup/helpers/get-structured-data-properties.js.map +1 -0
  10. package/lib/components/Menu/Menu.js +1 -4
  11. package/lib/components/Menu/Menu.js.map +1 -1
  12. package/lib/components/Menu/MenuContext.js +1 -2
  13. package/lib/components/Menu/MenuContext.js.map +1 -1
  14. package/lib/components/MenuItem/MenuItemRender.js +12 -27
  15. package/lib/components/MenuItem/MenuItemRender.js.map +1 -1
  16. package/lib/components/MenuItem/helpers/index.js +0 -14
  17. package/lib/components/MenuItem/helpers/index.js.map +1 -1
  18. package/lib/components/SearchContent/SearchContent.js +4 -3
  19. package/lib/components/SearchContent/SearchContent.js.map +1 -1
  20. package/lib/system-components/EditorMode/helpers/add-editor-mode-event-listeners.js +0 -1
  21. package/lib/system-components/EditorMode/helpers/add-editor-mode-event-listeners.js.map +1 -1
  22. package/lib-es/components/ContentGroup/ContentGroup.js +25 -10
  23. package/lib-es/components/ContentGroup/ContentGroup.js.map +1 -1
  24. package/lib-es/components/ContentGroup/ContentGroupAccordion.js +138 -0
  25. package/lib-es/components/ContentGroup/ContentGroupAccordion.js.map +1 -0
  26. package/lib-es/components/ContentGroup/constants.js +12 -1
  27. package/lib-es/components/ContentGroup/constants.js.map +1 -1
  28. package/lib-es/components/ContentGroup/helpers/get-structured-data-properties.js +33 -0
  29. package/lib-es/components/ContentGroup/helpers/get-structured-data-properties.js.map +1 -0
  30. package/lib-es/components/Menu/Menu.js +1 -4
  31. package/lib-es/components/Menu/Menu.js.map +1 -1
  32. package/lib-es/components/Menu/MenuContext.js +1 -2
  33. package/lib-es/components/Menu/MenuContext.js.map +1 -1
  34. package/lib-es/components/MenuItem/MenuItemRender.js +11 -25
  35. package/lib-es/components/MenuItem/MenuItemRender.js.map +1 -1
  36. package/lib-es/components/MenuItem/helpers/index.js +1 -3
  37. package/lib-es/components/MenuItem/helpers/index.js.map +1 -1
  38. package/lib-es/components/SearchContent/SearchContent.js +4 -3
  39. package/lib-es/components/SearchContent/SearchContent.js.map +1 -1
  40. package/lib-es/system-components/EditorMode/helpers/add-editor-mode-event-listeners.js +0 -1
  41. package/lib-es/system-components/EditorMode/helpers/add-editor-mode-event-listeners.js.map +1 -1
  42. package/package.json +10 -10
  43. package/src/components/ContentGroup/ContentGroup.js +37 -13
  44. package/src/components/ContentGroup/ContentGroupAccordion.js +163 -0
  45. package/src/components/ContentGroup/constants.js +14 -1
  46. package/src/components/ContentGroup/helpers/get-structured-data-properties.js +36 -0
  47. package/src/components/Menu/Menu.js +1 -3
  48. package/src/components/Menu/MenuContext.js +1 -1
  49. package/src/components/MenuItem/MenuItemRender.js +12 -40
  50. package/src/components/MenuItem/helpers/index.js +1 -3
  51. package/src/components/SearchContent/SearchContent.js +6 -5
  52. package/src/system-components/EditorMode/helpers/add-editor-mode-event-listeners.js +3 -1
  53. package/tests/unit/src/components/ContentGroup/ContentGroup.test.js +32 -2
  54. package/tests/unit/src/components/ContentGroup/ContentGroupAccordion.test.js +283 -0
  55. package/tests/unit/src/components/ContentGroup/__snapshots__/ContentGroup.test.js.snap +2 -2
  56. package/tests/unit/src/components/ContentGroup/__snapshots__/ContentGroupAccordion.test.js.snap +243 -0
  57. package/tests/unit/src/components/ContentGroup/helpers/get-structured-data-properties.test.js +105 -0
  58. package/tests/unit/src/components/MenuItem/MenuItem.test.js +0 -5
  59. package/tests/unit/src/components/MenuItem/MenuItemRender.test.js +3 -11
  60. package/lib/components/MenuItem/helpers/has-active-child.js +0 -19
  61. package/lib/components/MenuItem/helpers/has-active-child.js.map +0 -1
  62. package/lib/components/MenuItem/helpers/isUrlPathMatch.js +0 -18
  63. package/lib/components/MenuItem/helpers/isUrlPathMatch.js.map +0 -1
  64. package/lib-es/components/MenuItem/helpers/has-active-child.js +0 -5
  65. package/lib-es/components/MenuItem/helpers/has-active-child.js.map +0 -1
  66. package/lib-es/components/MenuItem/helpers/isUrlPathMatch.js +0 -8
  67. package/lib-es/components/MenuItem/helpers/isUrlPathMatch.js.map +0 -1
  68. package/src/components/MenuItem/helpers/has-active-child.js +0 -10
  69. package/src/components/MenuItem/helpers/isUrlPathMatch.js +0 -10
  70. package/tests/unit/src/components/MenuItem/helpers/constants.js +0 -73
  71. package/tests/unit/src/components/MenuItem/helpers/has-active-child.test.js +0 -35
  72. package/tests/unit/src/components/MenuItem/helpers/is-url-path-match.test.js +0 -53
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blaze-cms/react-page-builder",
3
- "version": "0.146.0-node18-core-styles-tooltips.20",
3
+ "version": "0.146.0-node18-tooltips.19",
4
4
  "description": "Blaze react page builder",
5
5
  "main": "lib/index.js",
6
6
  "module": "lib-es/index.js",
@@ -27,15 +27,15 @@
27
27
  },
28
28
  "license": "GPL-3.0",
29
29
  "dependencies": {
30
- "@blaze-cms/core-auth-ui": "0.146.0-node18-core-styles-tooltips.15",
31
- "@blaze-cms/core-errors": "0.146.0-node18-core-styles-tooltips.3",
32
- "@blaze-cms/core-errors-ui": "0.146.0-node18-core-styles-tooltips.3",
30
+ "@blaze-cms/core-auth-ui": "0.146.0-node18-tooltips.4",
31
+ "@blaze-cms/core-errors": "0.146.0-node18-tooltips.0",
32
+ "@blaze-cms/core-errors-ui": "0.146.0-node18-tooltips.0",
33
33
  "@blaze-cms/image-cdn-react": "0.3.0-alpha.7",
34
- "@blaze-cms/nextjs-components": "0.146.0-node18-core-styles-tooltips.3",
35
- "@blaze-cms/plugin-search-ui": "0.146.0-node18-core-styles-tooltips.19",
36
- "@blaze-cms/setup-ui": "0.146.0-node18-core-styles-tooltips.3",
37
- "@blaze-cms/utils": "0.146.0-node18-core-styles-tooltips.15",
38
- "@blaze-cms/utils-handlebars": "0.146.0-node18-core-styles-tooltips.3",
34
+ "@blaze-cms/nextjs-components": "0.146.0-node18-tooltips.0",
35
+ "@blaze-cms/plugin-search-ui": "0.146.0-node18-tooltips.11",
36
+ "@blaze-cms/setup-ui": "0.146.0-node18-tooltips.0",
37
+ "@blaze-cms/utils": "0.146.0-node18-tooltips.4",
38
+ "@blaze-cms/utils-handlebars": "0.146.0-node18-tooltips.0",
39
39
  "@blaze-react/badge": "^0.7.0",
40
40
  "@blaze-react/breadcrumb": "0.8.0-alpha.60",
41
41
  "@blaze-react/button": "0.8.0-alpha.82",
@@ -92,5 +92,5 @@
92
92
  "lib/*",
93
93
  "lib-es/*"
94
94
  ],
95
- "gitHead": "9cc20b1a43c13d866f03c6829f626813c5851dd4"
95
+ "gitHead": "2aed24333051ddcd4c2bec869a9df5835362d4c9"
96
96
  }
@@ -1,29 +1,53 @@
1
- import React from 'react';
1
+ import React, { Fragment, useMemo } from 'react';
2
2
  import PropTypes from 'prop-types';
3
+ import { CONTENT_GROUP_TYPES } from './constants';
3
4
  import ContentGroupTabs from './ContentGroupTabs';
5
+ import ContentGroupAccordion from './ContentGroupAccordion';
4
6
  import { getSectionsData } from './helpers';
7
+ import getStructuredDataProperties from './helpers/get-structured-data-properties';
8
+
9
+ const VALID_TYPES = Object.values(CONTENT_GROUP_TYPES);
10
+
11
+ const ContentGroup = ({ contentType, isFaqContent, children, ...props }) => {
12
+ const groupSections = children?.props?.children?.[1];
13
+
14
+ const GroupComponent = useMemo(
15
+ () =>
16
+ contentType === CONTENT_GROUP_TYPES.ACCORDION ? ContentGroupAccordion : ContentGroupTabs,
17
+ [contentType]
18
+ );
19
+ const WrapperComponent = useMemo(() => (!isFaqContent ? Fragment : 'div'), [isFaqContent]);
20
+
21
+ const { topWrapperProps, ...schemaProperties } = useMemo(
22
+ () => getStructuredDataProperties(isFaqContent),
23
+ [isFaqContent]
24
+ );
25
+
26
+ if (!groupSections || !groupSections.length || !VALID_TYPES.includes(contentType)) return '';
5
27
 
6
- const ContentGroup = ({ contentType, children, ...props }) => {
7
- const groupSections = children?.props?.children[1];
8
- if (!groupSections || !groupSections.length) return '';
9
- if (contentType !== 'tab' && contentType !== 'sidepanel') return '';
10
28
  const sectionsData = getSectionsData(groupSections);
11
29
 
12
30
  return (
13
- <ContentGroupTabs
14
- contentType={contentType}
15
- groupSections={groupSections}
16
- sectionsData={sectionsData}
17
- {...props}
18
- />
31
+ <WrapperComponent {...topWrapperProps}>
32
+ <GroupComponent
33
+ contentType={contentType}
34
+ groupSections={groupSections}
35
+ sectionsData={sectionsData}
36
+ {...props}
37
+ {...schemaProperties}
38
+ />
39
+ </WrapperComponent>
19
40
  );
20
41
  };
21
42
 
22
43
  ContentGroup.propTypes = {
23
44
  children: PropTypes.object.isRequired,
24
- contentType: PropTypes.string.isRequired
45
+ contentType: PropTypes.oneOf(VALID_TYPES).isRequired,
46
+ isFaqContent: PropTypes.bool
25
47
  };
26
48
 
27
- ContentGroup.defaultProps = {};
49
+ ContentGroup.defaultProps = {
50
+ isFaqContent: false
51
+ };
28
52
 
29
53
  export default ContentGroup;
@@ -0,0 +1,163 @@
1
+ import React, { useEffect, useMemo, useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import classnames from 'classnames';
4
+ import { useRouter } from 'next/router';
5
+ import { VscChevronUp, VscChevronDown } from 'react-icons/vsc';
6
+ import { CONTENT_GROUP, OPEN_STATES } from './constants';
7
+
8
+ const ContentGroupAccordion = ({
9
+ name,
10
+ groupSections,
11
+ sectionsData,
12
+ VariantComponent,
13
+ openState,
14
+ groupSectionProps,
15
+ groupSectionTitleProps,
16
+ groupContentWrapperProps,
17
+ groupContentProps
18
+ }) => {
19
+ const router = useRouter();
20
+ const { asPath } = router;
21
+
22
+ const sectionPairs = useMemo(
23
+ () =>
24
+ sectionsData.map(([sectionName, sectionLabel]) => ({
25
+ sectionName,
26
+ sectionLabel,
27
+ panelId: `${sectionName}-panel`
28
+ })),
29
+ [sectionsData]
30
+ );
31
+
32
+ const sectionNames = useMemo(() => sectionPairs.map(p => p.sectionName), [sectionPairs]);
33
+
34
+ const baseInitialOpen = useMemo(
35
+ () => {
36
+ if (openState === OPEN_STATES.ALL_OPEN) return new Set(sectionNames);
37
+ if (openState === OPEN_STATES.ALL_CLOSED && sectionNames.length) return new Set();
38
+
39
+ return new Set([sectionNames[0]]);
40
+ },
41
+ [openState, sectionNames]
42
+ );
43
+
44
+ const [openSet, setOpenSet] = useState(baseInitialOpen);
45
+
46
+ useEffect(
47
+ () => {
48
+ const rawHash = (asPath.split('#')[1] || '').trim();
49
+ if (rawHash && sectionNames.includes(rawHash) && !openSet.has(rawHash)) {
50
+ setOpenSet(prev => {
51
+ const next = new Set(prev);
52
+ next.add(rawHash);
53
+ return next;
54
+ });
55
+ }
56
+ },
57
+ // eslint-disable-next-line react-hooks/exhaustive-deps
58
+ [asPath, sectionNames]
59
+ );
60
+
61
+ const WrapperComponent = VariantComponent || 'div';
62
+
63
+ const baseClass = `${CONTENT_GROUP}-accordion`;
64
+ const itemsClass = `${baseClass}__items`;
65
+ const itemClass = `${baseClass}__item`;
66
+ const headerBtnClass = `${baseClass}__header-btn`;
67
+ const panelClass = `${baseClass}__panel`;
68
+ const headingClass = `${baseClass}__heading`;
69
+ const titleClass = `${baseClass}__title`;
70
+ const chevronClass = `${baseClass}__chevron`;
71
+ const chevronIconClass = `${baseClass}__chevron-icon`;
72
+
73
+ const onToggle = sectionName => {
74
+ setOpenSet(prev => {
75
+ const next = new Set(prev);
76
+ const willOpen = !next.has(sectionName);
77
+
78
+ if (willOpen) next.add(sectionName);
79
+ else next.delete(sectionName);
80
+
81
+ const baseUrl = asPath.split('#')[0];
82
+ const newUrl = willOpen ? `${baseUrl}#${sectionName}` : baseUrl;
83
+
84
+ router.replace(`/Resolver`, newUrl, { shallow: true, scroll: false });
85
+
86
+ return next;
87
+ });
88
+ };
89
+
90
+ return (
91
+ <div className={baseClass}>
92
+ <div className={itemsClass} aria-label={name}>
93
+ {groupSections.map((groupSection, index) => {
94
+ const { sectionName, sectionLabel, panelId } = sectionPairs[index];
95
+ const isOpen = openSet.has(sectionName);
96
+
97
+ return (
98
+ <div
99
+ key={sectionName}
100
+ className={classnames(itemClass, { [`${itemClass}--open`]: isOpen })}
101
+ {...groupSectionProps}>
102
+ <h3 className={headingClass}>
103
+ <button
104
+ id={sectionName}
105
+ type="button"
106
+ className={classnames(headerBtnClass, { [`${headerBtnClass}--open`]: isOpen })}
107
+ aria-expanded={isOpen}
108
+ aria-controls={panelId}
109
+ onClick={() => onToggle(sectionName)}>
110
+ <span className={titleClass} {...groupSectionTitleProps}>
111
+ {sectionLabel}
112
+ </span>
113
+
114
+ <span aria-hidden="true" className={chevronClass}>
115
+ {isOpen ? (
116
+ <VscChevronUp className={chevronIconClass} />
117
+ ) : (
118
+ <VscChevronDown className={chevronIconClass} />
119
+ )}
120
+ </span>
121
+ </button>
122
+ </h3>
123
+
124
+ <WrapperComponent
125
+ id={panelId}
126
+ role="region"
127
+ aria-labelledby={sectionName}
128
+ className={panelClass}
129
+ hidden={!isOpen}
130
+ data-section={sectionName}
131
+ {...groupContentWrapperProps}>
132
+ <div {...groupContentProps}>{groupSection}</div>
133
+ </WrapperComponent>
134
+ </div>
135
+ );
136
+ })}
137
+ </div>
138
+ </div>
139
+ );
140
+ };
141
+
142
+ ContentGroupAccordion.propTypes = {
143
+ name: PropTypes.string.isRequired,
144
+ groupSections: PropTypes.array.isRequired,
145
+ sectionsData: PropTypes.array.isRequired,
146
+ VariantComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.elementType]),
147
+ openState: PropTypes.oneOf(Object.values(OPEN_STATES)),
148
+ groupSectionProps: PropTypes.object,
149
+ groupSectionTitleProps: PropTypes.object,
150
+ groupContentWrapperProps: PropTypes.object,
151
+ groupContentProps: PropTypes.object
152
+ };
153
+
154
+ ContentGroupAccordion.defaultProps = {
155
+ VariantComponent: null,
156
+ openState: OPEN_STATES.ALL_CLOSED,
157
+ groupSectionProps: {},
158
+ groupSectionTitleProps: {},
159
+ groupContentWrapperProps: {},
160
+ groupContentProps: {}
161
+ };
162
+
163
+ export default ContentGroupAccordion;
@@ -3,5 +3,18 @@ const TABS = 'tabs';
3
3
  const SIDEPANEL = 'sidepanel';
4
4
  const CONTENT_GROUP = 'content-group';
5
5
  const PANEL = 'panel';
6
+ const FAQ_PAGE = 'https://schema.org/FAQPage';
6
7
 
7
- export { TAB, TABS, SIDEPANEL, CONTENT_GROUP, PANEL };
8
+ const CONTENT_GROUP_TYPES = Object.freeze({
9
+ TAB: 'tab',
10
+ SIDEPANEL: 'sidepanel',
11
+ ACCORDION: 'accordion'
12
+ });
13
+
14
+ const OPEN_STATES = Object.freeze({
15
+ FIRST_OPEN: 'first_open',
16
+ ALL_CLOSED: 'all_closed',
17
+ ALL_OPEN: 'all_open'
18
+ });
19
+
20
+ export { TAB, TABS, SIDEPANEL, CONTENT_GROUP, PANEL, CONTENT_GROUP_TYPES, OPEN_STATES, FAQ_PAGE };
@@ -0,0 +1,36 @@
1
+ const getStructuredDataProperties = isFaqContent => {
2
+ const properties = {
3
+ topWrapperProps: {},
4
+ groupSectionProps: {},
5
+ groupSectionTitleProps: {},
6
+ groupContentWrapperProps: {},
7
+ groupContentProps: {}
8
+ };
9
+ if (!isFaqContent) return properties;
10
+
11
+ properties.topWrapperProps = {
12
+ itemScope: true,
13
+ itemType: 'https://schema.org/FAQPage'
14
+ };
15
+ properties.groupSectionProps = {
16
+ itemScope: true,
17
+ itemProp: 'mainEntity',
18
+ itemType: 'https://schema.org/Question'
19
+ };
20
+ properties.groupSectionTitleProps = {
21
+ itemProp: 'name'
22
+ };
23
+ properties.groupContentWrapperProps = {
24
+ itemScope: true,
25
+ itemProp: 'acceptedAnswer',
26
+ itemType: 'https://schema.org/Answer'
27
+ };
28
+
29
+ properties.groupContentProps = {
30
+ itemProp: 'text'
31
+ };
32
+
33
+ return properties;
34
+ };
35
+
36
+ export default getStructuredDataProperties;
@@ -11,7 +11,6 @@ import BlazeLink from '../BlazeLink';
11
11
  const Menu = ({
12
12
  children,
13
13
  collapse,
14
- openActiveSubmenus,
15
14
  modifier,
16
15
  mobileMenuModifier,
17
16
  mobileMenuChildrenModifier,
@@ -52,7 +51,7 @@ const Menu = ({
52
51
  });
53
52
 
54
53
  return (
55
- <MenuContext.Provider value={{ showMobileMenu, openActiveSubmenus }}>
54
+ <MenuContext.Provider value={{ showMobileMenu }}>
56
55
  <div className={menuWrapperClasses}>
57
56
  {collapse && (
58
57
  <div className="menu--mobile-wrapper">
@@ -100,7 +99,6 @@ const Menu = ({
100
99
 
101
100
  Menu.propTypes = {
102
101
  collapse: PropTypes.bool.isRequired,
103
- openActiveSubmenus: PropTypes.bool.isRequired,
104
102
  logoOnMobile: PropTypes.bool.isRequired,
105
103
  logoOnDesktop: PropTypes.bool,
106
104
  logoOnMobileUrl: PropTypes.string,
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
2
 
3
- const MenuContext = React.createContext({ showMobileMenu: false, openActiveSubmenus: false });
3
+ const MenuContext = React.createContext({ showMobileMenu: false });
4
4
 
5
5
  export default MenuContext;
@@ -1,23 +1,17 @@
1
- /* eslint-disable jsx-a11y/no-static-element-interactions */
2
1
  import React, { useState, useContext, useEffect } from 'react';
3
2
  import PropTypes from 'prop-types';
4
3
  import { FaChevronDown, FaChevronUp } from 'react-icons/fa';
5
- import classnames from 'classnames';
6
4
  import { useRouter } from 'next/router';
7
5
  import { useStringTemplate } from '@blaze-cms/utils-handlebars';
8
- import { HOVER, MOUSE_ENTER, MOUSE_LEAVE, HIDDEN, CLICK } from '../../constants';
6
+ import { HOVER, MOUSE_ENTER, MOUSE_LEAVE, HIDDEN } from '../../constants';
9
7
  import { hasChildren } from '../../helpers';
10
8
  import BlazeLink from '../BlazeLink';
11
9
  import MenuContext from '../Menu/MenuContext';
12
- import { injectHelperIntoTemplate, isUrlPathMatch, hasActiveChild } from './helpers';
10
+ import { injectHelperIntoTemplate } from './helpers';
13
11
 
14
12
  const MenuItemRender = ({ children, eventType, text, modifier, url, parent }) => {
15
- const router = useRouter();
16
- const { showMobileMenu, openActiveSubmenus } = useContext(MenuContext);
17
-
18
- const isHoverEvent = eventType === HOVER;
19
- const isClickEvent = eventType === CLICK;
20
-
13
+ const [displayChildren, setDisplayChildren] = useState(false);
14
+ const { showMobileMenu } = useContext(MenuContext);
21
15
  const {
22
16
  loading: loadingText,
23
17
  data: [textToUse]
@@ -27,24 +21,14 @@ const MenuItemRender = ({ children, eventType, text, modifier, url, parent }) =>
27
21
  data: [urlToUse]
28
22
  } = useStringTemplate(parent, [injectHelperIntoTemplate(url, 'url_encode')]);
29
23
 
30
- const isActive = router ? isUrlPathMatch(router.asPath, urlToUse) : false;
31
- const isActiveParent = router ? hasActiveChild(router.asPath, children) : false;
32
- const shouldPreOpen = openActiveSubmenus && isActiveParent && isClickEvent;
24
+ const router = useRouter();
25
+ const isHoverEvent = eventType === HOVER;
26
+ const childrenDisplayClass = displayChildren ? '' : HIDDEN;
33
27
  const hasValidChildren = hasChildren(children);
34
28
 
35
- const [displayChildren, setDisplayChildren] = useState(shouldPreOpen);
36
-
37
29
  useEffect(() => {
38
- if (!showMobileMenu && !shouldPreOpen) setDisplayChildren(false);
39
- }, [
40
- children,
41
- isClickEvent,
42
- loadingUrl,
43
- openActiveSubmenus,
44
- router,
45
- shouldPreOpen,
46
- showMobileMenu
47
- ]);
30
+ if (!showMobileMenu) setDisplayChildren(false);
31
+ }, [showMobileMenu]);
48
32
 
49
33
  useEffect(() => {
50
34
  if (isHoverEvent) {
@@ -54,8 +38,6 @@ const MenuItemRender = ({ children, eventType, text, modifier, url, parent }) =>
54
38
 
55
39
  if (loadingUrl || loadingText) return '';
56
40
 
57
- const childrenDisplayClass = displayChildren ? '' : HIDDEN;
58
-
59
41
  const handleItemEvent = ({ type }) => {
60
42
  if (isHoverEvent) {
61
43
  if (type === MOUSE_ENTER) {
@@ -73,25 +55,15 @@ const MenuItemRender = ({ children, eventType, text, modifier, url, parent }) =>
73
55
  }
74
56
  };
75
57
 
76
- const menuItemLinkClassname = classnames('menu--item--link', {
77
- 'menu--item--link--active': isActive,
78
- 'menu--item--link--active-parent': isActiveParent
79
- });
80
-
81
58
  return (
82
59
  <li className={modifier} onMouseEnter={handleItemEvent} onMouseLeave={handleItemEvent}>
83
60
  <div
84
- className={menuItemLinkClassname}
61
+ className="menu--item--link"
85
62
  onClick={handleMobileClick}
86
63
  role={!urlToUse && hasValidChildren ? 'button' : undefined}
87
64
  tabIndex={!urlToUse && hasValidChildren ? 0 : undefined}>
88
- {urlToUse ? (
89
- <BlazeLink href={urlToUse}>{textToUse}</BlazeLink>
90
- ) : (
91
- <span role="button" onClick={handleItemEvent}>
92
- {textToUse}
93
- </span>
94
- )}
65
+ {urlToUse ? <BlazeLink href={urlToUse}>{textToUse}</BlazeLink> : <span>{textToUse}</span>}
66
+
95
67
  {hasValidChildren && (
96
68
  <i
97
69
  role="button"
@@ -1,5 +1,3 @@
1
1
  import injectHelperIntoTemplate from './inject-helper-into-template';
2
- import isUrlPathMatch from './isUrlPathMatch';
3
- import hasActiveChild from './has-active-child';
4
2
 
5
- export { injectHelperIntoTemplate, isUrlPathMatch, hasActiveChild };
3
+ export { injectHelperIntoTemplate };
@@ -18,6 +18,7 @@ const SearchContent = props => {
18
18
  searchUrl,
19
19
  navigateToSingleSearchResult
20
20
  } = props;
21
+
21
22
  const searchContentRef = useRef(null);
22
23
  const [collapsed, setCollapsed] = useState(collapsedSearch);
23
24
  const [showResults, setShowResults] = useState(false);
@@ -43,7 +44,6 @@ const SearchContent = props => {
43
44
  if (collapsedSearch) setCollapsed(true);
44
45
  };
45
46
  router.events.on('routeChangeStart', handleRouteChange);
46
-
47
47
  return () => {
48
48
  router.events.off('routeChangeStart', handleRouteChange);
49
49
  };
@@ -53,15 +53,16 @@ const SearchContent = props => {
53
53
  const handleClickOutside = event => {
54
54
  if (searchContentRef.current && !searchContentRef.current.contains(event.target)) {
55
55
  setShowResults(false);
56
- if (collapsedSearch) {
57
- setCollapsed(true);
58
- setSearchTerm('');
56
+ setSearchTerm('');
57
+ if (collapsedSearch) setCollapsed(true);
58
+
59
+ if (document.activeElement && document.activeElement instanceof HTMLElement) {
60
+ document.activeElement.blur();
59
61
  }
60
62
  }
61
63
  };
62
64
 
63
65
  document.addEventListener('click', handleClickOutside);
64
-
65
66
  return () => {
66
67
  document.removeEventListener('click', handleClickOutside);
67
68
  };
@@ -38,8 +38,10 @@ const addEditorModeEventListeners = ({ ref, name, adminHref, type, setLogoProps
38
38
  e.preventDefault();
39
39
  window.open(`${adminHref}#${componentId}`, adminHref);
40
40
  };
41
+
41
42
  const mouseenter = e => {
42
43
  e.stopPropagation();
44
+
43
45
  document.querySelectorAll(`.${EDITOR_MODE_HIGHLIGHT_CLASS}`).forEach(node => {
44
46
  node.classList.remove(EDITOR_MODE_HIGHLIGHT_CLASS);
45
47
  });
@@ -52,6 +54,7 @@ const addEditorModeEventListeners = ({ ref, name, adminHref, type, setLogoProps
52
54
  onClick: click
53
55
  });
54
56
  };
57
+
55
58
  const mouseleave = e => {
56
59
  e.stopPropagation();
57
60
  if (e.toElement) {
@@ -61,7 +64,6 @@ const addEditorModeEventListeners = ({ ref, name, adminHref, type, setLogoProps
61
64
  return;
62
65
  }
63
66
  }
64
- childNode.classList.remove(EDITOR_MODE_HIGHLIGHT_CLASS);
65
67
  setLogoProps(null);
66
68
  };
67
69
 
@@ -1,16 +1,46 @@
1
1
  /**
2
2
  * @jest-environment jsdom
3
3
  */
4
+ import React from 'react';
4
5
  import { render } from '@blaze-cms/tools/test-helpers/test-functions';
5
6
  import ContentGroup from '../../../../../src/components/ContentGroup/ContentGroup';
6
7
 
8
+ jest.mock('../../../../../src/components/ContentGroup/ContentGroupTabs', () => ({
9
+ __esModule: true,
10
+ default: ({ contentType }) => <div data-testid="content-group-tabs">Inner: {contentType}</div>
11
+ }));
12
+
13
+ const TestChild = ({ children }) => <div>{children}</div>;
14
+ const buildChildren = (sectionsCount = 2) => (
15
+ <div>
16
+ <div>Header</div>
17
+ {Array.from({ length: sectionsCount }).map((_, i) => (
18
+ <TestChild key={`s${i + 1}`}>Section {i + 1}</TestChild>
19
+ ))}
20
+ </div>
21
+ );
22
+
7
23
  describe('ContentGroup component', () => {
8
- it('should match snapshot and render based on contentType prop', () => {
9
- const { asFragment: tabContentGroup } = render(ContentGroup, { contentType: 'tab' });
24
+ it('matches snapshots for tab and sidepanel', () => {
25
+ const { asFragment: tabContentGroup } = render(ContentGroup, {
26
+ contentType: 'tab'
27
+ });
10
28
  const { asFragment: sidepanelContentGroup } = render(ContentGroup, {
11
29
  contentType: 'sidepanel'
12
30
  });
31
+
13
32
  expect(tabContentGroup()).toMatchSnapshot();
14
33
  expect(sidepanelContentGroup()).toMatchSnapshot();
15
34
  });
35
+
36
+ it('wraps output in FAQPage schema when isFaqContent is true', () => {
37
+ const { container } = render(ContentGroup, {
38
+ contentType: 'tab',
39
+ isFaqContent: true,
40
+ children: buildChildren()
41
+ });
42
+
43
+ const faqWrapper = container.querySelector('div[itemtype="https://schema.org/FAQPage"]');
44
+ expect(faqWrapper).toBeTruthy();
45
+ });
16
46
  });