mirror of
https://github.com/strapi/strapi.git
synced 2025-08-05 15:29:04 +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();
|
const { addNonRepeatableComponentToField } = useDataManager();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NonRepeatableWrapper isEmpty isReadOnly={isReadOnly}>
|
<NonRepeatableWrapper
|
||||||
<PlusButton
|
isEmpty
|
||||||
onClick={() => {
|
isReadOnly={isReadOnly}
|
||||||
if (!isReadOnly) {
|
onClick={() => {
|
||||||
addNonRepeatableComponentToField(name, componentUid);
|
if (!isReadOnly) {
|
||||||
}
|
addNonRepeatableComponentToField(name, componentUid);
|
||||||
}}
|
}
|
||||||
type="button"
|
}}
|
||||||
/>
|
>
|
||||||
|
<PlusButton type="button" />
|
||||||
<FormattedMessage id={`${pluginId}.components.empty-repeatable`}>
|
<FormattedMessage id={`${pluginId}.components.empty-repeatable`}>
|
||||||
{msg => <P style={{ paddingTop: 78 }}>{msg}</P>}
|
{msg => <P style={{ paddingTop: 78 }}>{msg}</P>}
|
||||||
</FormattedMessage>
|
</FormattedMessage>
|
||||||
|
@ -4,7 +4,7 @@ const Wrapper = styled.div`
|
|||||||
position: relative;
|
position: relative;
|
||||||
height: 90px;
|
height: 90px;
|
||||||
width: 139px !important;
|
width: 139px !important;
|
||||||
margin-right: 10px;
|
margin: 0 10px 10px 0;
|
||||||
padding: 18px 10px;
|
padding: 18px 10px;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
color: #919bae;
|
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 */
|
/* eslint-disable indent */
|
||||||
const ComponentsPicker = styled.div`
|
const ComponentsPicker = styled.div`
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
max-height: 0;
|
|
||||||
transition: max-height 0.2s ease-out;
|
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
@ -12,14 +10,8 @@ const ComponentsPicker = styled.div`
|
|||||||
background-color: #f2f3f4;
|
background-color: #f2f3f4;
|
||||||
}
|
}
|
||||||
|
|
||||||
${({ isOpen }) =>
|
|
||||||
isOpen &&
|
|
||||||
`
|
|
||||||
max-height: 260px;
|
|
||||||
`}
|
|
||||||
|
|
||||||
.componentPickerTitle {
|
.componentPickerTitle {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 10px;
|
||||||
color: #919bae;
|
color: #919bae;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
@ -27,7 +19,11 @@ const ComponentsPicker = styled.div`
|
|||||||
}
|
}
|
||||||
.componentsList {
|
.componentsList {
|
||||||
display: flex;
|
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 React, { memo, useCallback, useMemo, useState } from 'react';
|
||||||
import { get } from 'lodash';
|
import { get, groupBy } from 'lodash';
|
||||||
import isEqual from 'react-fast-compare';
|
import isEqual from 'react-fast-compare';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { Arrow } from '@buffetjs/icons';
|
import { Arrow } from '@buffetjs/icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import { Collapse } from 'reactstrap';
|
||||||
import pluginId from '../../pluginId';
|
import pluginId from '../../pluginId';
|
||||||
import useEditView from '../../hooks/useEditView';
|
import useEditView from '../../hooks/useEditView';
|
||||||
import DynamicComponentCard from '../DynamicComponentCard';
|
|
||||||
import FieldComponent from '../FieldComponent';
|
import FieldComponent from '../FieldComponent';
|
||||||
import NotAllowedInput from '../NotAllowedInput';
|
import NotAllowedInput from '../NotAllowedInput';
|
||||||
import connect from './utils/connect';
|
import connect from './utils/connect';
|
||||||
@ -20,6 +20,7 @@ import DynamicZoneWrapper from './DynamicZoneWrapper';
|
|||||||
import Label from './Label';
|
import Label from './Label';
|
||||||
import RoundCTA from './RoundCTA';
|
import RoundCTA from './RoundCTA';
|
||||||
import Wrapper from './Wrapper';
|
import Wrapper from './Wrapper';
|
||||||
|
import CategoryItem from './CategoryItem';
|
||||||
|
|
||||||
/* eslint-disable react/no-array-index-key */
|
/* eslint-disable react/no-array-index-key */
|
||||||
|
|
||||||
@ -42,16 +43,26 @@ const DynamicZone = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
|
const [categoryToOpen, setCategoryToOpen] = useState('');
|
||||||
|
|
||||||
const { components } = useEditView();
|
const { components } = useEditView();
|
||||||
|
|
||||||
|
const getDynamicComponent = useCallback(
|
||||||
|
componentUid => {
|
||||||
|
const component = components.find(compo => compo.uid === componentUid);
|
||||||
|
|
||||||
|
return component;
|
||||||
|
},
|
||||||
|
[components]
|
||||||
|
);
|
||||||
|
|
||||||
const getDynamicComponentSchemaData = useCallback(
|
const getDynamicComponentSchemaData = useCallback(
|
||||||
componentUid => {
|
componentUid => {
|
||||||
const component = components.find(compo => compo.uid === componentUid);
|
const { schema } = getDynamicComponent(componentUid);
|
||||||
const { schema } = component;
|
|
||||||
|
|
||||||
return schema;
|
return schema;
|
||||||
},
|
},
|
||||||
[components]
|
[getDynamicComponent]
|
||||||
);
|
);
|
||||||
|
|
||||||
const getDynamicComponentInfos = useCallback(
|
const getDynamicComponentInfos = useCallback(
|
||||||
@ -78,6 +89,28 @@ const DynamicZone = ({
|
|||||||
[layout, name]
|
[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 metas = useMemo(() => get(layout, ['metadatas', name, 'edit'], {}), [layout, name]);
|
||||||
const dynamicDisplayedComponentsLength = dynamicDisplayedComponents.length;
|
const dynamicDisplayedComponentsLength = dynamicDisplayedComponents.length;
|
||||||
const missingComponentNumber = min - dynamicDisplayedComponentsLength;
|
const missingComponentNumber = min - dynamicDisplayedComponentsLength;
|
||||||
@ -202,32 +235,38 @@ const DynamicZone = ({
|
|||||||
values={{ componentName: name }}
|
values={{ componentName: name }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ComponentsPicker isOpen={isOpen}>
|
<Collapse isOpen={isOpen}>
|
||||||
<div>
|
<ComponentsPicker>
|
||||||
<p className="componentPickerTitle">
|
<div>
|
||||||
<FormattedMessage id={`${pluginId}.components.DynamicZone.pick-compo`} />
|
<p className="componentPickerTitle">
|
||||||
</p>
|
<FormattedMessage id={`${pluginId}.components.DynamicZone.pick-compo`} />
|
||||||
<div className="componentsList">
|
</p>
|
||||||
{dynamicZoneAvailableComponents.map(componentUid => {
|
<div className="categoriesList">
|
||||||
const { icon, name: friendlyName } = getDynamicComponentInfos(componentUid);
|
{Object.keys(dynamicComponentCategories).map((categoryName, index) => {
|
||||||
|
const components = dynamicComponentCategories[categoryName];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DynamicComponentCard
|
<CategoryItem
|
||||||
key={componentUid}
|
key={categoryName}
|
||||||
componentUid={componentUid}
|
category={categoryName}
|
||||||
friendlyName={friendlyName}
|
components={components}
|
||||||
icon={icon}
|
isOpen={categoryToOpen === categoryName}
|
||||||
onClick={() => {
|
isFirst={index === 0}
|
||||||
setIsOpen(false);
|
onClickToggle={() => {
|
||||||
const shouldCheckErrors = hasError;
|
handleClickToggle(categoryName);
|
||||||
addComponentToDynamicZone(name, componentUid, shouldCheckErrors);
|
}}
|
||||||
}}
|
onClickComponent={componentUid => {
|
||||||
/>
|
setCategoryToOpen('');
|
||||||
);
|
const shouldCheckErrors = hasError;
|
||||||
})}
|
addComponentToDynamicZone(name, componentUid, shouldCheckErrors);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ComponentsPicker>
|
||||||
</ComponentsPicker>
|
</Collapse>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
) : (
|
) : (
|
||||||
<BaselineAlignement top="9px" />
|
<BaselineAlignement top="9px" />
|
||||||
|
Loading…
x
Reference in New Issue
Block a user