mirror of
https://github.com/strapi/strapi.git
synced 2025-12-18 10:43:56 +00:00
feat(content-manager): add container queries to form fields (#22693)
This commit is contained in:
parent
c2c6a58d5a
commit
926e9af936
@ -1,7 +1,8 @@
|
|||||||
import { useField } from '@strapi/admin/strapi-admin';
|
import { useField } from '@strapi/admin/strapi-admin';
|
||||||
import { Box, Flex, Grid } from '@strapi/design-system';
|
import { Box, Flex } from '@strapi/design-system';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import { ResponsiveGridItem, ResponsiveGridRoot } from '../../FormLayout';
|
||||||
import { ComponentProvider, useComponent } from '../ComponentContext';
|
import { ComponentProvider, useComponent } from '../ComponentContext';
|
||||||
|
|
||||||
import type { ComponentInputProps } from './Input';
|
import type { ComponentInputProps } from './Input';
|
||||||
@ -33,7 +34,7 @@ const NonRepeatableComponent = ({
|
|||||||
<Flex direction="column" alignItems="stretch" gap={6}>
|
<Flex direction="column" alignItems="stretch" gap={6}>
|
||||||
{layout.map((row, index) => {
|
{layout.map((row, index) => {
|
||||||
return (
|
return (
|
||||||
<Grid.Root gap={4} key={index}>
|
<ResponsiveGridRoot gap={4} key={index}>
|
||||||
{row.map(({ size, ...field }) => {
|
{row.map(({ size, ...field }) => {
|
||||||
/**
|
/**
|
||||||
* Layouts are built from schemas so they don't understand the complete
|
* Layouts are built from schemas so they don't understand the complete
|
||||||
@ -49,7 +50,7 @@ const NonRepeatableComponent = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid.Item
|
<ResponsiveGridItem
|
||||||
col={size}
|
col={size}
|
||||||
key={completeFieldName}
|
key={completeFieldName}
|
||||||
s={12}
|
s={12}
|
||||||
@ -58,10 +59,10 @@ const NonRepeatableComponent = ({
|
|||||||
alignItems="stretch"
|
alignItems="stretch"
|
||||||
>
|
>
|
||||||
{children({ ...field, label: translatedLabel, name: completeFieldName })}
|
{children({ ...field, label: translatedLabel, name: completeFieldName })}
|
||||||
</Grid.Item>
|
</ResponsiveGridItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Grid.Root>
|
</ResponsiveGridRoot>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import {
|
|||||||
Accordion,
|
Accordion,
|
||||||
IconButton,
|
IconButton,
|
||||||
useComposedRefs,
|
useComposedRefs,
|
||||||
Grid,
|
|
||||||
BoxComponent,
|
BoxComponent,
|
||||||
} from '@strapi/design-system';
|
} from '@strapi/design-system';
|
||||||
import { Plus, Drag, Trash } from '@strapi/icons';
|
import { Plus, Drag, Trash } from '@strapi/icons';
|
||||||
@ -26,6 +25,7 @@ import { getIn } from '../../../../../utils/objects';
|
|||||||
import { getTranslation } from '../../../../../utils/translations';
|
import { getTranslation } from '../../../../../utils/translations';
|
||||||
import { transformDocument } from '../../../utils/data';
|
import { transformDocument } from '../../../utils/data';
|
||||||
import { createDefaultForm } from '../../../utils/forms';
|
import { createDefaultForm } from '../../../utils/forms';
|
||||||
|
import { ResponsiveGridItem, ResponsiveGridRoot } from '../../FormLayout';
|
||||||
import { ComponentProvider, useComponent } from '../ComponentContext';
|
import { ComponentProvider, useComponent } from '../ComponentContext';
|
||||||
|
|
||||||
import { Initializer } from './Initializer';
|
import { Initializer } from './Initializer';
|
||||||
@ -273,7 +273,7 @@ const RepeatableComponent = ({
|
|||||||
>
|
>
|
||||||
{layout.map((row, index) => {
|
{layout.map((row, index) => {
|
||||||
return (
|
return (
|
||||||
<Grid.Root gap={4} key={index}>
|
<ResponsiveGridRoot gap={4} key={index}>
|
||||||
{row.map(({ size, ...field }) => {
|
{row.map(({ size, ...field }) => {
|
||||||
/**
|
/**
|
||||||
* Layouts are built from schemas so they don't understand the complete
|
* Layouts are built from schemas so they don't understand the complete
|
||||||
@ -289,7 +289,7 @@ const RepeatableComponent = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid.Item
|
<ResponsiveGridItem
|
||||||
col={size}
|
col={size}
|
||||||
key={completeFieldName}
|
key={completeFieldName}
|
||||||
s={12}
|
s={12}
|
||||||
@ -302,10 +302,10 @@ const RepeatableComponent = ({
|
|||||||
label: translatedLabel,
|
label: translatedLabel,
|
||||||
name: completeFieldName,
|
name: completeFieldName,
|
||||||
})}
|
})}
|
||||||
</Grid.Item>
|
</ResponsiveGridItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Grid.Root>
|
</ResponsiveGridRoot>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Component>
|
</Component>
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { useIntl } from 'react-intl';
|
|||||||
import { styled } from 'styled-components';
|
import { styled } from 'styled-components';
|
||||||
|
|
||||||
import { ComponentIcon } from '../../../../../components/ComponentIcon';
|
import { ComponentIcon } from '../../../../../components/ComponentIcon';
|
||||||
|
import { RESPONSIVE_CONTAINER_BREAKPOINTS } from '../../FormLayout';
|
||||||
|
|
||||||
interface ComponentCategoryProps {
|
interface ComponentCategoryProps {
|
||||||
category: string;
|
category: string;
|
||||||
@ -34,7 +35,7 @@ const ComponentCategory = ({
|
|||||||
{formatMessage({ id: category, defaultMessage: category })}
|
{formatMessage({ id: category, defaultMessage: category })}
|
||||||
</Accordion.Trigger>
|
</Accordion.Trigger>
|
||||||
</Accordion.Header>
|
</Accordion.Header>
|
||||||
<Accordion.Content>
|
<ResponsiveAccordionContent>
|
||||||
<Grid paddingTop={4} paddingBottom={4} paddingLeft={3} paddingRight={3}>
|
<Grid paddingTop={4} paddingBottom={4} paddingLeft={3} paddingRight={3}>
|
||||||
{components.map(({ uid, displayName, icon }) => (
|
{components.map(({ uid, displayName, icon }) => (
|
||||||
<ComponentBox
|
<ComponentBox
|
||||||
@ -59,15 +60,23 @@ const ComponentCategory = ({
|
|||||||
</ComponentBox>
|
</ComponentBox>
|
||||||
))}
|
))}
|
||||||
</Grid>
|
</Grid>
|
||||||
</Accordion.Content>
|
</ResponsiveAccordionContent>
|
||||||
</Accordion.Item>
|
</Accordion.Item>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ResponsiveAccordionContent = styled(Accordion.Content)`
|
||||||
|
container-type: inline-size;
|
||||||
|
`;
|
||||||
|
|
||||||
const Grid = styled(Box)`
|
const Grid = styled(Box)`
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, 14rem);
|
grid-template-columns: repeat(auto-fill, 100%);
|
||||||
grid-gap: ${({ theme }) => theme.spaces[1]};
|
grid-gap: ${({ theme }) => theme.spaces[1]};
|
||||||
|
|
||||||
|
@container (min-width: ${() => RESPONSIVE_CONTAINER_BREAKPOINTS.sm}) {
|
||||||
|
grid-template-columns: repeat(auto-fill, 14rem);
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ComponentBox = styled<FlexComponent<'button'>>(Flex)`
|
const ComponentBox = styled<FlexComponent<'button'>>(Flex)`
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import { useDocLayout } from '../../../../../hooks/useDocumentLayout';
|
|||||||
import { type UseDragAndDropOptions, useDragAndDrop } from '../../../../../hooks/useDragAndDrop';
|
import { type UseDragAndDropOptions, useDragAndDrop } from '../../../../../hooks/useDragAndDrop';
|
||||||
import { getIn } from '../../../../../utils/objects';
|
import { getIn } from '../../../../../utils/objects';
|
||||||
import { getTranslation } from '../../../../../utils/translations';
|
import { getTranslation } from '../../../../../utils/translations';
|
||||||
|
import { ResponsiveGridItem, ResponsiveGridRoot } from '../../FormLayout';
|
||||||
import { InputRenderer, type InputRendererProps } from '../../InputRenderer';
|
import { InputRenderer, type InputRendererProps } from '../../InputRenderer';
|
||||||
|
|
||||||
import type { ComponentPickerProps } from './ComponentPicker';
|
import type { ComponentPickerProps } from './ComponentPicker';
|
||||||
@ -244,7 +245,7 @@ const DynamicComponent = ({
|
|||||||
direction="column"
|
direction="column"
|
||||||
alignItems="stretch"
|
alignItems="stretch"
|
||||||
>
|
>
|
||||||
<Grid.Root gap={4}>
|
<ResponsiveGridRoot gap={4}>
|
||||||
{row.map(({ size, ...field }) => {
|
{row.map(({ size, ...field }) => {
|
||||||
const fieldName = `${name}.${index}.${field.name}`;
|
const fieldName = `${name}.${index}.${field.name}`;
|
||||||
|
|
||||||
@ -257,7 +258,7 @@ const DynamicComponent = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid.Item
|
<ResponsiveGridItem
|
||||||
col={size}
|
col={size}
|
||||||
key={fieldName}
|
key={fieldName}
|
||||||
s={12}
|
s={12}
|
||||||
@ -270,10 +271,10 @@ const DynamicComponent = ({
|
|||||||
) : (
|
) : (
|
||||||
<InputRenderer {...fieldWithTranslatedLabel} name={fieldName} />
|
<InputRenderer {...fieldWithTranslatedLabel} name={fieldName} />
|
||||||
)}
|
)}
|
||||||
</Grid.Item>
|
</ResponsiveGridItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Grid.Root>
|
</ResponsiveGridRoot>
|
||||||
</Grid.Item>
|
</Grid.Item>
|
||||||
))}
|
))}
|
||||||
</Grid.Root>
|
</Grid.Root>
|
||||||
|
|||||||
@ -1,11 +1,28 @@
|
|||||||
import { Box, Flex, Grid } from '@strapi/design-system';
|
import { Box, Flex, Grid } from '@strapi/design-system';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
|
import { styled } from 'styled-components';
|
||||||
|
|
||||||
import { useDoc } from '../../../hooks/useDocument';
|
import { useDoc } from '../../../hooks/useDocument';
|
||||||
import { EditLayout } from '../../../hooks/useDocumentLayout';
|
import { EditLayout } from '../../../hooks/useDocumentLayout';
|
||||||
|
|
||||||
import { InputRenderer } from './InputRenderer';
|
import { InputRenderer } from './InputRenderer';
|
||||||
|
|
||||||
|
export const RESPONSIVE_CONTAINER_BREAKPOINTS = {
|
||||||
|
sm: '27.5rem', // 440px
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ResponsiveGridRoot = styled(Grid.Root)`
|
||||||
|
container-type: inline-size;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ResponsiveGridItem = styled(Grid.Item)`
|
||||||
|
grid-column: span 12;
|
||||||
|
|
||||||
|
@container (min-width: ${RESPONSIVE_CONTAINER_BREAKPOINTS.sm}) {
|
||||||
|
${({ col }) => col && `grid-column: span ${col};`}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
interface FormLayoutProps extends Pick<EditLayout, 'layout'> {}
|
interface FormLayoutProps extends Pick<EditLayout, 'layout'> {}
|
||||||
|
|
||||||
const FormLayout = ({ layout }: FormLayoutProps) => {
|
const FormLayout = ({ layout }: FormLayoutProps) => {
|
||||||
@ -50,7 +67,7 @@ const FormLayout = ({ layout }: FormLayoutProps) => {
|
|||||||
>
|
>
|
||||||
<Flex direction="column" alignItems="stretch" gap={6}>
|
<Flex direction="column" alignItems="stretch" gap={6}>
|
||||||
{panel.map((row, gridRowIndex) => (
|
{panel.map((row, gridRowIndex) => (
|
||||||
<Grid.Root key={gridRowIndex} gap={4}>
|
<ResponsiveGridRoot key={gridRowIndex} gap={4}>
|
||||||
{row.map(({ size, ...field }) => {
|
{row.map(({ size, ...field }) => {
|
||||||
const fieldWithTranslatedLabel = {
|
const fieldWithTranslatedLabel = {
|
||||||
...field,
|
...field,
|
||||||
@ -60,7 +77,7 @@ const FormLayout = ({ layout }: FormLayoutProps) => {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Grid.Item
|
<ResponsiveGridItem
|
||||||
col={size}
|
col={size}
|
||||||
key={field.name}
|
key={field.name}
|
||||||
s={12}
|
s={12}
|
||||||
@ -69,10 +86,10 @@ const FormLayout = ({ layout }: FormLayoutProps) => {
|
|||||||
alignItems="stretch"
|
alignItems="stretch"
|
||||||
>
|
>
|
||||||
<InputRenderer {...fieldWithTranslatedLabel} />
|
<InputRenderer {...fieldWithTranslatedLabel} />
|
||||||
</Grid.Item>
|
</ResponsiveGridItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Grid.Root>
|
</ResponsiveGridRoot>
|
||||||
))}
|
))}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@ -110,8 +110,11 @@ test.describe('Settings', () => {
|
|||||||
* Create the missing components in the "content" dynamic zone.
|
* Create the missing components in the "content" dynamic zone.
|
||||||
*/
|
*/
|
||||||
await page.getByRole('button', { name: 'There are 2 missing components' }).click();
|
await page.getByRole('button', { name: 'There are 2 missing components' }).click();
|
||||||
|
// Click the button in the DZ component picker
|
||||||
await page.getByRole('button', { name: 'Product carousel' }).click();
|
await page.getByRole('button', { name: 'Product carousel' }).click();
|
||||||
|
// Click the DZ component toggle button
|
||||||
await page.getByRole('button', { name: 'Product carousel' }).click();
|
await page.getByRole('button', { name: 'Product carousel' }).click();
|
||||||
|
|
||||||
await page
|
await page
|
||||||
.getByRole('region', { name: /Product carousel/ })
|
.getByRole('region', { name: /Product carousel/ })
|
||||||
.getByRole('textbox', { name: 'title' })
|
.getByRole('textbox', { name: 'title' })
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user