2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-29 12:06:44 +00:00

[UI] Responsiveness Improvements (#8885)

* Shorten string in page title

* Style fixes

* Adjust cell width props

* Refactor <PageDetail> component

- Improve responsiveness

* Simplify <ItemDetailsGrid />

* Refactor <DetailsImage>
This commit is contained in:
Oliver 2025-01-14 10:38:48 +11:00 committed by GitHub
parent ea1b2e3079
commit 46f6450ee1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 224 additions and 189 deletions

View File

@ -380,16 +380,15 @@ export function DetailsTableField({
<Table.Tr style={{ verticalAlign: 'top' }}> <Table.Tr style={{ verticalAlign: 'top' }}>
<Table.Td <Table.Td
style={{ style={{
width: '50',
maxWidth: '50' maxWidth: '50'
}} }}
> >
<InvenTreeIcon icon={field.icon ?? (field.name as InvenTreeIconType)} /> <InvenTreeIcon icon={field.icon ?? (field.name as InvenTreeIconType)} />
</Table.Td> </Table.Td>
<Table.Td style={{ maxWidth: '65%', lineBreak: 'auto' }}> <Table.Td style={{ minWidth: 75, lineBreak: 'auto', flex: 2 }}>
<Text>{field.label}</Text> <Text>{field.label}</Text>
</Table.Td> </Table.Td>
<Table.Td style={{ lineBreak: 'anywhere' }}> <Table.Td style={{ lineBreak: 'anywhere', minWidth: 100, flex: 10 }}>
<FieldType field_data={field} field_value={fieldValue} /> <FieldType field_data={field} field_value={fieldValue} />
</Table.Td> </Table.Td>
<Table.Td style={{ width: '50' }}> <Table.Td style={{ width: '50' }}>
@ -409,7 +408,11 @@ export function DetailsTable({
title?: string; title?: string;
}>) { }>) {
return ( return (
<Paper p='xs' withBorder radius='xs'> <Paper
p='xs'
withBorder
style={{ overflowX: 'hidden', width: '100%', minWidth: 200 }}
>
<Stack gap='xs'> <Stack gap='xs'>
{title && <StylishText size='lg'>{title}</StylishText>} {title && <StylishText size='lg'>{title}</StylishText>}
<Table striped verticalSpacing={5} horizontalSpacing='sm'> <Table striped verticalSpacing={5} horizontalSpacing='sm'>

View File

@ -2,6 +2,7 @@ import { Trans, t } from '@lingui/macro';
import { import {
AspectRatio, AspectRatio,
Button, Button,
Grid,
Group, Group,
Image, Image,
Overlay, Overlay,
@ -421,7 +422,14 @@ export function DetailsImage(props: Readonly<DetailImageProps>) {
return ( return (
<> <>
{downloadImage.modal} {downloadImage.modal}
<AspectRatio ref={ref} maw={IMAGE_DIMENSION} ratio={1} pos='relative'> <Grid.Col span={{ base: 12, sm: 4 }}>
<AspectRatio
ref={ref}
maw={IMAGE_DIMENSION}
ratio={1}
pos='relative'
visibleFrom='xs'
>
<> <>
<ApiImage <ApiImage
src={img} src={img}
@ -446,6 +454,7 @@ export function DetailsImage(props: Readonly<DetailImageProps>) {
)} )}
</> </>
</AspectRatio> </AspectRatio>
</Grid.Col>
</> </>
); );
} }

View File

@ -1,16 +1,15 @@
import { Paper, SimpleGrid } from '@mantine/core'; import { Paper, SimpleGrid } from '@mantine/core';
import { useElementSize } from '@mantine/hooks';
import type React from 'react'; import type React from 'react';
import { useMemo } from 'react';
export function ItemDetailsGrid(props: React.PropsWithChildren<{}>) { export function ItemDetailsGrid(props: React.PropsWithChildren<{}>) {
const { ref, width } = useElementSize();
const cols = useMemo(() => (width > 700 ? 2 : 1), [width]);
return ( return (
<Paper p='xs'> <Paper p='xs'>
<SimpleGrid cols={cols} spacing='xs' verticalSpacing='xs' ref={ref}> <SimpleGrid
cols={{ base: 1, '900px': 2 }}
type='container'
spacing='xs'
verticalSpacing='xs'
>
{props.children} {props.children}
</SimpleGrid> </SimpleGrid>
</Paper> </Paper>

View File

@ -1,7 +1,8 @@
import { Group, Paper, Space, Stack, Text } from '@mantine/core'; import { Group, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
import { useHotkeys } from '@mantine/hooks'; import { useHotkeys } from '@mantine/hooks';
import { Fragment, type ReactNode } from 'react'; import { Fragment, type ReactNode, useMemo } from 'react';
import { shortenString } from '../../functions/tables';
import { ApiImage } from '../images/ApiImage'; import { ApiImage } from '../images/ApiImage';
import { StylishText } from '../items/StylishText'; import { StylishText } from '../items/StylishText';
import { type Breadcrumb, BreadcrumbList } from './BreadcrumbList'; import { type Breadcrumb, BreadcrumbList } from './BreadcrumbList';
@ -51,9 +52,41 @@ export function PageDetail({
] ]
]); ]);
const pageTitleString = useMemo(
() =>
shortenString({
str: title,
len: 50
}),
[title]
);
const description = useMemo(
() =>
shortenString({
str: subtitle,
len: 75
}),
[subtitle]
);
const maxCols = useMemo(() => {
let cols = 1;
if (!!detail) {
cols++;
}
if (!!badges) {
cols++;
}
return cols;
}, [detail, badges]);
return ( return (
<> <>
<PageTitle title={title} /> <PageTitle title={pageTitleString} />
<Stack gap='xs'> <Stack gap='xs'>
{breadcrumbs && breadcrumbs.length > 0 && ( {breadcrumbs && breadcrumbs.length > 0 && (
<BreadcrumbList <BreadcrumbList
@ -62,8 +95,19 @@ export function PageDetail({
/> />
)} )}
<Paper p='xs' radius='xs' shadow='xs'> <Paper p='xs' radius='xs' shadow='xs'>
<Stack gap='xs'> <Group
<Group justify='space-between' wrap='nowrap'> justify='space-between'
gap='xs'
wrap='nowrap'
align='flex-start'
>
<SimpleGrid
cols={{
base: 1,
md: Math.min(2, maxCols),
lg: Math.min(3, maxCols)
}}
>
<Group justify='left' wrap='nowrap'> <Group justify='left' wrap='nowrap'>
{imageUrl && ( {imageUrl && (
<ApiImage <ApiImage
@ -72,6 +116,7 @@ export function PageDetail({
miw={42} miw={42}
mah={42} mah={42}
maw={42} maw={42}
visibleFrom='sm'
/> />
)} )}
<Stack gap='xs'> <Stack gap='xs'>
@ -79,30 +124,33 @@ export function PageDetail({
{subtitle && ( {subtitle && (
<Group gap='xs'> <Group gap='xs'>
{icon} {icon}
<Text size='sm' truncate> <Text size='sm'>{description}</Text>
{subtitle}
</Text>
</Group> </Group>
)} )}
</Stack> </Stack>
</Group> </Group>
<Space /> {detail && <div>{detail}</div>}
{detail} {badges && (
<Group justify='right' gap='xs' wrap='nowrap'> <Group
justify='center'
gap='xs'
align='flex-start'
wrap='nowrap'
>
{badges?.map((badge, idx) => ( {badges?.map((badge, idx) => (
<Fragment key={idx}>{badge}</Fragment> <Fragment key={idx}>{badge}</Fragment>
))} ))}
</Group> </Group>
<Space /> )}
</SimpleGrid>
{actions && ( {actions && (
<Group gap={5} justify='right'> <Group gap={5} justify='right' wrap='nowrap' align='flex-start'>
{actions.map((action, idx) => ( {actions.map((action, idx) => (
<Fragment key={idx}>{action}</Fragment> <Fragment key={idx}>{action}</Fragment>
))} ))}
</Group> </Group>
)} )}
</Group> </Group>
</Stack>
</Paper> </Paper>
</Stack> </Stack>
</> </>

View File

@ -239,16 +239,14 @@ export default function BuildDetail() {
return ( return (
<ItemDetailsGrid> <ItemDetailsGrid>
<Grid> <Grid grow>
<Grid.Col span={4}>
<DetailsImage <DetailsImage
appRole={UserRoles.part} appRole={UserRoles.part}
apiPath={ApiEndpoints.part_list} apiPath={ApiEndpoints.part_list}
src={build.part_detail?.image ?? build.part_detail?.thumbnail} src={build.part_detail?.image ?? build.part_detail?.thumbnail}
pk={build.part} pk={build.part}
/> />
</Grid.Col> <Grid.Col span={{ base: 12, sm: 8 }}>
<Grid.Col span={8}>
<DetailsTable fields={tl} item={build} /> <DetailsTable fields={tl} item={build} />
</Grid.Col> </Grid.Col>
</Grid> </Grid>

View File

@ -145,8 +145,7 @@ export default function CompanyDetail(props: Readonly<CompanyDetailProps>) {
return ( return (
<ItemDetailsGrid> <ItemDetailsGrid>
<Grid> <Grid grow>
<Grid.Col span={4}>
<DetailsImage <DetailsImage
appRole={UserRoles.purchase_order} appRole={UserRoles.purchase_order}
apiPath={apiUrl(ApiEndpoints.company_list, company.pk)} apiPath={apiUrl(ApiEndpoints.company_list, company.pk)}
@ -159,8 +158,7 @@ export default function CompanyDetail(props: Readonly<CompanyDetailProps>) {
deleteFile: true deleteFile: true
}} }}
/> />
</Grid.Col> <Grid.Col span={{ base: 12, sm: 8 }}>
<Grid.Col span={8}>
<DetailsTable item={company} fields={tl} /> <DetailsTable item={company} fields={tl} />
</Grid.Col> </Grid.Col>
</Grid> </Grid>

View File

@ -133,8 +133,7 @@ export default function ManufacturerPartDetail() {
return ( return (
<ItemDetailsGrid> <ItemDetailsGrid>
<Grid> <Grid grow>
<Grid.Col span={4}>
<DetailsImage <DetailsImage
appRole={UserRoles.part} appRole={UserRoles.part}
src={manufacturerPart?.part_detail?.image} src={manufacturerPart?.part_detail?.image}
@ -144,8 +143,7 @@ export default function ManufacturerPartDetail() {
)} )}
pk={manufacturerPart?.part_detail?.pk} pk={manufacturerPart?.part_detail?.pk}
/> />
</Grid.Col> <Grid.Col span={{ base: 12, sm: 8 }}>
<Grid.Col span={8}>
<DetailsTable title={t`Part Details`} fields={tl} item={data} /> <DetailsTable title={t`Part Details`} fields={tl} item={data} />
</Grid.Col> </Grid.Col>
</Grid> </Grid>

View File

@ -217,8 +217,7 @@ export default function SupplierPartDetail() {
return ( return (
<ItemDetailsGrid> <ItemDetailsGrid>
<Grid> <Grid grow>
<Grid.Col span={4}>
<DetailsImage <DetailsImage
appRole={UserRoles.part} appRole={UserRoles.part}
src={supplierPart?.part_detail?.image} src={supplierPart?.part_detail?.image}
@ -228,7 +227,6 @@ export default function SupplierPartDetail() {
)} )}
pk={supplierPart?.part_detail?.pk} pk={supplierPart?.part_detail?.pk}
/> />
</Grid.Col>
<Grid.Col span={8}> <Grid.Col span={8}>
<DetailsTable title={t`Part Details`} fields={tl} item={data} /> <DetailsTable title={t`Part Details`} fields={tl} item={data} />
</Grid.Col> </Grid.Col>

View File

@ -5,7 +5,6 @@ import {
Grid, Grid,
Loader, Loader,
Skeleton, Skeleton,
Space,
Stack, Stack,
Text Text
} from '@mantine/core'; } from '@mantine/core';
@ -439,8 +438,7 @@ export default function PartDetail() {
return part ? ( return part ? (
<ItemDetailsGrid> <ItemDetailsGrid>
<Grid> <Grid grow>
<Grid.Col span={4}>
<DetailsImage <DetailsImage
appRole={UserRoles.part} appRole={UserRoles.part}
imageActions={{ imageActions={{
@ -454,8 +452,7 @@ export default function PartDetail() {
refresh={refreshInstance} refresh={refreshInstance}
pk={part.pk} pk={part.pk}
/> />
</Grid.Col> <Grid.Col span={{ base: 12, sm: 8 }}>
<Grid.Col span={8}>
<DetailsTable fields={tl} item={data} /> <DetailsTable fields={tl} item={data} />
</Grid.Col> </Grid.Col>
</Grid> </Grid>
@ -1040,9 +1037,7 @@ export default function PartDetail() {
}} }}
/> />
</Stack> </Stack>
) : ( ) : null
<Space />
)
} }
/> />
<PanelGroup <PanelGroup

View File

@ -263,16 +263,14 @@ export default function PurchaseOrderDetail() {
return ( return (
<ItemDetailsGrid> <ItemDetailsGrid>
<Grid> <Grid grow>
<Grid.Col span={4}>
<DetailsImage <DetailsImage
appRole={UserRoles.purchase_order} appRole={UserRoles.purchase_order}
apiPath={ApiEndpoints.company_list} apiPath={ApiEndpoints.company_list}
src={order.supplier_detail?.image} src={order.supplier_detail?.image}
pk={order.supplier} pk={order.supplier}
/> />
</Grid.Col> <Grid.Col span={{ base: 12, sm: 8 }}>
<Grid.Col span={8}>
<DetailsTable fields={tl} item={order} /> <DetailsTable fields={tl} item={order} />
</Grid.Col> </Grid.Col>
</Grid> </Grid>

View File

@ -233,16 +233,14 @@ export default function ReturnOrderDetail() {
return ( return (
<ItemDetailsGrid> <ItemDetailsGrid>
<Grid> <Grid grow>
<Grid.Col span={4}>
<DetailsImage <DetailsImage
appRole={UserRoles.purchase_order} appRole={UserRoles.purchase_order}
apiPath={ApiEndpoints.company_list} apiPath={ApiEndpoints.company_list}
src={order.customer_detail?.image} src={order.customer_detail?.image}
pk={order.customer} pk={order.customer}
/> />
</Grid.Col> <Grid.Col span={{ base: 12, sm: 8 }}>
<Grid.Col span={8}>
<DetailsTable fields={tl} item={order} /> <DetailsTable fields={tl} item={order} />
</Grid.Col> </Grid.Col>
</Grid> </Grid>

View File

@ -243,16 +243,14 @@ export default function SalesOrderDetail() {
return ( return (
<ItemDetailsGrid> <ItemDetailsGrid>
<Grid> <Grid grow>
<Grid.Col span={4}>
<DetailsImage <DetailsImage
appRole={UserRoles.purchase_order} appRole={UserRoles.purchase_order}
apiPath={ApiEndpoints.company_list} apiPath={ApiEndpoints.company_list}
src={order.customer_detail?.image} src={order.customer_detail?.image}
pk={order.customer} pk={order.customer}
/> />
</Grid.Col> <Grid.Col span={{ base: 12, sm: 8 }}>
<Grid.Col span={8}>
<DetailsTable fields={tl} item={order} /> <DetailsTable fields={tl} item={order} />
</Grid.Col> </Grid.Col>
</Grid> </Grid>

View File

@ -175,8 +175,7 @@ export default function SalesOrderShipmentDetail() {
return ( return (
<> <>
<ItemDetailsGrid> <ItemDetailsGrid>
<Grid> <Grid grow>
<Grid.Col span={4}>
<DetailsImage <DetailsImage
appRole={UserRoles.sales_order} appRole={UserRoles.sales_order}
apiPath={ApiEndpoints.company_list} apiPath={ApiEndpoints.company_list}
@ -189,8 +188,7 @@ export default function SalesOrderShipmentDetail() {
deleteFile: false deleteFile: false
}} }}
/> />
</Grid.Col> <Grid.Col span={{ base: 12, sm: 8 }}>
<Grid.Col span={8}>
<DetailsTable fields={tl} item={data} /> <DetailsTable fields={tl} item={data} />
</Grid.Col> </Grid.Col>
</Grid> </Grid>

View File

@ -346,19 +346,16 @@ export default function StockDetail() {
return ( return (
<ItemDetailsGrid> <ItemDetailsGrid>
<Grid> <Grid grow>
<Grid.Col span={4}>
<DetailsImage <DetailsImage
appRole={UserRoles.part} appRole={UserRoles.part}
apiPath={ApiEndpoints.part_list} apiPath={ApiEndpoints.part_list}
src={ src={
stockitem.part_detail?.image ?? stockitem.part_detail?.image ?? stockitem?.part_detail?.thumbnail
stockitem?.part_detail?.thumbnail
} }
pk={stockitem.part} pk={stockitem.part}
/> />
</Grid.Col> <Grid.Col span={{ base: 12, sm: 8 }}>
<Grid.Col span={8}>
<DetailsTable fields={tl} item={data} /> <DetailsTable fields={tl} item={data} />
</Grid.Col> </Grid.Col>
</Grid> </Grid>