mirror of
https://github.com/strapi/strapi.git
synced 2025-12-04 19:13:20 +00:00
Add tabs to attribute modal
This commit is contained in:
parent
4949da189e
commit
ded30b56e3
@ -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;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -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>
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user