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,66 +12,71 @@ import { Divider } from '@strapi/design-system/Divider';
import { Grid, GridItem } from '@strapi/design-system/Grid';
import { KeyboardNavigable } from '@strapi/design-system/KeyboardNavigable';
import { ModalBody } from '@strapi/design-system/ModalLayout';
import { Flex } from '@strapi/design-system/Flex';
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 AttributeOption from './AttributeOption';
const AttributeOptions = ({ attributes, forTarget, kind }) => {
const AttributeOptions = ({ attributes }) => {
const { formatMessage } = useIntl();
const titleIdSuffix = forTarget.includes('component') ? 'component' : kind;
const titleId = getTrad(`modalForm.sub-header.chooseAttribute.${titleIdSuffix}`);
const defaultTabId = getTrad(`modalForm.tabs.default`);
const customTabId = getTrad(`modalForm.tabs.custom`);
return (
<ModalBody>
<Flex paddingBottom={4}>
<Typography variant="beta" as="h2">
{formatMessage({ id: titleId, defaultMessage: 'Select a field' })}
</Typography>
</Flex>
<Divider />
<Box paddingTop={6} paddingBottom={4}>
<KeyboardNavigable tagName="button">
<Stack spacing={8}>
{attributes.map((attributeRow, index) => {
const key = index;
<ModalBody paddingTop={3} paddingLeft={6} paddingRight={6} paddingBottom={4}>
<TabGroup label="Attribute type tabs" id="attribute-type-tabs" variant="simple">
<Tabs>
<Tab>{formatMessage({ id: defaultTabId, defaultMessage: 'Default' })}</Tab>
<Tab>{formatMessage({ id: customTabId, defaultMessage: 'Custom' })}</Tab>
</Tabs>
<Box paddingBottom={6}>
<Divider />
</Box>
<TabPanels>
<TabPanel>
<KeyboardNavigable tagName="button">
<Stack spacing={8}>
{attributes.map((attributeRow, index) => {
const key = index;
return (
<Grid key={key} gap={0}>
{attributeRow.map((attribute, index) => {
const isOdd = index % 2 === 1;
const paddingLeft = isOdd ? 2 : 0;
const paddingRight = isOdd ? 0 : 2;
return (
<Grid key={key} gap={0}>
{attributeRow.map((attribute, index) => {
const isOdd = index % 2 === 1;
const paddingLeft = isOdd ? 2 : 0;
const paddingRight = isOdd ? 0 : 2;
return (
<GridItem key={attribute} col={6} style={{ height: '100%' }}>
<Box
paddingLeft={paddingLeft}
paddingRight={paddingRight}
paddingBottom={1}
style={{ height: '100%' }}
>
<AttributeOption type={attribute} />
</Box>
</GridItem>
);
})}
</Grid>
);
})}
</Stack>
</KeyboardNavigable>
</Box>
return (
<GridItem key={attribute} col={6} style={{ height: '100%' }}>
<Box
paddingLeft={paddingLeft}
paddingRight={paddingRight}
paddingBottom={2}
style={{ height: '100%' }}
>
<AttributeOption type={attribute} />
</Box>
</GridItem>
);
})}
</Grid>
);
})}
</Stack>
</KeyboardNavigable>
</TabPanel>
<TabPanel>
<Box>Coming soon</Box>
</TabPanel>
</TabPanels>
</TabGroup>
</ModalBody>
);
};
AttributeOptions.propTypes = {
attributes: PropTypes.array.isRequired,
forTarget: PropTypes.string.isRequired,
kind: PropTypes.string.isRequired,
};
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)
);
const schemaKind = get(contentTypes, [targetUid, 'schema', 'kind']);
return (
<>
<ModalLayout onClose={handleClosed} labelledBy="title">
@ -907,13 +905,7 @@ const FormModal = () => {
targetUid={targetUid}
attributeType={attributeType}
/>
{isPickingAttribute && (
<AttributeOptions
attributes={displayedAttributes}
forTarget={forTarget}
kind={schemaKind || 'collectionType'}
/>
)}
{isPickingAttribute && <AttributeOptions attributes={displayedAttributes} />}
{!isPickingAttribute && (
<form onSubmit={handleSubmit}>
<ModalBody>

View File

@ -155,6 +155,8 @@
"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.singleType": "Select a field for your single type",
"modalForm.tabs.default": "Default",
"modalForm.tabs.custom": "Custom",
"modelPage.attribute.relation-polymorphic": "Relation (polymorphic)",
"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",