diff --git a/packages/strapi-plugin-content-manager/admin/src/components/ComponentInitializer/index.js b/packages/strapi-plugin-content-manager/admin/src/components/ComponentInitializer/index.js index 2e62be4e98..6ab563ac5d 100644 --- a/packages/strapi-plugin-content-manager/admin/src/components/ComponentInitializer/index.js +++ b/packages/strapi-plugin-content-manager/admin/src/components/ComponentInitializer/index.js @@ -11,15 +11,16 @@ const ComponentInitializer = ({ componentUid, isReadOnly, name }) => { const { addNonRepeatableComponentToField } = useDataManager(); return ( - - { - if (!isReadOnly) { - addNonRepeatableComponentToField(name, componentUid); - } - }} - type="button" - /> + { + if (!isReadOnly) { + addNonRepeatableComponentToField(name, componentUid); + } + }} + > + {msg =>

{msg}

}
diff --git a/packages/strapi-plugin-content-manager/admin/src/components/DynamicComponentCard/Wrapper.js b/packages/strapi-plugin-content-manager/admin/src/components/DynamicComponentCard/Wrapper.js index f2b21eac56..cf2822bf23 100644 --- a/packages/strapi-plugin-content-manager/admin/src/components/DynamicComponentCard/Wrapper.js +++ b/packages/strapi-plugin-content-manager/admin/src/components/DynamicComponentCard/Wrapper.js @@ -4,7 +4,7 @@ const Wrapper = styled.div` position: relative; height: 90px; width: 139px !important; - margin-right: 10px; + margin: 0 10px 10px 0; padding: 18px 10px; background-color: #ffffff; color: #919bae; diff --git a/packages/strapi-plugin-content-manager/admin/src/components/DynamicZone/Banner.js b/packages/strapi-plugin-content-manager/admin/src/components/DynamicZone/Banner.js new file mode 100644 index 0000000000..2f23c74a85 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/DynamicZone/Banner.js @@ -0,0 +1,35 @@ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Carret } from '@buffetjs/icons'; +import BannerWrapper from './BannerWrapper'; + +/* eslint-disable jsx-a11y/no-static-element-interactions */ + +const Banner = ({ category, isOpen, onClickToggle, isFirst }) => { + return ( + +
+ +
+
{category}
+
+ ); +}; + +Banner.defaultProps = { + isFirst: false, + isOpen: false, + onClickToggle: () => {}, +}; + +Banner.propTypes = { + category: PropTypes.string.isRequired, + isFirst: PropTypes.bool, + isOpen: PropTypes.bool, + onClickToggle: PropTypes.func, +}; + +Banner.displayName = 'Banner'; + +export default Banner; diff --git a/packages/strapi-plugin-content-manager/admin/src/components/DynamicZone/BannerWrapper.js b/packages/strapi-plugin-content-manager/admin/src/components/DynamicZone/BannerWrapper.js new file mode 100644 index 0000000000..8992b2d027 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/DynamicZone/BannerWrapper.js @@ -0,0 +1,78 @@ +import styled from 'styled-components'; + +/* eslint-disable */ + +const BannerWrapper = styled.button` + display: flex; + height: 36px; + width: 100%; + padding: 0 15px; + border-bottom: 0; + border: 1px solid rgba(227, 233, 243, 0.75); + background-color: ${({ theme }) => theme.main.colors.white}; + font-size: ${({ theme }) => theme.main.sizes.fonts.md}; + font-weight: ${({ theme }) => theme.main.fontWeights.semiBold}; + cursor: pointer; + + &:focus { + outline: 0; + } + + .img-wrapper { + width: 19px; + height: 19px; + margin-right: 19px; + border-radius: 50%; + background-color: ${({ theme }) => theme.main.colors.mediumGrey}; + text-align: center; + } + .label { + text-transform: capitalize; + } + + svg { + path { + fill: ${({ theme }) => theme.main.colors.leftMenu['link-color']} !important; + } + } + + -webkit-font-smoothing: antialiased; + + > div { + align-self: center; + margin-top: -2px; + } + + ${({ isFirst, theme }) => { + if (isFirst) { + return ` + border-top-right-radius: ${theme.main.sizes.borderRadius}; + border-top-left-radius: ${theme.main.sizes.borderRadius}; + `; + } + }} + + ${({ isOpen, theme }) => { + if (isOpen) { + return ` + border: 1px solid ${theme.main.colors.darkBlue}; + background-color: ${theme.main.colors.lightBlue}; + color: ${theme.main.colors.mediumBlue}; + font-weight: ${theme.main.fontWeights.bold}; + + .img-wrapper { + background-color: ${theme.main.colors.darkBlue}; + transform: rotate(180deg); + } + + svg { + path { + fill: ${theme.main.colors.mediumBlue} !important; + } + } + `; + } + }} +`; + +export default BannerWrapper; diff --git a/packages/strapi-plugin-content-manager/admin/src/components/DynamicZone/CategoryItem.js b/packages/strapi-plugin-content-manager/admin/src/components/DynamicZone/CategoryItem.js new file mode 100644 index 0000000000..8fbdc87d27 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/DynamicZone/CategoryItem.js @@ -0,0 +1,65 @@ +import React, { useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; +import { Collapse } from 'reactstrap'; +import Banner from './Banner'; +import ComponentsList from './ComponentsList'; +import DynamicComponentCard from '../DynamicComponentCard'; + +const CategoryItem = ({ + category, + components, + isOpen, + isFirst, + onClickToggle, + onClickComponent, +}) => { + const [showComponents, setShowComponents] = useState(false); + useEffect(() => { + if (isOpen) { + setShowComponents(true); + } + }, [isOpen]); + + const handleExited = () => setShowComponents(false); + + return ( + <> + + + {showComponents && ( + + {components.map(component => { + const { + info: { icon, name: friendlyName }, + componentUid, + } = component; + + return ( + { + onClickComponent(componentUid); + }} + /> + ); + })} + + )} + + + ); +}; + +CategoryItem.propTypes = { + category: PropTypes.string.isRequired, + components: PropTypes.array.isRequired, + isOpen: PropTypes.bool.isRequired, + isFirst: PropTypes.bool.isRequired, + onClickToggle: PropTypes.func.isRequired, + onClickComponent: PropTypes.func.isRequired, +}; + +export default CategoryItem; diff --git a/packages/strapi-plugin-content-manager/admin/src/components/DynamicZone/ComponentsList.js b/packages/strapi-plugin-content-manager/admin/src/components/DynamicZone/ComponentsList.js new file mode 100644 index 0000000000..49997cc806 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/DynamicZone/ComponentsList.js @@ -0,0 +1,9 @@ +import styled from 'styled-components'; + +const ComponentsList = styled.div` + padding-top: 10px; + padding-left: 15px; + padding-right: 15px; +`; + +export default ComponentsList; diff --git a/packages/strapi-plugin-content-manager/admin/src/components/DynamicZone/ComponentsPicker.js b/packages/strapi-plugin-content-manager/admin/src/components/DynamicZone/ComponentsPicker.js index 63b35ee41d..afd96ccca8 100644 --- a/packages/strapi-plugin-content-manager/admin/src/components/DynamicZone/ComponentsPicker.js +++ b/packages/strapi-plugin-content-manager/admin/src/components/DynamicZone/ComponentsPicker.js @@ -3,8 +3,6 @@ import styled from 'styled-components'; /* eslint-disable indent */ const ComponentsPicker = styled.div` overflow: hidden; - max-height: 0; - transition: max-height 0.2s ease-out; > div { margin-top: 15px; @@ -12,14 +10,8 @@ const ComponentsPicker = styled.div` background-color: #f2f3f4; } - ${({ isOpen }) => - isOpen && - ` - max-height: 260px; - `} - .componentPickerTitle { - margin-bottom: 15px; + margin-bottom: 10px; color: #919bae; font-weight: 600; font-size: 13px; @@ -27,7 +19,11 @@ const ComponentsPicker = styled.div` } .componentsList { display: flex; - overflow-x: auto; + flex-wrap: wrap; + } + + .categoriesList { + padding-bottom: 4px; } `; diff --git a/packages/strapi-plugin-content-manager/admin/src/components/DynamicZone/index.js b/packages/strapi-plugin-content-manager/admin/src/components/DynamicZone/index.js index 369b761126..3162a6350d 100644 --- a/packages/strapi-plugin-content-manager/admin/src/components/DynamicZone/index.js +++ b/packages/strapi-plugin-content-manager/admin/src/components/DynamicZone/index.js @@ -1,13 +1,13 @@ import React, { memo, useCallback, useMemo, useState } from 'react'; -import { get } from 'lodash'; +import { get, groupBy } from 'lodash'; import isEqual from 'react-fast-compare'; import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; import { Arrow } from '@buffetjs/icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Collapse } from 'reactstrap'; import pluginId from '../../pluginId'; import useEditView from '../../hooks/useEditView'; -import DynamicComponentCard from '../DynamicComponentCard'; import FieldComponent from '../FieldComponent'; import NotAllowedInput from '../NotAllowedInput'; import connect from './utils/connect'; @@ -20,6 +20,7 @@ import DynamicZoneWrapper from './DynamicZoneWrapper'; import Label from './Label'; import RoundCTA from './RoundCTA'; import Wrapper from './Wrapper'; +import CategoryItem from './CategoryItem'; /* eslint-disable react/no-array-index-key */ @@ -42,16 +43,26 @@ const DynamicZone = ({ }) => { const [isOpen, setIsOpen] = useState(false); + const [categoryToOpen, setCategoryToOpen] = useState(''); + const { components } = useEditView(); + const getDynamicComponent = useCallback( + componentUid => { + const component = components.find(compo => compo.uid === componentUid); + + return component; + }, + [components] + ); + const getDynamicComponentSchemaData = useCallback( componentUid => { - const component = components.find(compo => compo.uid === componentUid); - const { schema } = component; + const { schema } = getDynamicComponent(componentUid); return schema; }, - [components] + [getDynamicComponent] ); const getDynamicComponentInfos = useCallback( @@ -78,6 +89,28 @@ const DynamicZone = ({ [layout, name] ); + const dynamicComponentCategories = useMemo(() => { + const componentsWithInfos = dynamicZoneAvailableComponents.map(componentUid => { + const { + category, + schema: { info }, + } = getDynamicComponent(componentUid); + + return { componentUid, category, info }; + }); + + return groupBy(componentsWithInfos, 'category'); + }, [dynamicZoneAvailableComponents, getDynamicComponent]); + + const handleClickToggle = useCallback( + categoryName => { + const nextCategoryToOpen = categoryToOpen === categoryName ? '' : categoryName; + + setCategoryToOpen(nextCategoryToOpen); + }, + [categoryToOpen] + ); + const metas = useMemo(() => get(layout, ['metadatas', name, 'edit'], {}), [layout, name]); const dynamicDisplayedComponentsLength = dynamicDisplayedComponents.length; const missingComponentNumber = min - dynamicDisplayedComponentsLength; @@ -202,32 +235,38 @@ const DynamicZone = ({ values={{ componentName: name }} /> - -
-

- -

-
- {dynamicZoneAvailableComponents.map(componentUid => { - const { icon, name: friendlyName } = getDynamicComponentInfos(componentUid); + + +
+

+ +

+
+ {Object.keys(dynamicComponentCategories).map((categoryName, index) => { + const components = dynamicComponentCategories[categoryName]; - return ( - { - setIsOpen(false); - const shouldCheckErrors = hasError; - addComponentToDynamicZone(name, componentUid, shouldCheckErrors); - }} - /> - ); - })} + return ( + { + handleClickToggle(categoryName); + }} + onClickComponent={componentUid => { + setCategoryToOpen(''); + const shouldCheckErrors = hasError; + addComponentToDynamicZone(name, componentUid, shouldCheckErrors); + }} + /> + ); + })} +
-
- + + ) : (