Add tabs to attribute modal

This commit is contained in:
Mark Kaylor 2022-07-18 14:32:25 +02:00
parent 4949da189e
commit ded30b56e3
5 changed files with 1389 additions and 53 deletions

View File

@ -12,27 +12,29 @@ import { Divider } from '@strapi/design-system/Divider';
import { Grid, GridItem } from '@strapi/design-system/Grid'; import { Grid, GridItem } from '@strapi/design-system/Grid';
import { KeyboardNavigable } from '@strapi/design-system/KeyboardNavigable'; import { KeyboardNavigable } from '@strapi/design-system/KeyboardNavigable';
import { ModalBody } from '@strapi/design-system/ModalLayout'; import { ModalBody } from '@strapi/design-system/ModalLayout';
import { Flex } from '@strapi/design-system/Flex';
import { Stack } from '@strapi/design-system/Stack'; import { Stack } from '@strapi/design-system/Stack';
import { Typography } from '@strapi/design-system/Typography'; import { Tabs, Tab, TabGroup, TabPanels, TabPanel } from '@strapi/design-system/Tabs';
import { getTrad } from '../../utils'; import { getTrad } from '../../utils';
import AttributeOption from './AttributeOption'; import AttributeOption from './AttributeOption';
const AttributeOptions = ({ attributes, forTarget, kind }) => { const AttributeOptions = ({ attributes }) => {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const titleIdSuffix = forTarget.includes('component') ? 'component' : kind; const defaultTabId = getTrad(`modalForm.tabs.default`);
const titleId = getTrad(`modalForm.sub-header.chooseAttribute.${titleIdSuffix}`); const customTabId = getTrad(`modalForm.tabs.custom`);
return ( return (
<ModalBody> <ModalBody paddingTop={3} paddingLeft={6} paddingRight={6} paddingBottom={4}>
<Flex paddingBottom={4}> <TabGroup label="Attribute type tabs" id="attribute-type-tabs" variant="simple">
<Typography variant="beta" as="h2"> <Tabs>
{formatMessage({ id: titleId, defaultMessage: 'Select a field' })} <Tab>{formatMessage({ id: defaultTabId, defaultMessage: 'Default' })}</Tab>
</Typography> <Tab>{formatMessage({ id: customTabId, defaultMessage: 'Custom' })}</Tab>
</Flex> </Tabs>
<Box paddingBottom={6}>
<Divider /> <Divider />
<Box paddingTop={6} paddingBottom={4}> </Box>
<TabPanels>
<TabPanel>
<KeyboardNavigable tagName="button"> <KeyboardNavigable tagName="button">
<Stack spacing={8}> <Stack spacing={8}>
{attributes.map((attributeRow, index) => { {attributes.map((attributeRow, index) => {
@ -50,7 +52,7 @@ const AttributeOptions = ({ attributes, forTarget, kind }) => {
<Box <Box
paddingLeft={paddingLeft} paddingLeft={paddingLeft}
paddingRight={paddingRight} paddingRight={paddingRight}
paddingBottom={1} paddingBottom={2}
style={{ height: '100%' }} style={{ height: '100%' }}
> >
<AttributeOption type={attribute} /> <AttributeOption type={attribute} />
@ -63,15 +65,18 @@ const AttributeOptions = ({ attributes, forTarget, kind }) => {
})} })}
</Stack> </Stack>
</KeyboardNavigable> </KeyboardNavigable>
</Box> </TabPanel>
<TabPanel>
<Box>Coming soon</Box>
</TabPanel>
</TabPanels>
</TabGroup>
</ModalBody> </ModalBody>
); );
}; };
AttributeOptions.propTypes = { AttributeOptions.propTypes = {
attributes: PropTypes.array.isRequired, attributes: PropTypes.array.isRequired,
forTarget: PropTypes.string.isRequired,
kind: PropTypes.string.isRequired,
}; };
export default AttributeOptions; export default AttributeOptions;

View File

@ -0,0 +1,105 @@
import { render, screen, getByText, fireEvent } from '@testing-library/react';
import { createMemoryHistory } from 'history';
import React from 'react';
import { Router } from 'react-router-dom';
import { lightTheme, darkTheme } from '@strapi/design-system';
import LanguageProvider from '../../../../../../admin/admin/src/components/LanguageProvider';
import Theme from '../../../../../../admin/admin/src/components/Theme';
import ThemeToggleProvider from '../../../../../../admin/admin/src/components/ThemeToggleProvider';
import en from '../../../../../../admin/admin/src/translations/en.json';
import FormModalNavigationProvider from '../../FormModalNavigationProvider';
import pluginEn from '../../../translations/en.json';
import getTrad from '../../../utils/getTrad';
import AttributeOptions from '../index';
const mockAttributes = [
[
'text',
'email',
'richtext',
'password',
'number',
'enumeration',
'date',
'media',
'boolean',
'json',
'relation',
'uid',
],
['component', 'dynamiczone'],
];
const makeApp = () => {
const history = createMemoryHistory();
const messages = {
en: Object.keys(pluginEn).reduce(
(acc, current) => {
acc[getTrad(current)] = pluginEn[current];
return acc;
},
{ ...en }
),
};
const localeNames = { en: 'English' };
return (
<LanguageProvider messages={messages} localeNames={localeNames}>
<ThemeToggleProvider themes={{ light: lightTheme, dark: darkTheme }}>
<Theme>
<Router history={history}>
<FormModalNavigationProvider>
<AttributeOptions attributes={mockAttributes} />
</FormModalNavigationProvider>
</Router>
</Theme>
</ThemeToggleProvider>
</LanguageProvider>
);
};
describe('AttributeOptions', () => {
it('renders and matches the snapshot', () => {
const App = makeApp();
const { container } = render(App);
expect(container).toMatchSnapshot();
});
it('shows the simple tabs', async () => {
const App = makeApp();
render(App);
const tabs = screen.getByLabelText('Attribute type tabs');
const defaultTab = await getByText(tabs, 'Default');
const customTab = await getByText(tabs, 'Custom');
expect(defaultTab).toBeVisible();
expect(customTab).toBeVisible();
});
it('defaults to the default tab', async () => {
const App = makeApp();
render(App);
const comingSoonText = screen.queryByText('Coming soon');
expect(comingSoonText).toEqual(null);
});
it('switches to the custom tab', async () => {
const App = makeApp();
render(App);
const customTab = screen.getByRole('tab', { selected: false });
fireEvent.click(customTab);
const button = screen.getByRole('tab', { selected: true });
const customTabActive = await getByText(button, 'Custom');
const comingSoonText = screen.getByText('Coming soon');
expect(customTabActive).not.toBe(null);
expect(comingSoonText).toBeVisible();
});
});

View File

@ -891,8 +891,6 @@ const FormModal = () => {
advancedFormInputNames.includes(key) advancedFormInputNames.includes(key)
); );
const schemaKind = get(contentTypes, [targetUid, 'schema', 'kind']);
return ( return (
<> <>
<ModalLayout onClose={handleClosed} labelledBy="title"> <ModalLayout onClose={handleClosed} labelledBy="title">
@ -907,13 +905,7 @@ const FormModal = () => {
targetUid={targetUid} targetUid={targetUid}
attributeType={attributeType} attributeType={attributeType}
/> />
{isPickingAttribute && ( {isPickingAttribute && <AttributeOptions attributes={displayedAttributes} />}
<AttributeOptions
attributes={displayedAttributes}
forTarget={forTarget}
kind={schemaKind || 'collectionType'}
/>
)}
{!isPickingAttribute && ( {!isPickingAttribute && (
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<ModalBody> <ModalBody>

View File

@ -155,6 +155,8 @@
"modalForm.sub-header.chooseAttribute.collectionType": "Select a field for your collection type", "modalForm.sub-header.chooseAttribute.collectionType": "Select a field for your collection type",
"modalForm.sub-header.chooseAttribute.component": "Select a field for your component", "modalForm.sub-header.chooseAttribute.component": "Select a field for your component",
"modalForm.sub-header.chooseAttribute.singleType": "Select a field for your single type", "modalForm.sub-header.chooseAttribute.singleType": "Select a field for your single type",
"modalForm.tabs.default": "Default",
"modalForm.tabs.custom": "Custom",
"modelPage.attribute.relation-polymorphic": "Relation (polymorphic)", "modelPage.attribute.relation-polymorphic": "Relation (polymorphic)",
"modelPage.attribute.relationWith": "Relation with", "modelPage.attribute.relationWith": "Relation with",
"notification.error.dynamiczone-min.validation": "At least one component is required in a dynamic zone to be able to save a content type", "notification.error.dynamiczone-min.validation": "At least one component is required in a dynamic zone to be able to save a content type",