feat: Add css utilities to Box, Stack and Group components

This commit is contained in:
Ahmed Bouhuolia
2024-10-13 01:06:17 +02:00
parent b7b86bb0c5
commit ddea7be24a
18 changed files with 134 additions and 136 deletions

View File

@@ -1,13 +1,15 @@
import React, { forwardRef, Ref } from 'react'; import React, { forwardRef, Ref } from 'react';
import { HTMLDivProps, Props } from '@blueprintjs/core'; import { HTMLDivProps, Props } from '@blueprintjs/core';
import { SystemProps, x } from '@xstyled/emotion';
export interface BoxProps extends Props, HTMLDivProps { export interface BoxProps
className?: string; extends SystemProps,
} Props,
Omit<HTMLDivProps, 'color'> {}
export const Box = forwardRef( export const Box = forwardRef(
({ className, ...rest }: BoxProps, ref: Ref<HTMLDivElement>) => { ({ className, ...rest }: BoxProps, ref: Ref<HTMLDivElement>) => {
const Element = 'div'; const Element = x.div;
return <Element className={className} ref={ref} {...rest} />; return <Element className={className} ref={ref} {...rest} />;
}, },

View File

@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import styled from 'styled-components'; import { SystemProps } from '@xstyled/emotion';
import { Box } from '../Box'; import { Box } from '../Box';
import { filterFalsyChildren } from './_utils'; import { filterFalsyChildren } from './_utils';
@@ -12,7 +12,9 @@ export const GROUP_POSITIONS = {
apart: 'space-between', apart: 'space-between',
}; };
export interface GroupProps extends React.ComponentPropsWithoutRef<'div'> { export interface GroupProps
extends SystemProps,
Omit<React.ComponentPropsWithoutRef<'div'>, 'color'> {
/** Defines justify-content property */ /** Defines justify-content property */
position?: GroupPosition; position?: GroupPosition;
@@ -27,34 +29,30 @@ export interface GroupProps extends React.ComponentPropsWithoutRef<'div'> {
/** Defines align-items css property */ /** Defines align-items css property */
align?: React.CSSProperties['alignItems']; align?: React.CSSProperties['alignItems'];
flex?: React.CSSProperties['flex'];
} }
const defaultProps: Partial<GroupProps> = { export function Group({
position: 'left', position = 'left',
spacing: 20, spacing = 20,
flex: 'none' align = 'center',
}; noWrap,
children,
export function Group({ children, ...props }: GroupProps) { ...props
const groupProps = { }: GroupProps) {
...defaultProps,
...props,
};
const filteredChildren = filterFalsyChildren(children); const filteredChildren = filterFalsyChildren(children);
return <GroupStyled {...groupProps}>{filteredChildren}</GroupStyled>; return (
<Box
boxSizing={'border-box'}
display={'flex'}
flexDirection={'row'}
alignItems={align}
flexWrap={noWrap ? 'nowrap' : 'wrap'}
justifyContent={GROUP_POSITIONS[position]}
gap={`${spacing}px`}
{...props}
>
{filteredChildren}
</Box>
);
} }
const GroupStyled = styled(Box)`
box-sizing: border-box;
display: flex;
flex-direction: row;
flex: ${(props: GroupProps) => (props.flex)};
align-items: ${(props: GroupProps) => (props.align || 'center')};
flex-wrap: ${(props: GroupProps) => (props.noWrap ? 'nowrap' : 'wrap')};
justify-content: ${(props: GroupProps) =>
GROUP_POSITIONS[props.position || 'left']};
gap: ${(props: GroupProps) => props.spacing}px;
`;

View File

@@ -1,8 +1,9 @@
import React from 'react'; import React from 'react';
import styled from 'styled-components'; import { x, SystemProps } from '@xstyled/emotion';
import { Box } from '../Box';
export interface StackProps extends React.ComponentPropsWithoutRef<'div'> { export interface StackProps
extends SystemProps,
Omit<React.ComponentPropsWithoutRef<'div'>, 'color'> {
/** Key of theme.spacing or number to set gap in px */ /** Key of theme.spacing or number to set gap in px */
spacing?: number; spacing?: number;
@@ -11,30 +12,22 @@ export interface StackProps extends React.ComponentPropsWithoutRef<'div'> {
/** justify-content CSS property */ /** justify-content CSS property */
justify?: React.CSSProperties['justifyContent']; justify?: React.CSSProperties['justifyContent'];
flex?: React.CSSProperties['flex'];
} }
const defaultProps: Partial<StackProps> = { export function Stack({
spacing: 20, spacing = 20,
align: 'stretch', align = 'stretch',
justify: 'top', justify = 'top',
flex: 'none', ...restProps
}; }: StackProps) {
return (
export function Stack(props: StackProps) { <x.div
const stackProps = { display={'flex'}
...defaultProps, flexDirection="column"
...props, justifyContent="justify"
}; gap={`${spacing}px`}
return <StackStyled {...stackProps} />; alignItems={align}
{...restProps}
/>
);
} }
const StackStyled = styled(Box)`
display: flex;
flex-direction: column;
align-items: ${(props: StackProps) => props.align};
justify-content: justify;
gap: ${(props: StackProps) => props.spacing}px;
flex: ${(props: StackProps) => props.flex};
`;

View File

@@ -1,10 +1,7 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import classNames from 'classnames';
import styled from 'styled-components';
import { CLASSES } from '@/constants/classes'; import { Row, Col, Paper, Stack } from '@/components';
import { Row, Col, Paper } from '@/components';
import { CreditNoteFormFooterLeft } from './CreditNoteFormFooterLeft'; import { CreditNoteFormFooterLeft } from './CreditNoteFormFooterLeft';
import { CreditNoteFormFooterRight } from './CreditNoteFormFooterRight'; import { CreditNoteFormFooterRight } from './CreditNoteFormFooterRight';
import { UploadAttachmentButton } from '@/containers/Attachments/UploadAttachmentButton'; import { UploadAttachmentButton } from '@/containers/Attachments/UploadAttachmentButton';
@@ -14,8 +11,8 @@ import { UploadAttachmentButton } from '@/containers/Attachments/UploadAttachmen
*/ */
export default function CreditNoteFormFooter() { export default function CreditNoteFormFooter() {
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}> <Stack mt={'20px'} px={'32px'} pb={'20px'} flex={1}>
<CreditNoteFooterPaper> <Paper p={'20px'}>
<Row> <Row>
<Col md={8}> <Col md={8}>
<CreditNoteFormFooterLeft /> <CreditNoteFormFooterLeft />
@@ -26,10 +23,7 @@ export default function CreditNoteFormFooter() {
<CreditNoteFormFooterRight /> <CreditNoteFormFooterRight />
</Col> </Col>
</Row> </Row>
</CreditNoteFooterPaper> </Paper>
</div> </Stack>
); );
} }
const CreditNoteFooterPaper = styled(Paper)`
padding: 20px;
`;

View File

@@ -1,23 +1,28 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import classNames from 'classnames';
import { useFormikContext } from 'formik'; import { useFormikContext } from 'formik';
import { CLASSES } from '@/constants/classes';
import CreditNoteFormHeaderFields from './CreditNoteFormHeaderFields'; import CreditNoteFormHeaderFields from './CreditNoteFormHeaderFields';
import { getEntriesTotal } from '@/containers/Entries/utils'; import { getEntriesTotal } from '@/containers/Entries/utils';
import { PageFormBigNumber } from '@/components'; import { Group, PageFormBigNumber } from '@/components';
/** /**
* Credit note header. * Credit note header.
*/ */
function CreditNoteFormHeader() { function CreditNoteFormHeader() {
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER)}> <Group
position="apart"
align={'flex-start'}
display="flex"
bg="white"
p="25px 32px"
borderBottom="1px solid #d2dce2"
>
<CreditNoteFormHeaderFields /> <CreditNoteFormHeaderFields />
<CreditNoteFormBigNumber /> <CreditNoteFormBigNumber />
</div> </Group>
); );
} }

View File

@@ -1,11 +1,10 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import classNames from 'classnames';
import { FastField } from 'formik'; import { FastField } from 'formik';
import { CLASSES } from '@/constants/classes';
import ItemsEntriesTable from '@/containers/Entries/ItemsEntriesTable'; import ItemsEntriesTable from '@/containers/Entries/ItemsEntriesTable';
import { useCreditNoteFormContext } from './CreditNoteFormProvider'; import { useCreditNoteFormContext } from './CreditNoteFormProvider';
import { entriesFieldShouldUpdate } from './utils'; import { entriesFieldShouldUpdate } from './utils';
import { Box } from '@/components';
/** /**
* Credit note items entries editor field. * Credit note items entries editor field.
@@ -14,7 +13,7 @@ export default function CreditNoteItemsEntriesEditorField() {
const { items } = useCreditNoteFormContext(); const { items } = useCreditNoteFormContext();
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_BODY)}> <Box p="18px 32px 0">
<FastField <FastField
name={'entries'} name={'entries'}
items={items} items={items}
@@ -38,6 +37,6 @@ export default function CreditNoteItemsEntriesEditorField() {
/> />
)} )}
</FastField> </FastField>
</div> </Box>
); );
} }

View File

@@ -6,20 +6,21 @@ import { x } from '@xstyled/emotion';
import EstimateFormHeaderFields from './EstimateFormHeaderFields'; import EstimateFormHeaderFields from './EstimateFormHeaderFields';
import { getEntriesTotal } from '@/containers/Entries/utils'; import { getEntriesTotal } from '@/containers/Entries/utils';
import { PageFormBigNumber } from '@/components'; import { Group, PageFormBigNumber } from '@/components';
// Estimate form top header. // Estimate form top header.
function EstimateFormHeader() { function EstimateFormHeader() {
return ( return (
<x.div <Group
display="flex" position="apart"
align={'flex-start'}
bg="white" bg="white"
p="25px 32px" p="25px 32px"
borderBottom="1px solid #d2dce2" borderBottom="1px solid #d2dce2"
> >
<EstimateFormHeaderFields /> <EstimateFormHeaderFields />
<EstimateFormBigTotal /> <EstimateFormBigTotal />
</x.div> </Group>
); );
} }

View File

@@ -1,8 +1,7 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import classNames from 'classnames'; import { x } from '@xstyled/emotion';
import { FastField } from 'formik'; import { FastField } from 'formik';
import { CLASSES } from '@/constants/classes';
import ItemsEntriesTable from '@/containers/Entries/ItemsEntriesTable'; import ItemsEntriesTable from '@/containers/Entries/ItemsEntriesTable';
import { useEstimateFormContext } from './EstimateFormProvider'; import { useEstimateFormContext } from './EstimateFormProvider';
import { entriesFieldShouldUpdate } from './utils'; import { entriesFieldShouldUpdate } from './utils';
@@ -14,7 +13,7 @@ export default function EstimateFormItemsEntriesField() {
const { items } = useEstimateFormContext(); const { items } = useEstimateFormContext();
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_BODY)}> <x.div p="18px 32px 0">
<FastField <FastField
name={'entries'} name={'entries'}
items={items} items={items}
@@ -38,6 +37,6 @@ export default function EstimateFormItemsEntriesField() {
/> />
)} )}
</FastField> </FastField>
</div> </x.div>
); );
} }

View File

@@ -2,10 +2,8 @@
import React from 'react'; import React from 'react';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { useFormikContext } from 'formik'; import { useFormikContext } from 'formik';
import { x } from '@xstyled/emotion'; import { Group, PageFormBigNumber } from '@/components';
import InvoiceFormHeaderFields from './InvoiceFormHeaderFields'; import InvoiceFormHeaderFields from './InvoiceFormHeaderFields';
import { PageFormBigNumber } from '@/components';
import { useInvoiceDueAmount } from './utils'; import { useInvoiceDueAmount } from './utils';
/** /**
@@ -13,15 +11,16 @@ import { useInvoiceDueAmount } from './utils';
*/ */
function InvoiceFormHeader() { function InvoiceFormHeader() {
return ( return (
<x.div <Group
display="flex" position="apart"
align={'flex-start'}
bg="white" bg="white"
p="25px 32px" p="25px 32px"
borderBottom="1px solid #d2dce2" borderBottom="1px solid #d2dce2"
> >
<InvoiceFormHeaderFields /> <InvoiceFormHeaderFields />
<InvoiceFormBigTotal /> <InvoiceFormBigTotal />
</x.div> </Group>
); );
} }

View File

@@ -2,15 +2,14 @@
import React from 'react'; import React from 'react';
import { FastField } from 'formik'; import { FastField } from 'formik';
import PaymentReceiveItemsTable from './PaymentReceiveItemsTable'; import PaymentReceiveItemsTable from './PaymentReceiveItemsTable';
import classNames from 'classnames'; import { Box } from '@/components';
import { CLASSES } from '@/constants/classes';
/** /**
* Payment Receive form body. * Payment Receive form body.
*/ */
export default function PaymentReceiveFormBody() { export default function PaymentReceiveFormBody() {
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_BODY)}> <Box p="18px 32px 0">
<FastField name={'entries'}> <FastField name={'entries'}>
{({ form: { values, setFieldValue }, field: { value } }) => ( {({ form: { values, setFieldValue }, field: { value } }) => (
<PaymentReceiveItemsTable <PaymentReceiveItemsTable
@@ -22,6 +21,6 @@ export default function PaymentReceiveFormBody() {
/> />
)} )}
</FastField> </FastField>
</div> </Box>
); );
} }

View File

@@ -2,7 +2,7 @@
import React from 'react'; import React from 'react';
import { x } from '@xstyled/emotion'; import { x } from '@xstyled/emotion';
import { Row, Col, Paper } from '@/components'; import { Row, Col, Paper, Box } from '@/components';
import { PaymentReceiveFormFootetLeft } from './PaymentReceiveFormFootetLeft'; import { PaymentReceiveFormFootetLeft } from './PaymentReceiveFormFootetLeft';
import { PaymentReceiveFormFootetRight } from './PaymentReceiveFormFootetRight'; import { PaymentReceiveFormFootetRight } from './PaymentReceiveFormFootetRight';
import { UploadAttachmentButton } from '@/containers/Attachments/UploadAttachmentButton'; import { UploadAttachmentButton } from '@/containers/Attachments/UploadAttachmentButton';
@@ -12,7 +12,7 @@ import { UploadAttachmentButton } from '@/containers/Attachments/UploadAttachmen
*/ */
export default function PaymentReceiveFormFooter() { export default function PaymentReceiveFormFooter() {
return ( return (
<x.div mt={'20px'} px={'32px'} pb={'20px'} flex={1}> <Box mt={'20px'} px={'32px'} pb={'20px'} flex={1}>
<Paper p={'20px'}> <Paper p={'20px'}>
<Row> <Row>
<Col md={8}> <Col md={8}>
@@ -25,6 +25,6 @@ export default function PaymentReceiveFormFooter() {
</Col> </Col>
</Row> </Row>
</Paper> </Paper>
</x.div> </Box>
); );
} }

View File

@@ -3,7 +3,7 @@ import React, { useMemo } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { sumBy } from 'lodash'; import { sumBy } from 'lodash';
import { useFormikContext } from 'formik'; import { useFormikContext } from 'formik';
import { Money } from '@/components'; import { Group, Money } from '@/components';
import { FormattedMessage as T } from '@/components'; import { FormattedMessage as T } from '@/components';
import { CLASSES } from '@/constants/classes'; import { CLASSES } from '@/constants/classes';
@@ -14,12 +14,16 @@ import PaymentReceiveHeaderFields from './PaymentReceiveHeaderFields';
*/ */
function PaymentReceiveFormHeader() { function PaymentReceiveFormHeader() {
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER)}> <Group
<div className={classNames(CLASSES.PAGE_FORM_HEADER_PRIMARY)}> position="apart"
<PaymentReceiveHeaderFields /> align={'flex-start'}
<PaymentReceiveFormBigTotal /> bg="white"
</div> p="25px 32px"
</div> borderBottom="1px solid #d2dce2"
>
<PaymentReceiveHeaderFields />
<PaymentReceiveFormBigTotal />
</Group>
); );
} }

View File

@@ -1,7 +1,7 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { css } from '@xstyled/emotion'; import { css } from '@emotion/css';
import { import {
PaymentReceiveFormProvider, PaymentReceiveFormProvider,
usePaymentReceiveFormContext, usePaymentReceiveFormContext,

View File

@@ -16,7 +16,7 @@ import { useReceiptFormContext } from './ReceiptFormProvider';
import ReceiptFromHeader from './ReceiptFormHeader'; import ReceiptFromHeader from './ReceiptFormHeader';
import ReceiptItemsEntriesEditor from './ReceiptItemsEntriesEditor'; import ReceiptItemsEntriesEditor from './ReceiptItemsEntriesEditor';
import ReceiptFormFloatingActions from './ReceiptFormFloatingActions'; import ReceiptFormFloatingActions from './ReceiptFormFloatingActions';
import ReceiptFormFooter from './ReceiptFormFooter'; import { ReceiptFormFooter } from './ReceiptFormFooter';
import ReceiptFormDialogs from './ReceiptFormDialogs'; import ReceiptFormDialogs from './ReceiptFormDialogs';
import ReceiptFormTopBar from './ReceiptFormTopbar'; import ReceiptFormTopBar from './ReceiptFormTopbar';
@@ -162,7 +162,7 @@ function ReceiptFormRoot({
overflow: 'hidden', overflow: 'hidden',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
flex: 1 flex: 1,
})} })}
> >
<PageForm flex={1}> <PageForm flex={1}>

View File

@@ -1,18 +1,15 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import classNames from 'classnames'; import { x } from '@xstyled/emotion';
import styled from 'styled-components';
import { CLASSES } from '@/constants/classes';
import { Paper, Row, Col } from '@/components'; import { Paper, Row, Col } from '@/components';
import { ReceiptFormFooterLeft } from './ReceiptFormFooterLeft'; import { ReceiptFormFooterLeft } from './ReceiptFormFooterLeft';
import { ReceiptFormFooterRight } from './ReceiptFormFooterRight'; import { ReceiptFormFooterRight } from './ReceiptFormFooterRight';
import { UploadAttachmentButton } from '@/containers/Attachments/UploadAttachmentButton'; import { UploadAttachmentButton } from '@/containers/Attachments/UploadAttachmentButton';
export default function ReceiptFormFooter({}) { export function ReceiptFormFooter({}) {
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}> <x.div mt={'20px'} px={'32px'} pb={'20px'} flex={1}>
<ReceiptFooterPaper> <Paper p={'20px'}>
<Row> <Row>
<Col md={8}> <Col md={8}>
<ReceiptFormFooterLeft /> <ReceiptFormFooterLeft />
@@ -23,11 +20,7 @@ export default function ReceiptFormFooter({}) {
<ReceiptFormFooterRight /> <ReceiptFormFooterRight />
</Col> </Col>
</Row> </Row>
</ReceiptFooterPaper> </Paper>
</div> </x.div>
); );
} }
const ReceiptFooterPaper = styled(Paper)`
padding: 20px;
`;

View File

@@ -1,13 +1,9 @@
// @ts-nocheck // @ts-nocheck
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import classNames from 'classnames';
import { useFormikContext } from 'formik'; import { useFormikContext } from 'formik';
import { Group, PageFormBigNumber } from '@/components';
import { CLASSES } from '@/constants/classes';
import { PageFormBigNumber } from '@/components';
import ReceiptFormHeaderFields from './ReceiptFormHeaderFields'; import ReceiptFormHeaderFields from './ReceiptFormHeaderFields';
import { getEntriesTotal } from '@/containers/Entries/utils'; import { getEntriesTotal } from '@/containers/Entries/utils';
/** /**
@@ -18,12 +14,18 @@ function ReceiptFormHeader({
onReceiptNumberChanged, onReceiptNumberChanged,
}) { }) {
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER)}> <Group
position="apart"
align={'flex-start'}
display="flex"
bg="white"
p="25px 32px"
>
<ReceiptFormHeaderFields <ReceiptFormHeaderFields
onReceiptNumberChanged={onReceiptNumberChanged} onReceiptNumberChanged={onReceiptNumberChanged}
/> />
<ReceiptFormHeaderBigTotal /> <ReceiptFormHeaderBigTotal />
</div> </Group>
); );
} }

View File

@@ -1,6 +1,7 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { css } from '@emotion/css';
import { import {
ReceiptFormProvider, ReceiptFormProvider,
@@ -30,7 +31,13 @@ function ReceiptFormPageContent() {
const { isBootLoading } = useReceiptFormContext(); const { isBootLoading } = useReceiptFormContext();
return ( return (
<DashboardInsider loading={isBootLoading}> <DashboardInsider
loading={isBootLoading}
className={css`
min-height: calc(100vh - var(--top-offset));
max-height: calc(100vh - var(--top-offset));
`}
>
<ReceiptForm /> <ReceiptForm />
</DashboardInsider> </DashboardInsider>
); );

View File

@@ -1,8 +1,7 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import classNames from 'classnames'; import { x } from '@xstyled/emotion';
import { FastField } from 'formik'; import { FastField } from 'formik';
import { CLASSES } from '@/constants/classes';
import ItemsEntriesTable from '@/containers/Entries/ItemsEntriesTable'; import ItemsEntriesTable from '@/containers/Entries/ItemsEntriesTable';
import { useReceiptFormContext } from './ReceiptFormProvider'; import { useReceiptFormContext } from './ReceiptFormProvider';
import { entriesFieldShouldUpdate } from './utils'; import { entriesFieldShouldUpdate } from './utils';
@@ -11,8 +10,12 @@ export default function ReceiptItemsEntriesEditor({ defaultReceipt }) {
const { items } = useReceiptFormContext(); const { items } = useReceiptFormContext();
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_BODY)}> <x.div p="18px 32px 0">
<FastField name={'entries'} items={items} shouldUpdate={entriesFieldShouldUpdate}> <FastField
name={'entries'}
items={items}
shouldUpdate={entriesFieldShouldUpdate}
>
{({ {({
form: { values, setFieldValue }, form: { values, setFieldValue },
field: { value }, field: { value },
@@ -31,6 +34,6 @@ export default function ReceiptItemsEntriesEditor({ defaultReceipt }) {
/> />
)} )}
</FastField> </FastField>
</div> </x.div>
); );
} }