mirror of
https://github.com/strapi/strapi.git
synced 2025-09-21 06:22:30 +00:00
Add components select
Signed-off-by: soupette <cyril@strapi.io>
This commit is contained in:
parent
97950e25b1
commit
25065ba0c8
@ -1,5 +1,3 @@
|
|||||||
export const ADD_COMPONENTS_TO_DYNAMIC_ZONE =
|
|
||||||
'ContentTypeBuilder/FormModal/ADD_COMPONENTS_TO_DYNAMIC_ZONE';
|
|
||||||
export const ON_CHANGE = 'ContentTypeBuilder/FormModal/ON_CHANGE';
|
export const ON_CHANGE = 'ContentTypeBuilder/FormModal/ON_CHANGE';
|
||||||
export const ON_CHANGE_RELATION_TARGET = 'ContentTypeBuilder/FormModal/ON_CHANGE_RELATION_TARGET';
|
export const ON_CHANGE_RELATION_TARGET = 'ContentTypeBuilder/FormModal/ON_CHANGE_RELATION_TARGET';
|
||||||
export const ON_CHANGE_RELATION_TYPE = 'ContentTypeBuilder/FormModal/ON_CHANGE_RELATION_TYPE';
|
export const ON_CHANGE_RELATION_TYPE = 'ContentTypeBuilder/FormModal/ON_CHANGE_RELATION_TYPE';
|
||||||
|
@ -35,7 +35,7 @@ const form = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'components',
|
name: 'components',
|
||||||
type: 'componentSelect',
|
type: 'select-components',
|
||||||
intlLabel: {
|
intlLabel: {
|
||||||
id: getTrad('modalForm.attributes.select-components'),
|
id: getTrad('modalForm.attributes.select-components'),
|
||||||
defaultMessage: 'Select the components',
|
defaultMessage: 'Select the components',
|
||||||
|
@ -37,6 +37,7 @@ import ComponentIconPicker from '../ComponentIconPicker';
|
|||||||
import Relation from '../Relation';
|
import Relation from '../Relation';
|
||||||
import SelectCategory from '../SelectCategory';
|
import SelectCategory from '../SelectCategory';
|
||||||
import SelectComponent from '../SelectComponent';
|
import SelectComponent from '../SelectComponent';
|
||||||
|
import SelectComponents from '../SelectComponents';
|
||||||
// import SelectCategory from '../WrapperSelect';
|
// import SelectCategory from '../WrapperSelect';
|
||||||
// import WrapperSelect from '../WrapperSelect';
|
// import WrapperSelect from '../WrapperSelect';
|
||||||
import findAttribute from '../../utils/findAttribute';
|
import findAttribute from '../../utils/findAttribute';
|
||||||
@ -59,7 +60,6 @@ import {
|
|||||||
SET_DATA_TO_EDIT,
|
SET_DATA_TO_EDIT,
|
||||||
SET_DYNAMIC_ZONE_DATA_SCHEMA,
|
SET_DYNAMIC_ZONE_DATA_SCHEMA,
|
||||||
SET_ATTRIBUTE_DATA_SCHEMA,
|
SET_ATTRIBUTE_DATA_SCHEMA,
|
||||||
// ADD_COMPONENTS_TO_DYNAMIC_ZONE,
|
|
||||||
SET_ERRORS,
|
SET_ERRORS,
|
||||||
ON_CHANGE,
|
ON_CHANGE,
|
||||||
RESET_PROPS_AND_SET_THE_FORM_FOR_ADDING_A_COMPO_TO_A_DZ,
|
RESET_PROPS_AND_SET_THE_FORM_FOR_ADDING_A_COMPO_TO_A_DZ,
|
||||||
@ -554,17 +554,6 @@ const FormModal = () => {
|
|||||||
}, '');
|
}, '');
|
||||||
};
|
};
|
||||||
|
|
||||||
// const handleClickAddComponentsToDynamicZone = ({
|
|
||||||
// target: { name, components, shouldAddComponents },
|
|
||||||
// }) => {
|
|
||||||
// dispatch({
|
|
||||||
// type: ADD_COMPONENTS_TO_DYNAMIC_ZONE,
|
|
||||||
// name,
|
|
||||||
// components,
|
|
||||||
// shouldAddComponents,
|
|
||||||
// });
|
|
||||||
// };
|
|
||||||
|
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
({ target: { name, value, type, ...rest } }) => {
|
({ target: { name, value, type, ...rest } }) => {
|
||||||
const namesThatCanResetToNullValue = [
|
const namesThatCanResetToNullValue = [
|
||||||
@ -1099,11 +1088,13 @@ const FormModal = () => {
|
|||||||
relation: Relation,
|
relation: Relation,
|
||||||
'select-category': SelectCategory,
|
'select-category': SelectCategory,
|
||||||
'select-component': SelectComponent,
|
'select-component': SelectComponent,
|
||||||
|
'select-components': SelectComponents,
|
||||||
'select-default-boolean': BooleanDefaultValueSelect,
|
'select-default-boolean': BooleanDefaultValueSelect,
|
||||||
'toggle-draft-publish': DraftAndPublishToggle,
|
'toggle-draft-publish': DraftAndPublishToggle,
|
||||||
...inputsFromPlugins,
|
...inputsFromPlugins,
|
||||||
},
|
},
|
||||||
componentToCreate,
|
componentToCreate,
|
||||||
|
dynamicZoneTarget: state.dynamicZoneTarget,
|
||||||
formErrors,
|
formErrors,
|
||||||
isAddingAComponentToAnotherComponent,
|
isAddingAComponentToAnotherComponent,
|
||||||
isCreatingComponentWhileAddingAField,
|
isCreatingComponentWhileAddingAField,
|
||||||
|
@ -3,7 +3,6 @@ import pluralize from 'pluralize';
|
|||||||
import set from 'lodash/set';
|
import set from 'lodash/set';
|
||||||
import snakeCase from 'lodash/snakeCase';
|
import snakeCase from 'lodash/snakeCase';
|
||||||
import getRelationType from '../../utils/getRelationType';
|
import getRelationType from '../../utils/getRelationType';
|
||||||
import makeUnique from '../../utils/makeUnique';
|
|
||||||
import { createComponentUid } from './utils/createUid';
|
import { createComponentUid } from './utils/createUid';
|
||||||
import { shouldPluralizeName, shouldPluralizeTargetAttribute } from './utils/relations';
|
import { shouldPluralizeName, shouldPluralizeTargetAttribute } from './utils/relations';
|
||||||
import * as actions from './constants';
|
import * as actions from './constants';
|
||||||
@ -20,22 +19,6 @@ const reducer = (state = initialState, action) =>
|
|||||||
// eslint-disable-next-line consistent-return
|
// eslint-disable-next-line consistent-return
|
||||||
produce(state, draftState => {
|
produce(state, draftState => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case actions.ADD_COMPONENTS_TO_DYNAMIC_ZONE: {
|
|
||||||
const { components, shouldAddComponents } = action;
|
|
||||||
let currentList = [];
|
|
||||||
|
|
||||||
if (shouldAddComponents) {
|
|
||||||
currentList = [...state.modifiedData.components, ...components];
|
|
||||||
} else {
|
|
||||||
currentList = state.modifiedData.components.filter(comp => {
|
|
||||||
return components.indexOf(comp) === -1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
draftState.modifiedData.components = makeUnique(currentList);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case actions.ON_CHANGE: {
|
case actions.ON_CHANGE: {
|
||||||
const { keys, value } = action;
|
const { keys, value } = action;
|
||||||
const obj = state.modifiedData;
|
const obj = state.modifiedData;
|
||||||
|
@ -2,66 +2,6 @@ import reducer, { initialState } from '../reducer';
|
|||||||
import * as actions from '../constants';
|
import * as actions from '../constants';
|
||||||
|
|
||||||
describe('CTB | components | FormModal | reducer | actions', () => {
|
describe('CTB | components | FormModal | reducer | actions', () => {
|
||||||
describe('ADD_COMPONENTS_TO_DYNAMIC_ZONE', () => {
|
|
||||||
it('Should add the components correctly', () => {
|
|
||||||
const action = {
|
|
||||||
type: actions.ADD_COMPONENTS_TO_DYNAMIC_ZONE,
|
|
||||||
components: ['default.test', 'default.test2', 'default.test3'],
|
|
||||||
shouldAddComponents: true,
|
|
||||||
name: 'components',
|
|
||||||
};
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
initialData: {},
|
|
||||||
modifiedData: {
|
|
||||||
type: 'dynamiczone',
|
|
||||||
name: 'dz',
|
|
||||||
components: ['default.test'],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const expected = {
|
|
||||||
initialData: {},
|
|
||||||
modifiedData: {
|
|
||||||
type: 'dynamiczone',
|
|
||||||
name: 'dz',
|
|
||||||
components: ['default.test', 'default.test2', 'default.test3'],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(reducer(state, action)).toEqual(expected);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should remove the components correctly', () => {
|
|
||||||
const action = {
|
|
||||||
type: actions.ADD_COMPONENTS_TO_DYNAMIC_ZONE,
|
|
||||||
components: ['default.test2', 'default.test3'],
|
|
||||||
shouldAddComponents: false,
|
|
||||||
name: 'components',
|
|
||||||
};
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
initialData: {},
|
|
||||||
modifiedData: {
|
|
||||||
type: 'dynamiczone',
|
|
||||||
name: 'dz',
|
|
||||||
components: ['default.test', 'default.test2', 'default.test3'],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const expected = {
|
|
||||||
initialData: {},
|
|
||||||
modifiedData: {
|
|
||||||
type: 'dynamiczone',
|
|
||||||
name: 'dz',
|
|
||||||
components: ['default.test'],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(reducer(state, action)).toEqual(expected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe(actions.ON_CHANGE, () => {
|
describe(actions.ON_CHANGE, () => {
|
||||||
it('Should update the modifiedData object correctly', () => {
|
it('Should update the modifiedData object correctly', () => {
|
||||||
const action = {
|
const action = {
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
/**
|
|
||||||
*
|
|
||||||
* Tests for SelectComponent
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { render } from '@testing-library/react';
|
|
||||||
import { ThemeProvider, lightTheme } from '@strapi/parts';
|
|
||||||
import { IntlProvider } from 'react-intl';
|
|
||||||
import SelectComponent from '../index';
|
|
||||||
|
|
||||||
const messages = {
|
|
||||||
'content-type-builder.component.name': 'Select Component',
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('<SelectComponent />', () => {
|
|
||||||
it('renders and matches the snapshot', () => {
|
|
||||||
const {
|
|
||||||
container: { firstChild },
|
|
||||||
} = render(
|
|
||||||
<ThemeProvider theme={lightTheme}>
|
|
||||||
<IntlProvider locale="en" messages={messages} defaultLocale="en">
|
|
||||||
<SelectComponent />
|
|
||||||
</IntlProvider>
|
|
||||||
</ThemeProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(firstChild).toMatchInlineSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
@ -0,0 +1,85 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* SelectComponents
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { MultiSelectNested } from '@strapi/parts/Select';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
import useDataManager from '../../hooks/useDataManager';
|
||||||
|
import findAttribute from '../../utils/findAttribute';
|
||||||
|
import { getTrad } from '../../utils';
|
||||||
|
|
||||||
|
const SelectComponents = ({ dynamicZoneTarget, intlLabel, name, onChange, value }) => {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
const { componentsGroupedByCategory, modifiedData } = useDataManager();
|
||||||
|
const dzSchema =
|
||||||
|
findAttribute(modifiedData.contentType.schema.attributes, dynamicZoneTarget) || {};
|
||||||
|
const alreadyUsedComponents = dzSchema.components || [];
|
||||||
|
const filteredComponentsGroupedByCategory = Object.keys(componentsGroupedByCategory).reduce(
|
||||||
|
(acc, current) => {
|
||||||
|
const filteredComponents = componentsGroupedByCategory[current].filter(({ uid }) => {
|
||||||
|
return !alreadyUsedComponents.includes(uid);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (filteredComponents.length > 0) {
|
||||||
|
acc[current] = filteredComponents;
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
const options = Object.entries(filteredComponentsGroupedByCategory).reduce((acc, current) => {
|
||||||
|
const [categoryName, components] = current;
|
||||||
|
const section = {
|
||||||
|
label: categoryName,
|
||||||
|
children: components.map(({ uid, schema: { name } }) => {
|
||||||
|
return { label: name, value: uid };
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
acc.push(section);
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const displayedValue = formatMessage(
|
||||||
|
{
|
||||||
|
id: getTrad('components.SelectComponents.displayed-value'),
|
||||||
|
defaultMessage:
|
||||||
|
'{number, plural, =0 {# components} one {# component} other {# components}} selected',
|
||||||
|
},
|
||||||
|
{ number: value.length }
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MultiSelectNested
|
||||||
|
id="select1"
|
||||||
|
label={formatMessage(intlLabel)}
|
||||||
|
customizeContent={() => displayedValue}
|
||||||
|
name={name}
|
||||||
|
onChange={values => {
|
||||||
|
onChange({ target: { name, value: values, type: 'select-components' } });
|
||||||
|
}}
|
||||||
|
options={options}
|
||||||
|
value={value || []}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
SelectComponents.propTypes = {
|
||||||
|
intlLabel: PropTypes.shape({
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
defaultMessage: PropTypes.string.isRequired,
|
||||||
|
values: PropTypes.object,
|
||||||
|
}).isRequired,
|
||||||
|
dynamicZoneTarget: PropTypes.string.isRequired,
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
|
value: PropTypes.array.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SelectComponents;
|
@ -190,5 +190,6 @@
|
|||||||
"table.button.no-fields": "Add new field",
|
"table.button.no-fields": "Add new field",
|
||||||
"table.headers.name": "Name",
|
"table.headers.name": "Name",
|
||||||
"table.headers.type": "Type",
|
"table.headers.type": "Type",
|
||||||
"ComponentIconPicker.search.placeholder": "Search for an icon"
|
"ComponentIconPicker.search.placeholder": "Search for an icon",
|
||||||
|
"components.SelectComponents.displayed-value": "{number, plural, =0 {# components} one {# component} other {# components}} selected"
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user