diff --git a/packages/webapp/package.json b/packages/webapp/package.json index d7bd5702e..effb13ba1 100644 --- a/packages/webapp/package.json +++ b/packages/webapp/package.json @@ -17,6 +17,8 @@ "@casl/ability": "^5.4.3", "@casl/react": "^2.3.0", "@craco/craco": "^5.9.0", + "@emotion/css": "^11.13.4", + "@emotion/react": "^11.13.3", "@reduxjs/toolkit": "^1.2.5", "@stripe/connect-js": "^3.3.12", "@stripe/react-connect-js": "^3.3.13", @@ -48,6 +50,7 @@ "@typescript-eslint/eslint-plugin": "^2.10.0", "@typescript-eslint/parser": "^2.10.0", "@welldone-software/why-did-you-render": "^6.0.0-rc.1", + "@xstyled/emotion": "^3.8.1", "accounting": "^0.4.1", "axios": "^1.6.0", "basscss": "^8.0.2", @@ -124,6 +127,7 @@ "style-loader": "0.23.1", "styled-components": "^5.3.1", "stylis-rtlcss": "^2.1.1", + "theme-ui": "^0.16.2", "typescript": "^4.8.3", "yup": "^0.28.1" }, diff --git a/packages/webapp/src/components/App.tsx b/packages/webapp/src/components/App.tsx index 5c62605f9..d54dcf905 100644 --- a/packages/webapp/src/components/App.tsx +++ b/packages/webapp/src/components/App.tsx @@ -35,6 +35,7 @@ const OneClickDemoPage = lazy( const PaymentPortalPage = lazy( () => import('@/containers/PaymentPortal/PaymentPortalPage'), ); + /** * App inner. */ @@ -59,7 +60,10 @@ function AppInsider({ history }) { children={} /> } /> - } /> + } + /> } /> diff --git a/packages/webapp/src/components/AppIntlProvider.tsx b/packages/webapp/src/components/AppIntlProvider.tsx index 24dd05be0..595a4a839 100644 --- a/packages/webapp/src/components/AppIntlProvider.tsx +++ b/packages/webapp/src/components/AppIntlProvider.tsx @@ -1,12 +1,31 @@ // @ts-nocheck import React, { createContext } from 'react'; -const AppIntlContext = createContext(); +interface AppIntlContextValue { + currentLocale: string; + direction: 'rtl' | 'ltr'; + isRTL: boolean; + isLTR: boolean; +} + +const AppIntlContext = createContext( + {} as AppIntlContextValue, +); + +interface AppIntlProviderProps { + currentLocale: string; + isRTL: boolean; + children: React.ReactNode; +} /** * Application intl provider. */ -function AppIntlProvider({ currentLocale, isRTL, children }) { +function AppIntlProvider({ + currentLocale, + isRTL, + children, +}: AppIntlProviderProps) { const provider = { currentLocale, isRTL, @@ -21,6 +40,7 @@ function AppIntlProvider({ currentLocale, isRTL, children }) { ); } -const useAppIntlContext = () => React.useContext(AppIntlContext); +const useAppIntlContext = () => + React.useContext(AppIntlContext); export { AppIntlProvider, useAppIntlContext }; diff --git a/packages/webapp/src/components/Dashboard/DashboardInsider.tsx b/packages/webapp/src/components/Dashboard/DashboardInsider.tsx index f46dae7bb..3b1217816 100644 --- a/packages/webapp/src/components/Dashboard/DashboardInsider.tsx +++ b/packages/webapp/src/components/Dashboard/DashboardInsider.tsx @@ -2,6 +2,7 @@ import React from 'react'; import classnames from 'classnames'; import { LoadingIndicator } from '../Indicator'; +import { css } from '@emotion/css'; export function DashboardInsider({ loading, @@ -9,6 +10,7 @@ export function DashboardInsider({ name, mount = false, className, + style }) { return (
{children} diff --git a/packages/webapp/src/components/Dashboard/DashboardThemeProvider.tsx b/packages/webapp/src/components/Dashboard/DashboardThemeProvider.tsx index 763e5e773..fc7f657a9 100644 --- a/packages/webapp/src/components/Dashboard/DashboardThemeProvider.tsx +++ b/packages/webapp/src/components/Dashboard/DashboardThemeProvider.tsx @@ -1,9 +1,20 @@ -// @ts-nocheck import React from 'react'; -import { ThemeProvider, StyleSheetManager } from 'styled-components'; +import { + ThemeProvider as StyleComponentsThemeProvider, + StyleSheetManager, +} from 'styled-components'; import rtlcss from 'stylis-rtlcss'; +import { + defaultTheme, + ThemeProvider as XStyledEmotionThemeProvider, +} from '@xstyled/emotion'; import { useAppIntlContext } from '../AppIntlProvider'; +const theme = { + ...defaultTheme, + bpPrefix: 'bp4', +}; + interface DashboardThemeProviderProps { children: React.ReactNode; } @@ -17,7 +28,11 @@ export function DashboardThemeProvider({ - {children} + + + {children} + + ); } diff --git a/packages/webapp/src/components/Layout/Box/Box.tsx b/packages/webapp/src/components/Layout/Box/Box.tsx index 9d104cf58..589ab9ce3 100644 --- a/packages/webapp/src/components/Layout/Box/Box.tsx +++ b/packages/webapp/src/components/Layout/Box/Box.tsx @@ -1,13 +1,15 @@ import React, { forwardRef, Ref } from 'react'; import { HTMLDivProps, Props } from '@blueprintjs/core'; +import { SystemProps, x } from '@xstyled/emotion'; -export interface BoxProps extends Props, HTMLDivProps { - className?: string; -} +export interface BoxProps + extends SystemProps, + Props, + Omit {} export const Box = forwardRef( ({ className, ...rest }: BoxProps, ref: Ref) => { - const Element = 'div'; + const Element = x.div; return ; }, diff --git a/packages/webapp/src/components/Layout/Group/Group.tsx b/packages/webapp/src/components/Layout/Group/Group.tsx index ee0174598..761b65df9 100644 --- a/packages/webapp/src/components/Layout/Group/Group.tsx +++ b/packages/webapp/src/components/Layout/Group/Group.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import styled from 'styled-components'; +import { SystemProps } from '@xstyled/emotion'; import { Box } from '../Box'; import { filterFalsyChildren } from './_utils'; @@ -12,7 +12,9 @@ export const GROUP_POSITIONS = { apart: 'space-between', }; -export interface GroupProps extends React.ComponentPropsWithoutRef<'div'> { +export interface GroupProps + extends SystemProps, + Omit, 'color'> { /** Defines justify-content property */ position?: GroupPosition; @@ -27,34 +29,30 @@ export interface GroupProps extends React.ComponentPropsWithoutRef<'div'> { /** Defines align-items css property */ align?: React.CSSProperties['alignItems']; - - flex?: React.CSSProperties['flex']; } -const defaultProps: Partial = { - position: 'left', - spacing: 20, - flex: 'none' -}; - -export function Group({ children, ...props }: GroupProps) { - const groupProps = { - ...defaultProps, - ...props, - }; +export function Group({ + position = 'left', + spacing = 20, + align = 'center', + noWrap, + children, + ...props +}: GroupProps) { const filteredChildren = filterFalsyChildren(children); - return {filteredChildren}; + return ( + + {filteredChildren} + + ); } - -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; -`; diff --git a/packages/webapp/src/components/Layout/Stack/Stack.tsx b/packages/webapp/src/components/Layout/Stack/Stack.tsx index 3ca710402..49ca61ce1 100644 --- a/packages/webapp/src/components/Layout/Stack/Stack.tsx +++ b/packages/webapp/src/components/Layout/Stack/Stack.tsx @@ -1,8 +1,9 @@ import React from 'react'; -import styled from 'styled-components'; -import { Box } from '../Box'; +import { x, SystemProps } from '@xstyled/emotion'; -export interface StackProps extends React.ComponentPropsWithoutRef<'div'> { +export interface StackProps + extends SystemProps, + Omit, 'color'> { /** Key of theme.spacing or number to set gap in px */ spacing?: number; @@ -11,30 +12,22 @@ export interface StackProps extends React.ComponentPropsWithoutRef<'div'> { /** justify-content CSS property */ justify?: React.CSSProperties['justifyContent']; - - flex?: React.CSSProperties['flex']; } -const defaultProps: Partial = { - spacing: 20, - align: 'stretch', - justify: 'top', - flex: 'none', -}; - -export function Stack(props: StackProps) { - const stackProps = { - ...defaultProps, - ...props, - }; - return ; +export function Stack({ + spacing = 20, + align = 'stretch', + justify = 'top', + ...restProps +}: StackProps) { + return ( + + ); } - -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}; -`; diff --git a/packages/webapp/src/components/PageForm/PageForm.tsx b/packages/webapp/src/components/PageForm/PageForm.tsx new file mode 100644 index 000000000..7d173359d --- /dev/null +++ b/packages/webapp/src/components/PageForm/PageForm.tsx @@ -0,0 +1,91 @@ +import React, { FC } from 'react'; +import clsx from 'classnames'; +import { x, SystemProps } from '@xstyled/emotion'; +import { css } from '@emotion/css'; +import { Group, GroupProps } from '@/components'; + +interface PageFormProps extends SystemProps { + children: React.ReactNode; +} + +/** + * Page form layout. + * @returns {React.ReactNode} + */ +export const PageForm = ({ children, ...props }: PageFormProps) => { + return ( + + {children} + + ); +}; +PageForm.displayName = 'PageFormBody'; + +/** + * Page form body layout, by default the content body is scrollable. + * @returns {React.ReactNode} + */ +const PageFormBody: FC<{ children: React.ReactNode } & SystemProps> = ({ + children, + ...props +}) => { + return ( + + {children} + + ); +}; +PageFormBody.displayName = 'PageFormBody'; + +/** + * Page form footer. + * @returns {React.ReactNode} + */ +const PageFormFooter: FC<{ children: React.ReactNode } & SystemProps> = ({ children }) => { + return {children} ; +}; + +PageFormFooter.displayName = 'PageFormFooter'; + +const footerActionsStyle = ` + width: 100%; + background: #fff; + padding: 14px 18px; + border-top: 1px solid rgb(210, 221, 226); + box-shadow: 0px -1px 4px 0px rgba(0, 0, 0, 0.05); + + .bp4-button-group{ + .bp4-button{ + &:not(:last-child), + &.bp4-popover-wrapper:not(:last-child) { + border-right: 1px solid rgba(92, 112, 127, 0.3); + margin-right: 0; + + &.bp4-intent-primary{ + border-right: 1px solid rgba(255, 255, 255, 0.3); + } + } + } + } +`; + +const PageFormFooterActions: FC = ({ + children, + className, + ...restProps +}) => { + return ( + + {children} + + ); +}; +PageFormFooterActions.displayName = 'PageFormFooterActions'; + +PageForm.Body = PageFormBody; +PageForm.Footer = PageFormFooter; +PageForm.FooterActions = PageFormFooterActions; diff --git a/packages/webapp/src/components/PageForm/index.ts b/packages/webapp/src/components/PageForm/index.ts index 56a507b60..90dc6f8e7 100644 --- a/packages/webapp/src/components/PageForm/index.ts +++ b/packages/webapp/src/components/PageForm/index.ts @@ -2,3 +2,4 @@ export * from './FormTopbar'; export * from './FormTopbarSelectInputs'; export * from './PageFormBigNumber'; +export * from './PageForm'; \ No newline at end of file diff --git a/packages/webapp/src/components/Paper/Paper.tsx b/packages/webapp/src/components/Paper/Paper.tsx index e8fe82bc5..91c9c7d2d 100644 --- a/packages/webapp/src/components/Paper/Paper.tsx +++ b/packages/webapp/src/components/Paper/Paper.tsx @@ -1,13 +1,20 @@ -// @ts-nocheck import React from 'react'; -import styled from 'styled-components'; +import { x, SystemProps } from '@xstyled/emotion'; -export function Paper({ children, className }) { - return {children}; +interface PaperProps extends SystemProps { + children: React.ReactNode; } -const PaperRoot = styled.div` - border: 1px solid #d2dce2; - background: #fff; - padding: 10px; -`; +export const Paper = ({ children, ...props }: PaperProps) => { + return ( + + {children} + + ); +}; +Paper.displayName = 'Paper'; diff --git a/packages/webapp/src/containers/Accounting/MakeJournal/MakeJournalFormFooter.tsx b/packages/webapp/src/containers/Accounting/MakeJournal/MakeJournalFormFooter.tsx index 1266dee98..fafaad27f 100644 --- a/packages/webapp/src/containers/Accounting/MakeJournal/MakeJournalFormFooter.tsx +++ b/packages/webapp/src/containers/Accounting/MakeJournal/MakeJournalFormFooter.tsx @@ -1,7 +1,6 @@ // @ts-nocheck import React from 'react'; import classNames from 'classnames'; -import styled from 'styled-components'; import { CLASSES } from '@/constants/classes'; import { Row, Col, Paper } from '@/components'; @@ -12,7 +11,7 @@ import { UploadAttachmentButton } from '@/containers/Attachments/UploadAttachmen export default function MakeJournalFormFooter() { return (
- + @@ -23,10 +22,7 @@ export default function MakeJournalFormFooter() { - +
); } -const MakeJournalFooterPaper = styled(Paper)` - padding: 20px; -`; diff --git a/packages/webapp/src/containers/Expenses/ExpenseForm/ExpenseFormFooter.tsx b/packages/webapp/src/containers/Expenses/ExpenseForm/ExpenseFormFooter.tsx index 4178c27ca..8c1c093ce 100644 --- a/packages/webapp/src/containers/Expenses/ExpenseForm/ExpenseFormFooter.tsx +++ b/packages/webapp/src/containers/Expenses/ExpenseForm/ExpenseFormFooter.tsx @@ -1,7 +1,6 @@ // @ts-nocheck import React from 'react'; import classNames from 'classnames'; -import styled from 'styled-components'; import { CLASSES } from '@/constants/classes'; import { Row, Col, Paper } from '@/components'; @@ -12,7 +11,7 @@ import { UploadAttachmentButton } from '@/containers/Attachments/UploadAttachmen export default function ExpenseFormFooter() { return (
- + @@ -23,11 +22,7 @@ export default function ExpenseFormFooter() { - +
); } - -const ExpensesFooterPaper = styled(Paper)` - padding: 20px; -`; diff --git a/packages/webapp/src/containers/Purchases/Bills/BillForm/BillFormFooter.tsx b/packages/webapp/src/containers/Purchases/Bills/BillForm/BillFormFooter.tsx index da530548b..b89c5b7d3 100644 --- a/packages/webapp/src/containers/Purchases/Bills/BillForm/BillFormFooter.tsx +++ b/packages/webapp/src/containers/Purchases/Bills/BillForm/BillFormFooter.tsx @@ -13,7 +13,7 @@ import { UploadAttachmentButton } from '@/containers/Attachments/UploadAttachmen export default function BillFormFooter() { return (
- + @@ -24,11 +24,7 @@ export default function BillFormFooter() { - +
); } - -const BillFooterPaper = styled(Paper)` - padding: 20px; -`; diff --git a/packages/webapp/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFormFooter.tsx b/packages/webapp/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFormFooter.tsx index 7f91451f1..510d6f556 100644 --- a/packages/webapp/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFormFooter.tsx +++ b/packages/webapp/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFormFooter.tsx @@ -15,7 +15,7 @@ import { UploadAttachmentButton } from '@/containers/Attachments/UploadAttachmen export default function VendorCreditNoteFormFooter() { return (
- + @@ -26,11 +26,7 @@ export default function VendorCreditNoteFormFooter() { - +
); } - -const VendorCreditNoteFooterPaper = styled(Paper)` - padding: 20px; -`; diff --git a/packages/webapp/src/containers/Purchases/PaymentsMade/PaymentForm/PaymentMadeFooter.tsx b/packages/webapp/src/containers/Purchases/PaymentsMade/PaymentForm/PaymentMadeFooter.tsx index 8db56462e..9db847bf9 100644 --- a/packages/webapp/src/containers/Purchases/PaymentsMade/PaymentForm/PaymentMadeFooter.tsx +++ b/packages/webapp/src/containers/Purchases/PaymentsMade/PaymentForm/PaymentMadeFooter.tsx @@ -15,7 +15,7 @@ import { UploadAttachmentButton } from '@/containers/Attachments/UploadAttachmen export default function PaymentMadeFooter() { return (
- + @@ -26,11 +26,7 @@ export default function PaymentMadeFooter() { - +
); } - -const PaymentReceiveFooterPaper = styled(Paper)` - padding: 20px; -`; diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFloatingActions.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFloatingActions.tsx index 85bd40d93..9fa8baec9 100644 --- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFloatingActions.tsx +++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFloatingActions.tsx @@ -12,9 +12,14 @@ import { Menu, MenuItem, } from '@blueprintjs/core'; -import { If, Icon, FormattedMessage as T, Group, FSelect } from '@/components'; -import { CLASSES } from '@/constants/classes'; -import classNames from 'classnames'; +import { + If, + Icon, + FormattedMessage as T, + Group, + FSelect, + PageForm, +} from '@/components'; import { useCreditNoteFormContext } from './CreditNoteFormProvider'; import { BrandingThemeFormGroup, @@ -82,140 +87,141 @@ export default function CreditNoteFloatingActions() { const brandingTemplatesOptions = useCreditNoteFormBrandingTemplatesOptions(); return ( - - {/* ----------- Save And Open ----------- */} - - -
+ ); } diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptForm.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptForm.tsx index 94f8425a0..24ea92cae 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptForm.tsx +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptForm.tsx @@ -1,12 +1,11 @@ // @ts-nocheck import intl from 'react-intl-universal'; -import classNames from 'classnames'; import { Formik, Form } from 'formik'; import { Intent } from '@blueprintjs/core'; import { sumBy, isEmpty } from 'lodash'; import { useHistory } from 'react-router-dom'; +import { css } from '@emotion/css'; -import { CLASSES } from '@/constants/classes'; import { EditReceiptFormSchema, CreateReceiptFormSchema, @@ -17,7 +16,7 @@ import { useReceiptFormContext } from './ReceiptFormProvider'; import ReceiptFromHeader from './ReceiptFormHeader'; import ReceiptItemsEntriesEditor from './ReceiptItemsEntriesEditor'; import ReceiptFormFloatingActions from './ReceiptFormFloatingActions'; -import ReceiptFormFooter from './ReceiptFormFooter'; +import { ReceiptFormFooter } from './ReceiptFormFooter'; import ReceiptFormDialogs from './ReceiptFormDialogs'; import ReceiptFormTopBar from './ReceiptFormTopbar'; @@ -38,11 +37,12 @@ import { ReceiptSyncAutoExRateToForm, ReceiptSyncIncrementSettingsToForm, } from './components'; +import { PageForm } from '@/components/PageForm'; /** * Receipt form. */ -function ReceiptForm({ +function ReceiptFormRoot({ // #withSettings receiptNextNumber, receiptNumberPrefix, @@ -150,40 +150,46 @@ function ReceiptForm({ }; return ( -
- -
- - - - - + + + + + + + - {/*---------- Dialogs ---------*/} - -css - {/*---------- Effects ---------*/} - - - -
-
+ + + + + + {/*---------- Dialogs ---------*/} + + + {/*---------- Effects ---------*/} + + + + ); } -export default compose( +export const ReceiptForm = compose( withDashboardActions, withSettings(({ receiptSettings }) => ({ receiptNextNumber: receiptSettings?.nextNumber, @@ -194,4 +200,4 @@ export default compose( preferredDepositAccount: receiptSettings?.preferredDepositAccount, })), withCurrentOrganization(), -)(ReceiptForm); +)(ReceiptFormRoot); diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormFloatingActions.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormFloatingActions.tsx index 8e198ea27..48389542a 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormFloatingActions.tsx +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormFloatingActions.tsx @@ -1,6 +1,5 @@ // @ts-nocheck import React from 'react'; -import classNames from 'classnames'; import { Intent, Button, @@ -14,7 +13,6 @@ import { import { FSelect, Group, FormattedMessage as T } from '@/components'; import { useFormikContext } from 'formik'; import { useHistory } from 'react-router-dom'; -import { CLASSES } from '@/constants/classes'; import { If, Icon } from '@/components'; import { useReceiptFormContext } from './ReceiptFormProvider'; import { useReceiptFormBrandingTemplatesOptions } from './utils'; @@ -22,6 +20,7 @@ import { BrandingThemeFormGroup, BrandingThemeSelectButton, } from '@/containers/BrandingTemplates/BrandingTemplatesSelectFields'; +import { PageForm } from '@/components/PageForm'; /** * Receipt floating actions bar. @@ -84,139 +83,142 @@ export default function ReceiptFormFloatingActions() { const brandingTemplatesOptions = useReceiptFormBrandingTemplatesOptions(); return ( - - {/* ----------- Save And Close ----------- */} - - -