mirror of
https://github.com/strapi/strapi.git
synced 2025-08-03 14:28:40 +00:00
Merge branch 'master' into feature/german-translations-update
This commit is contained in:
commit
c38e1c4da8
@ -11,15 +11,16 @@ const ComponentInitializer = ({ componentUid, isReadOnly, name }) => {
|
||||
const { addNonRepeatableComponentToField } = useDataManager();
|
||||
|
||||
return (
|
||||
<NonRepeatableWrapper isEmpty isReadOnly={isReadOnly}>
|
||||
<PlusButton
|
||||
onClick={() => {
|
||||
if (!isReadOnly) {
|
||||
addNonRepeatableComponentToField(name, componentUid);
|
||||
}
|
||||
}}
|
||||
type="button"
|
||||
/>
|
||||
<NonRepeatableWrapper
|
||||
isEmpty
|
||||
isReadOnly={isReadOnly}
|
||||
onClick={() => {
|
||||
if (!isReadOnly) {
|
||||
addNonRepeatableComponentToField(name, componentUid);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<PlusButton type="button" />
|
||||
<FormattedMessage id={`${pluginId}.components.empty-repeatable`}>
|
||||
{msg => <P style={{ paddingTop: 78 }}>{msg}</P>}
|
||||
</FormattedMessage>
|
||||
|
@ -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;
|
||||
|
@ -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 (
|
||||
<BannerWrapper type="button" isFirst={isFirst} isOpen={isOpen} onClick={onClickToggle}>
|
||||
<div className="img-wrapper">
|
||||
<Carret />
|
||||
</div>
|
||||
<div className="label">{category}</div>
|
||||
</BannerWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
@ -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;
|
@ -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 (
|
||||
<>
|
||||
<Banner onClickToggle={onClickToggle} isFirst={isFirst} isOpen={isOpen} category={category} />
|
||||
<Collapse isOpen={isOpen} onExited={handleExited}>
|
||||
{showComponents && (
|
||||
<ComponentsList className="componentsList">
|
||||
{components.map(component => {
|
||||
const {
|
||||
info: { icon, name: friendlyName },
|
||||
componentUid,
|
||||
} = component;
|
||||
|
||||
return (
|
||||
<DynamicComponentCard
|
||||
key={componentUid}
|
||||
componentUid={componentUid}
|
||||
friendlyName={friendlyName}
|
||||
icon={icon}
|
||||
onClick={() => {
|
||||
onClickComponent(componentUid);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</ComponentsList>
|
||||
)}
|
||||
</Collapse>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
@ -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;
|
@ -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;
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -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 }}
|
||||
/>
|
||||
</div>
|
||||
<ComponentsPicker isOpen={isOpen}>
|
||||
<div>
|
||||
<p className="componentPickerTitle">
|
||||
<FormattedMessage id={`${pluginId}.components.DynamicZone.pick-compo`} />
|
||||
</p>
|
||||
<div className="componentsList">
|
||||
{dynamicZoneAvailableComponents.map(componentUid => {
|
||||
const { icon, name: friendlyName } = getDynamicComponentInfos(componentUid);
|
||||
<Collapse isOpen={isOpen}>
|
||||
<ComponentsPicker>
|
||||
<div>
|
||||
<p className="componentPickerTitle">
|
||||
<FormattedMessage id={`${pluginId}.components.DynamicZone.pick-compo`} />
|
||||
</p>
|
||||
<div className="categoriesList">
|
||||
{Object.keys(dynamicComponentCategories).map((categoryName, index) => {
|
||||
const components = dynamicComponentCategories[categoryName];
|
||||
|
||||
return (
|
||||
<DynamicComponentCard
|
||||
key={componentUid}
|
||||
componentUid={componentUid}
|
||||
friendlyName={friendlyName}
|
||||
icon={icon}
|
||||
onClick={() => {
|
||||
setIsOpen(false);
|
||||
const shouldCheckErrors = hasError;
|
||||
addComponentToDynamicZone(name, componentUid, shouldCheckErrors);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
return (
|
||||
<CategoryItem
|
||||
key={categoryName}
|
||||
category={categoryName}
|
||||
components={components}
|
||||
isOpen={categoryToOpen === categoryName}
|
||||
isFirst={index === 0}
|
||||
onClickToggle={() => {
|
||||
handleClickToggle(categoryName);
|
||||
}}
|
||||
onClickComponent={componentUid => {
|
||||
setCategoryToOpen('');
|
||||
const shouldCheckErrors = hasError;
|
||||
addComponentToDynamicZone(name, componentUid, shouldCheckErrors);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ComponentsPicker>
|
||||
</ComponentsPicker>
|
||||
</Collapse>
|
||||
</Wrapper>
|
||||
) : (
|
||||
<BaselineAlignement top="9px" />
|
||||
|
Loading…
x
Reference in New Issue
Block a user