mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-22 07:40:32 +00:00
feat: render server-side invoice pdf template using React server
This commit is contained in:
@@ -20,11 +20,11 @@
|
|||||||
"bigcapital": "./bin/bigcapital.js"
|
"bigcapital": "./bin/bigcapital.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.576.0",
|
|
||||||
"@aws-sdk/s3-request-presigner": "^3.583.0",
|
|
||||||
"@bigcapital/utils": "*",
|
"@bigcapital/utils": "*",
|
||||||
"@bigcapital/email-components": "*",
|
"@bigcapital/email-components": "*",
|
||||||
"@bigcapital/pdf-templates": "*",
|
"@bigcapital/pdf-templates": "*",
|
||||||
|
"@aws-sdk/client-s3": "^3.576.0",
|
||||||
|
"@aws-sdk/s3-request-presigner": "^3.583.0",
|
||||||
"@casl/ability": "^5.4.3",
|
"@casl/ability": "^5.4.3",
|
||||||
"@hapi/boom": "^7.4.3",
|
"@hapi/boom": "^7.4.3",
|
||||||
"@lemonsqueezy/lemonsqueezy.js": "^2.2.0",
|
"@lemonsqueezy/lemonsqueezy.js": "^2.2.0",
|
||||||
|
|||||||
@@ -451,14 +451,14 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
ACCEPT_TYPE.APPLICATION_PDF,
|
ACCEPT_TYPE.APPLICATION_PDF,
|
||||||
]);
|
]);
|
||||||
// Retrieves invoice in pdf format.
|
// Retrieves invoice in pdf format.
|
||||||
if (ACCEPT_TYPE.APPLICATION_PDF == acceptType) {
|
if (true) {
|
||||||
const [pdfContent, filename] =
|
const [pdfContent, filename] =
|
||||||
await this.saleInvoiceApplication.saleInvoicePdf(
|
await this.saleInvoiceApplication.saleInvoicePdf(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleInvoiceId
|
saleInvoiceId
|
||||||
);
|
);
|
||||||
res.set({
|
res.set({
|
||||||
'Content-Type': 'application/pdf',
|
'Content-Type': 'text/html',
|
||||||
'Content-Length': pdfContent.length,
|
'Content-Length': pdfContent.length,
|
||||||
'Content-Disposition': `attachment; filename="${filename}"`,
|
'Content-Disposition': `attachment; filename="${filename}"`,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { renderInvoicePaperTemplateHtml } from '@bigcapital/pdf-templates';
|
||||||
import { ChromiumlyTenancy } from '@/services/ChromiumlyTenancy/ChromiumlyTenancy';
|
import { ChromiumlyTenancy } from '@/services/ChromiumlyTenancy/ChromiumlyTenancy';
|
||||||
import { TemplateInjectable } from '@/services/TemplateInjectable/TemplateInjectable';
|
import { TemplateInjectable } from '@/services/TemplateInjectable/TemplateInjectable';
|
||||||
import { GetSaleInvoice } from './GetSaleInvoice';
|
import { GetSaleInvoice } from './GetSaleInvoice';
|
||||||
@@ -8,6 +9,7 @@ import { InvoicePdfTemplateAttributes } from '@/interfaces';
|
|||||||
import { SaleInvoicePdfTemplate } from './SaleInvoicePdfTemplate';
|
import { SaleInvoicePdfTemplate } from './SaleInvoicePdfTemplate';
|
||||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
import events from '@/subscribers/events';
|
import events from '@/subscribers/events';
|
||||||
|
import { renderInvoicePaymentEmail } from '@bigcapital/email-components';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SaleInvoicePdf {
|
export class SaleInvoicePdf {
|
||||||
@@ -45,11 +47,16 @@ export class SaleInvoicePdf {
|
|||||||
tenantId,
|
tenantId,
|
||||||
invoiceId
|
invoiceId
|
||||||
);
|
);
|
||||||
const htmlContent = await this.templateInjectable.render(
|
// const htmlContent = await this.templateInjectable.render(
|
||||||
tenantId,
|
// tenantId,
|
||||||
'modules/invoice-standard',
|
// 'modules/invoice-standard',
|
||||||
brandingAttributes
|
// brandingAttributes
|
||||||
);
|
// );
|
||||||
|
|
||||||
|
const htmlContent = renderInvoicePaperTemplateHtml({});
|
||||||
|
|
||||||
|
console.log(htmlContent);
|
||||||
|
|
||||||
// Converts the given html content to pdf document.
|
// Converts the given html content to pdf document.
|
||||||
const buffer = await this.chromiumlyTenancy.convertHtmlContent(
|
const buffer = await this.chromiumlyTenancy.convertHtmlContent(
|
||||||
tenantId,
|
tenantId,
|
||||||
@@ -62,7 +69,7 @@ export class SaleInvoicePdf {
|
|||||||
events.saleInvoice.onPdfViewed,
|
events.saleInvoice.onPdfViewed,
|
||||||
eventPayload
|
eventPayload
|
||||||
);
|
);
|
||||||
return [buffer, filename];
|
return [htmlContent, filename];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
64
pnpm-lock.yaml
generated
64
pnpm-lock.yaml
generated
@@ -5991,7 +5991,7 @@ packages:
|
|||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
dependencies:
|
dependencies:
|
||||||
ajv: 6.12.6
|
ajv: 6.12.6
|
||||||
debug: 4.3.4
|
debug: 4.3.7(supports-color@5.5.0)
|
||||||
espree: 9.6.1
|
espree: 9.6.1
|
||||||
globals: 13.24.0
|
globals: 13.24.0
|
||||||
ignore: 5.3.1
|
ignore: 5.3.1
|
||||||
@@ -6138,7 +6138,7 @@ packages:
|
|||||||
engines: {node: '>=10.10.0'}
|
engines: {node: '>=10.10.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@humanwhocodes/object-schema': 2.0.3
|
'@humanwhocodes/object-schema': 2.0.3
|
||||||
debug: 4.3.4
|
debug: 4.3.7(supports-color@5.5.0)
|
||||||
minimatch: 3.1.2
|
minimatch: 3.1.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -6730,7 +6730,7 @@ packages:
|
|||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@lerna/create@8.1.3(typescript@4.9.5):
|
/@lerna/create@8.1.3(typescript@5.6.3):
|
||||||
resolution: {integrity: sha512-JFvIYrlvR8Txa8h7VZx8VIQDltukEKOKaZL/muGO7Q/5aE2vjOKHsD/jkWYe/2uFy1xv37ubdx17O1UXQNadPg==}
|
resolution: {integrity: sha512-JFvIYrlvR8Txa8h7VZx8VIQDltukEKOKaZL/muGO7Q/5aE2vjOKHsD/jkWYe/2uFy1xv37ubdx17O1UXQNadPg==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6745,7 +6745,7 @@ packages:
|
|||||||
columnify: 1.6.0
|
columnify: 1.6.0
|
||||||
conventional-changelog-core: 5.0.1
|
conventional-changelog-core: 5.0.1
|
||||||
conventional-recommended-bump: 7.0.1
|
conventional-recommended-bump: 7.0.1
|
||||||
cosmiconfig: 8.3.6(typescript@4.9.5)
|
cosmiconfig: 8.3.6(typescript@5.6.3)
|
||||||
dedent: 0.7.0
|
dedent: 0.7.0
|
||||||
execa: 5.0.0
|
execa: 5.0.0
|
||||||
fs-extra: 11.2.0
|
fs-extra: 11.2.0
|
||||||
@@ -15672,22 +15672,6 @@ packages:
|
|||||||
path-type: 4.0.0
|
path-type: 4.0.0
|
||||||
yaml: 1.10.2
|
yaml: 1.10.2
|
||||||
|
|
||||||
/cosmiconfig@8.3.6(typescript@4.9.5):
|
|
||||||
resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==}
|
|
||||||
engines: {node: '>=14'}
|
|
||||||
peerDependencies:
|
|
||||||
typescript: '>=4.9.5'
|
|
||||||
peerDependenciesMeta:
|
|
||||||
typescript:
|
|
||||||
optional: true
|
|
||||||
dependencies:
|
|
||||||
import-fresh: 3.3.0
|
|
||||||
js-yaml: 4.1.0
|
|
||||||
parse-json: 5.2.0
|
|
||||||
path-type: 4.0.0
|
|
||||||
typescript: 4.9.5
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/cosmiconfig@8.3.6(typescript@5.6.3):
|
/cosmiconfig@8.3.6(typescript@5.6.3):
|
||||||
resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==}
|
resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
@@ -23180,7 +23164,7 @@ packages:
|
|||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@lerna/create': 8.1.3(typescript@4.9.5)
|
'@lerna/create': 8.1.3(typescript@5.6.3)
|
||||||
'@npmcli/run-script': 7.0.2
|
'@npmcli/run-script': 7.0.2
|
||||||
'@nx/devkit': 19.0.7(nx@19.0.7)
|
'@nx/devkit': 19.0.7(nx@19.0.7)
|
||||||
'@octokit/plugin-enterprise-rest': 6.0.1
|
'@octokit/plugin-enterprise-rest': 6.0.1
|
||||||
@@ -23193,7 +23177,7 @@ packages:
|
|||||||
conventional-changelog-angular: 7.0.0
|
conventional-changelog-angular: 7.0.0
|
||||||
conventional-changelog-core: 5.0.1
|
conventional-changelog-core: 5.0.1
|
||||||
conventional-recommended-bump: 7.0.1
|
conventional-recommended-bump: 7.0.1
|
||||||
cosmiconfig: 8.3.6(typescript@4.9.5)
|
cosmiconfig: 8.3.6(typescript@5.6.3)
|
||||||
dedent: 0.7.0
|
dedent: 0.7.0
|
||||||
envinfo: 7.8.1
|
envinfo: 7.8.1
|
||||||
execa: 5.0.0
|
execa: 5.0.0
|
||||||
@@ -23245,7 +23229,7 @@ packages:
|
|||||||
strong-log-transformer: 2.1.0
|
strong-log-transformer: 2.1.0
|
||||||
tar: 6.2.1
|
tar: 6.2.1
|
||||||
temp-dir: 1.0.0
|
temp-dir: 1.0.0
|
||||||
typescript: 4.9.5
|
typescript: 5.6.3
|
||||||
upath: 2.0.1
|
upath: 2.0.1
|
||||||
uuid: 9.0.1
|
uuid: 9.0.1
|
||||||
validate-npm-package-license: 3.0.4
|
validate-npm-package-license: 3.0.4
|
||||||
@@ -28673,7 +28657,7 @@ packages:
|
|||||||
semver: 7.6.2
|
semver: 7.6.2
|
||||||
source-map-loader: 3.0.2(webpack@5.91.0)
|
source-map-loader: 3.0.2(webpack@5.91.0)
|
||||||
style-loader: 3.3.4(webpack@5.91.0)
|
style-loader: 3.3.4(webpack@5.91.0)
|
||||||
tailwindcss: 3.4.3(ts-node@10.9.2)
|
tailwindcss: 3.4.14(ts-node@10.9.2)
|
||||||
terser-webpack-plugin: 5.3.10(esbuild@0.23.1)(webpack@5.91.0)
|
terser-webpack-plugin: 5.3.10(esbuild@0.23.1)(webpack@5.91.0)
|
||||||
typescript: 4.9.5
|
typescript: 4.9.5
|
||||||
webpack: 5.91.0(esbuild@0.23.1)
|
webpack: 5.91.0(esbuild@0.23.1)
|
||||||
@@ -31440,37 +31424,6 @@ packages:
|
|||||||
- ts-node
|
- ts-node
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/tailwindcss@3.4.3(ts-node@10.9.2):
|
|
||||||
resolution: {integrity: sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==}
|
|
||||||
engines: {node: '>=14.0.0'}
|
|
||||||
hasBin: true
|
|
||||||
dependencies:
|
|
||||||
'@alloc/quick-lru': 5.2.0
|
|
||||||
arg: 5.0.2
|
|
||||||
chokidar: 3.6.0
|
|
||||||
didyoumean: 1.2.2
|
|
||||||
dlv: 1.1.3
|
|
||||||
fast-glob: 3.3.2
|
|
||||||
glob-parent: 6.0.2
|
|
||||||
is-glob: 4.0.3
|
|
||||||
jiti: 1.21.0
|
|
||||||
lilconfig: 2.1.0
|
|
||||||
micromatch: 4.0.7
|
|
||||||
normalize-path: 3.0.0
|
|
||||||
object-hash: 3.0.0
|
|
||||||
picocolors: 1.1.1
|
|
||||||
postcss: 8.4.47
|
|
||||||
postcss-import: 15.1.0(postcss@8.4.47)
|
|
||||||
postcss-js: 4.0.1(postcss@8.4.47)
|
|
||||||
postcss-load-config: 4.0.2(postcss@8.4.47)(ts-node@10.9.2)
|
|
||||||
postcss-nested: 6.0.1(postcss@8.4.47)
|
|
||||||
postcss-selector-parser: 6.1.0
|
|
||||||
resolve: 1.22.8
|
|
||||||
sucrase: 3.35.0
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- ts-node
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/tapable@0.1.10:
|
/tapable@0.1.10:
|
||||||
resolution: {integrity: sha512-jX8Et4hHg57mug1/079yitEKWGB3LCwoxByLsNim89LABq8NqgiX+6iYVOsq0vX8uJHkU+DZ5fnq95f800bEsQ==}
|
resolution: {integrity: sha512-jX8Et4hHg57mug1/079yitEKWGB3LCwoxByLsNim89LABq8NqgiX+6iYVOsq0vX8uJHkU+DZ5fnq95f800bEsQ==}
|
||||||
engines: {node: '>=0.6'}
|
engines: {node: '>=0.6'}
|
||||||
@@ -32439,6 +32392,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==}
|
resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==}
|
||||||
engines: {node: '>=4.2.0'}
|
engines: {node: '>=4.2.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
dev: false
|
||||||
|
|
||||||
/typescript@5.4.2:
|
/typescript@5.4.2:
|
||||||
resolution: {integrity: sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==}
|
resolution: {integrity: sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
export const Test = () => {
|
|
||||||
return <h1>asdasd</h1>;
|
|
||||||
};
|
|
||||||
343
shared/pdf-templates/src/components/InvoicePaperTemplate.tsx
Normal file
343
shared/pdf-templates/src/components/InvoicePaperTemplate.tsx
Normal file
@@ -0,0 +1,343 @@
|
|||||||
|
import { renderToString } from 'react-dom/server';
|
||||||
|
import {
|
||||||
|
PaperTemplate,
|
||||||
|
PaperTemplateProps,
|
||||||
|
PaperTemplateTotalBorder,
|
||||||
|
} from './PaperTemplate';
|
||||||
|
import { x } from '@xstyled/emotion';
|
||||||
|
import { Box } from '../lib/layout/Box';
|
||||||
|
import { Text } from '../lib/text/Text';
|
||||||
|
import { Stack } from '../lib/layout/Stack';
|
||||||
|
import { Group } from '../lib/layout/Group';
|
||||||
|
import {
|
||||||
|
DefaultPdfTemplateTerms,
|
||||||
|
DefaultPdfTemplateItemDescription,
|
||||||
|
DefaultPdfTemplateStatement,
|
||||||
|
DefaultPdfTemplateItemName,
|
||||||
|
DefaultPdfTemplateAddressBilledTo,
|
||||||
|
DefaultPdfTemplateAddressBilledFrom,
|
||||||
|
} from './_constants';
|
||||||
|
import { PaperTemplateLayout } from './PaperTemplateLayout';
|
||||||
|
import createCache from '@emotion/cache';
|
||||||
|
|
||||||
|
interface PapaerLine {
|
||||||
|
item?: string;
|
||||||
|
description?: string;
|
||||||
|
quantity?: string;
|
||||||
|
rate?: string;
|
||||||
|
total?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PaperTax {
|
||||||
|
label: string;
|
||||||
|
amount: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InvoicePaperTemplateProps extends PaperTemplateProps {
|
||||||
|
primaryColor?: string;
|
||||||
|
secondaryColor?: string;
|
||||||
|
|
||||||
|
showCompanyLogo?: boolean;
|
||||||
|
companyLogoUri?: string;
|
||||||
|
|
||||||
|
showInvoiceNumber?: boolean;
|
||||||
|
invoiceNumber?: string;
|
||||||
|
invoiceNumberLabel?: string;
|
||||||
|
|
||||||
|
showDateIssue?: boolean;
|
||||||
|
dateIssue?: string;
|
||||||
|
dateIssueLabel?: string;
|
||||||
|
|
||||||
|
showDueDate?: boolean;
|
||||||
|
dueDate?: string;
|
||||||
|
dueDateLabel?: string;
|
||||||
|
|
||||||
|
companyName?: string;
|
||||||
|
bigtitle?: string;
|
||||||
|
|
||||||
|
// Address
|
||||||
|
showCustomerAddress?: boolean;
|
||||||
|
customerAddress?: string;
|
||||||
|
|
||||||
|
showCompanyAddress?: boolean;
|
||||||
|
companyAddress?: string;
|
||||||
|
|
||||||
|
billedToLabel?: string;
|
||||||
|
|
||||||
|
// Entries
|
||||||
|
lineItemLabel?: string;
|
||||||
|
lineQuantityLabel?: string;
|
||||||
|
lineRateLabel?: string;
|
||||||
|
lineTotalLabel?: string;
|
||||||
|
|
||||||
|
// Totals
|
||||||
|
showTotal?: boolean;
|
||||||
|
totalLabel?: string;
|
||||||
|
total?: string;
|
||||||
|
|
||||||
|
showDiscount?: boolean;
|
||||||
|
discountLabel?: string;
|
||||||
|
discount?: string;
|
||||||
|
|
||||||
|
showSubtotal?: boolean;
|
||||||
|
subtotalLabel?: string;
|
||||||
|
subtotal?: string;
|
||||||
|
|
||||||
|
showPaymentMade?: boolean;
|
||||||
|
paymentMadeLabel?: string;
|
||||||
|
paymentMade?: string;
|
||||||
|
|
||||||
|
showTaxes?: boolean;
|
||||||
|
|
||||||
|
showDueAmount?: boolean;
|
||||||
|
showBalanceDue?: boolean;
|
||||||
|
balanceDueLabel?: string;
|
||||||
|
balanceDue?: string;
|
||||||
|
|
||||||
|
// Footer
|
||||||
|
termsConditionsLabel?: string;
|
||||||
|
showTermsConditions?: boolean;
|
||||||
|
termsConditions?: string;
|
||||||
|
|
||||||
|
statementLabel?: string;
|
||||||
|
showStatement?: boolean;
|
||||||
|
statement?: string;
|
||||||
|
|
||||||
|
lines?: Array<PapaerLine>;
|
||||||
|
taxes?: Array<PaperTax>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function InvoicePaperTemplate({
|
||||||
|
primaryColor,
|
||||||
|
secondaryColor,
|
||||||
|
|
||||||
|
companyName = 'Bigcapital Technology, Inc.',
|
||||||
|
|
||||||
|
showCompanyLogo = true,
|
||||||
|
companyLogoUri = '',
|
||||||
|
|
||||||
|
dueDate = 'September 3, 2024',
|
||||||
|
dueDateLabel = 'Date due',
|
||||||
|
showDueDate = true,
|
||||||
|
|
||||||
|
dateIssue = 'September 3, 2024',
|
||||||
|
dateIssueLabel = 'Date of issue',
|
||||||
|
showDateIssue = true,
|
||||||
|
|
||||||
|
// dateIssue,
|
||||||
|
invoiceNumberLabel = 'Invoice number',
|
||||||
|
invoiceNumber = '346D3D40-0001',
|
||||||
|
showInvoiceNumber = true,
|
||||||
|
|
||||||
|
// Address
|
||||||
|
showCustomerAddress = true,
|
||||||
|
customerAddress = DefaultPdfTemplateAddressBilledTo,
|
||||||
|
|
||||||
|
showCompanyAddress = true,
|
||||||
|
companyAddress = DefaultPdfTemplateAddressBilledFrom,
|
||||||
|
|
||||||
|
billedToLabel = 'Billed To',
|
||||||
|
|
||||||
|
// Entries
|
||||||
|
lineItemLabel = 'Item',
|
||||||
|
lineQuantityLabel = 'Qty',
|
||||||
|
lineRateLabel = 'Rate',
|
||||||
|
lineTotalLabel = 'Total',
|
||||||
|
|
||||||
|
totalLabel = 'Total',
|
||||||
|
subtotalLabel = 'Subtotal',
|
||||||
|
discountLabel = 'Discount',
|
||||||
|
paymentMadeLabel = 'Payment Made',
|
||||||
|
balanceDueLabel = 'Balance Due',
|
||||||
|
|
||||||
|
// Totals
|
||||||
|
showTotal = true,
|
||||||
|
showSubtotal = true,
|
||||||
|
showDiscount = true,
|
||||||
|
showTaxes = true,
|
||||||
|
showPaymentMade = true,
|
||||||
|
showDueAmount = true,
|
||||||
|
showBalanceDue = true,
|
||||||
|
|
||||||
|
total = '$662.75',
|
||||||
|
subtotal = '630.00',
|
||||||
|
discount = '0.00',
|
||||||
|
paymentMade = '100.00',
|
||||||
|
balanceDue = '$562.75',
|
||||||
|
|
||||||
|
// Footer paragraphs.
|
||||||
|
termsConditionsLabel = 'Terms & Conditions',
|
||||||
|
showTermsConditions = true,
|
||||||
|
termsConditions = DefaultPdfTemplateTerms,
|
||||||
|
|
||||||
|
lines = [
|
||||||
|
{
|
||||||
|
item: DefaultPdfTemplateItemName,
|
||||||
|
description: DefaultPdfTemplateItemDescription,
|
||||||
|
rate: '1',
|
||||||
|
quantity: '1000',
|
||||||
|
total: '$1000.00',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
taxes = [
|
||||||
|
{ label: 'Sample Tax1 (4.70%)', amount: '11.75' },
|
||||||
|
{ label: 'Sample Tax2 (7.00%)', amount: '21.74' },
|
||||||
|
],
|
||||||
|
|
||||||
|
statementLabel = 'Statement',
|
||||||
|
showStatement = true,
|
||||||
|
statement = DefaultPdfTemplateStatement,
|
||||||
|
...props
|
||||||
|
}: InvoicePaperTemplateProps) {
|
||||||
|
return (
|
||||||
|
<PaperTemplate
|
||||||
|
primaryColor={primaryColor}
|
||||||
|
secondaryColor={secondaryColor}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<Stack spacing={24}>
|
||||||
|
<Group align="start" spacing={10}>
|
||||||
|
<Stack flex={1}>
|
||||||
|
<PaperTemplate.BigTitle title={'Invoice'} />
|
||||||
|
|
||||||
|
<PaperTemplate.TermsList>
|
||||||
|
{showInvoiceNumber && (
|
||||||
|
<PaperTemplate.TermsItem label={invoiceNumberLabel}>
|
||||||
|
{invoiceNumber}
|
||||||
|
</PaperTemplate.TermsItem>
|
||||||
|
)}
|
||||||
|
{showDateIssue && (
|
||||||
|
<PaperTemplate.TermsItem label={dateIssueLabel}>
|
||||||
|
{dateIssue}
|
||||||
|
</PaperTemplate.TermsItem>
|
||||||
|
)}
|
||||||
|
{showDueDate && (
|
||||||
|
<PaperTemplate.TermsItem label={dueDateLabel}>
|
||||||
|
{dueDate}
|
||||||
|
</PaperTemplate.TermsItem>
|
||||||
|
)}
|
||||||
|
</PaperTemplate.TermsList>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
{companyLogoUri && showCompanyLogo && (
|
||||||
|
<PaperTemplate.Logo logoUri={companyLogoUri} />
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<PaperTemplate.AddressesGroup>
|
||||||
|
{showCompanyAddress && (
|
||||||
|
<PaperTemplate.Address>
|
||||||
|
<Box dangerouslySetInnerHTML={{ __html: companyAddress }} />
|
||||||
|
</PaperTemplate.Address>
|
||||||
|
)}
|
||||||
|
{showCustomerAddress && (
|
||||||
|
<PaperTemplate.Address>
|
||||||
|
<strong>{billedToLabel}</strong>
|
||||||
|
<Box dangerouslySetInnerHTML={{ __html: customerAddress }} />
|
||||||
|
</PaperTemplate.Address>
|
||||||
|
)}
|
||||||
|
</PaperTemplate.AddressesGroup>
|
||||||
|
|
||||||
|
<Stack spacing={0}>
|
||||||
|
<PaperTemplate.Table
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
label: lineItemLabel,
|
||||||
|
accessor: (data) => (
|
||||||
|
<Stack spacing={2}>
|
||||||
|
<Text>{data.item}</Text>
|
||||||
|
<Text
|
||||||
|
// variant={'muted'}
|
||||||
|
// style={{ fontSize: 12 }}
|
||||||
|
>
|
||||||
|
{data.description}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{ label: lineQuantityLabel, accessor: 'quantity' },
|
||||||
|
{ label: lineRateLabel, accessor: 'rate', align: 'right' },
|
||||||
|
{ label: lineTotalLabel, accessor: 'total', align: 'right' },
|
||||||
|
]}
|
||||||
|
data={lines}
|
||||||
|
/>
|
||||||
|
<PaperTemplate.Totals>
|
||||||
|
{showSubtotal && (
|
||||||
|
<PaperTemplate.TotalLine
|
||||||
|
label={subtotalLabel}
|
||||||
|
amount={subtotal}
|
||||||
|
border={PaperTemplateTotalBorder.Gray}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{showDiscount && (
|
||||||
|
<PaperTemplate.TotalLine
|
||||||
|
label={discountLabel}
|
||||||
|
amount={discount}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{showTaxes && (
|
||||||
|
<>
|
||||||
|
{taxes.map((tax, index) => (
|
||||||
|
<PaperTemplate.TotalLine
|
||||||
|
key={index}
|
||||||
|
label={tax.label}
|
||||||
|
amount={tax.amount}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{showTotal && (
|
||||||
|
<PaperTemplate.TotalLine
|
||||||
|
label={totalLabel}
|
||||||
|
amount={total}
|
||||||
|
border={PaperTemplateTotalBorder.Dark}
|
||||||
|
style={{ fontWeight: 500 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{showPaymentMade && (
|
||||||
|
<PaperTemplate.TotalLine
|
||||||
|
label={paymentMadeLabel}
|
||||||
|
amount={paymentMade}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{showBalanceDue && (
|
||||||
|
<PaperTemplate.TotalLine
|
||||||
|
label={balanceDueLabel}
|
||||||
|
amount={balanceDue}
|
||||||
|
border={PaperTemplateTotalBorder.Dark}
|
||||||
|
style={{ fontWeight: 500 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</PaperTemplate.Totals>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Stack spacing={0}>
|
||||||
|
{showTermsConditions && termsConditions && (
|
||||||
|
<PaperTemplate.Statement label={termsConditionsLabel}>
|
||||||
|
{termsConditions}
|
||||||
|
</PaperTemplate.Statement>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{showStatement && statement && (
|
||||||
|
<PaperTemplate.Statement label={statementLabel}>
|
||||||
|
{statement}
|
||||||
|
</PaperTemplate.Statement>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</PaperTemplate>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const renderInvoicePaperTemplateHtml = (
|
||||||
|
props: InvoicePaperTemplateProps
|
||||||
|
) => {
|
||||||
|
const key = 'custom';
|
||||||
|
const cache = createCache({ key });
|
||||||
|
|
||||||
|
return renderToString(
|
||||||
|
<PaperTemplateLayout cache={cache}>
|
||||||
|
<InvoicePaperTemplate {...props} />
|
||||||
|
</PaperTemplateLayout>
|
||||||
|
);
|
||||||
|
};
|
||||||
242
shared/pdf-templates/src/components/PaperTemplate.tsx
Normal file
242
shared/pdf-templates/src/components/PaperTemplate.tsx
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import clsx from 'classnames';
|
||||||
|
import { get, isFunction } from 'lodash';
|
||||||
|
import { x } from '@xstyled/emotion';
|
||||||
|
import { Box, BoxProps } from '../lib/layout/Box';
|
||||||
|
import { Group, GroupProps } from '../lib/layout/Group';
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
root: 'root',
|
||||||
|
bigTitle: 'bigTitle',
|
||||||
|
logoWrap: 'logoWrap',
|
||||||
|
logoImg: 'logoImg',
|
||||||
|
table: 'table',
|
||||||
|
tableBody: 'tableBody',
|
||||||
|
totals: 'totals',
|
||||||
|
totalsItem: 'totalsItem',
|
||||||
|
totalBottomBordered: 'totalBottomBordered',
|
||||||
|
totalBottomGrayBordered: 'totalBottomGrayBordered',
|
||||||
|
totalsItemLabel: 'totalsItemLabel',
|
||||||
|
totalsItemAmount: 'totalsItemAmount',
|
||||||
|
addressRoot: 'addressRoot',
|
||||||
|
paragraph: 'paragraph',
|
||||||
|
paragraphLabel: 'paragraphLabel',
|
||||||
|
details: 'details',
|
||||||
|
detail: 'detail',
|
||||||
|
detailLabel: 'detailLabel',
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface PaperTemplateProps extends BoxProps {
|
||||||
|
primaryColor?: string;
|
||||||
|
secondaryColor?: string;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PaperTemplate({
|
||||||
|
primaryColor,
|
||||||
|
secondaryColor,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: PaperTemplateProps) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
borderRadius="5px"
|
||||||
|
backgroundColor="#fff"
|
||||||
|
color="#111"
|
||||||
|
boxShadow="inset 0 4px 0px 0 var(--invoice-primary-color), 0 10px 15px rgba(0, 0, 0, 0.05)"
|
||||||
|
padding="30px 30px"
|
||||||
|
fontSize="12px"
|
||||||
|
position="relative"
|
||||||
|
m="0 auto"
|
||||||
|
h="1123px"
|
||||||
|
w="794px"
|
||||||
|
{...restProps}
|
||||||
|
className={clsx(styles.root, restProps?.className)}
|
||||||
|
>
|
||||||
|
<style>{`:root { --invoice-primary-color: ${primaryColor}; --invoice-secondary-color: ${secondaryColor}; }`}</style>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PaperTemplateTableProps {
|
||||||
|
columns: Array<{
|
||||||
|
accessor: string | ((data: Record<string, any>) => JSX.Element);
|
||||||
|
label: string;
|
||||||
|
value?: JSX.Element;
|
||||||
|
align?: 'left' | 'center' | 'right';
|
||||||
|
}>;
|
||||||
|
data: Array<Record<string, any>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PaperTemplateBigTitleProps {
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
PaperTemplate.BigTitle = ({ title }: PaperTemplateBigTitleProps) => {
|
||||||
|
return (
|
||||||
|
<x.h1
|
||||||
|
style={{
|
||||||
|
fontSize: '30px',
|
||||||
|
margin: 0,
|
||||||
|
lineHeight: 1,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: '#333',
|
||||||
|
}}
|
||||||
|
className={styles.bigTitle}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</x.h1>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface PaperTemplateLogoProps {
|
||||||
|
logoUri: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
PaperTemplate.Logo = ({ logoUri }: PaperTemplateLogoProps) => {
|
||||||
|
return (
|
||||||
|
<div className={styles.logoWrap}>
|
||||||
|
<img className={styles.logoImg} alt="" src={logoUri} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
PaperTemplate.Table = ({ columns, data }: PaperTemplateTableProps) => {
|
||||||
|
return (
|
||||||
|
<table className={styles.table}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{columns.map((col, index) => (
|
||||||
|
<th key={index} align={col.align}>
|
||||||
|
{col.label}
|
||||||
|
</th>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody className={styles.tableBody}>
|
||||||
|
{data.map((_data: any) => (
|
||||||
|
<tr>
|
||||||
|
{columns.map((column, index) => (
|
||||||
|
<td align={column.align} key={index}>
|
||||||
|
{isFunction(column?.accessor)
|
||||||
|
? column?.accessor(_data)
|
||||||
|
: get(_data, column.accessor)}
|
||||||
|
</td>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum PaperTemplateTotalBorder {
|
||||||
|
Gray = 'gray',
|
||||||
|
Dark = 'dark',
|
||||||
|
}
|
||||||
|
|
||||||
|
PaperTemplate.Totals = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
return (
|
||||||
|
<x.div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
marginLeft: 'auto',
|
||||||
|
width: '300px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</x.div>);
|
||||||
|
};
|
||||||
|
PaperTemplate.TotalLine = ({
|
||||||
|
label,
|
||||||
|
amount,
|
||||||
|
border,
|
||||||
|
style,
|
||||||
|
}: {
|
||||||
|
label: string;
|
||||||
|
amount: string;
|
||||||
|
border?: PaperTemplateTotalBorder;
|
||||||
|
style?: any;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<x.div
|
||||||
|
display={'flex'}
|
||||||
|
padding={'4px 0'}
|
||||||
|
|
||||||
|
className={clsx(styles.totalsItem, {
|
||||||
|
[styles.totalBottomBordered]: border === PaperTemplateTotalBorder.Dark,
|
||||||
|
[styles.totalBottomGrayBordered]:
|
||||||
|
border === PaperTemplateTotalBorder.Gray,
|
||||||
|
})}
|
||||||
|
style={style}
|
||||||
|
>
|
||||||
|
<x.div min-w="160px">{label}</x.div>
|
||||||
|
<x.div flex={'1 1 auto'} textAlign={'right'}>{amount}</x.div>
|
||||||
|
</x.div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
PaperTemplate.MutedText = () => { };
|
||||||
|
|
||||||
|
PaperTemplate.Text = () => { };
|
||||||
|
|
||||||
|
PaperTemplate.AddressesGroup = (props: GroupProps) => {
|
||||||
|
return (
|
||||||
|
<Group
|
||||||
|
spacing={10}
|
||||||
|
align={'flex-start'}
|
||||||
|
{...props}
|
||||||
|
className={styles.addressRoot}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
PaperTemplate.Address = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
return <Box>{children}</Box>;
|
||||||
|
};
|
||||||
|
|
||||||
|
PaperTemplate.Statement = ({
|
||||||
|
label,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
label: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className={styles.paragraph}>
|
||||||
|
{label && <div className={styles.paragraphLabel}>{label}</div>}
|
||||||
|
<div>{children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
PaperTemplate.TermsList = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
return (
|
||||||
|
<x.div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '4px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</x.div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
PaperTemplate.TermsItem = ({
|
||||||
|
label,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
label: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<x.div style={{ display: 'flex', flexDirection: 'row', gap: '12px' }}>
|
||||||
|
<x.div style={{ minWidth: '120px', color: '#333' }}>{label}</x.div>
|
||||||
|
<x.div>{children}</x.div>
|
||||||
|
</x.div>
|
||||||
|
);
|
||||||
|
};
|
||||||
25
shared/pdf-templates/src/components/PaperTemplateLayout.tsx
Normal file
25
shared/pdf-templates/src/components/PaperTemplateLayout.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { CacheProvider, ThemeProvider } from '@emotion/react';
|
||||||
|
import { EmotionCache } from '@emotion/cache';
|
||||||
|
import { defaultTheme } from '@xstyled/system';
|
||||||
|
|
||||||
|
const theme = {
|
||||||
|
...defaultTheme,
|
||||||
|
};
|
||||||
|
export function PaperTemplateLayout({ cache, children }: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
cache: EmotionCache;
|
||||||
|
}) {
|
||||||
|
const html = (
|
||||||
|
<CacheProvider value={cache}>
|
||||||
|
<ThemeProvider theme={theme}>{children}</ThemeProvider>
|
||||||
|
</CacheProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body>
|
||||||
|
<div id="root">{html}</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
25
shared/pdf-templates/src/components/_constants.ts
Normal file
25
shared/pdf-templates/src/components/_constants.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
export const DefaultPdfTemplateTerms =
|
||||||
|
'All services provided are non-refundable. For any disputes, please contact us within 7 days of receiving this invoice.';
|
||||||
|
|
||||||
|
export const DefaultPdfTemplateStatement =
|
||||||
|
'Thank you for your business. We look forward to working with you again!';
|
||||||
|
|
||||||
|
export const DefaultPdfTemplateItemName = 'Web development';
|
||||||
|
|
||||||
|
export const DefaultPdfTemplateItemDescription =
|
||||||
|
'Website development with content and SEO optimization';
|
||||||
|
|
||||||
|
export const DefaultPdfTemplateAddressBilledTo = `Bigcapital Technology, Inc.<br />
|
||||||
|
131 Continental Dr, <br />
|
||||||
|
Suite 305, <br />
|
||||||
|
Newark, Delaware 19713, <br />
|
||||||
|
United States,<br />
|
||||||
|
+1 762-339-5634
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const DefaultPdfTemplateAddressBilledFrom = `131 Continental Dr Suite 305 Newark, <br />
|
||||||
|
Delaware 19713,<br />
|
||||||
|
United States, <br />
|
||||||
|
+1 762-339-5634, <br />
|
||||||
|
ahmed@bigcapital.app
|
||||||
|
`;
|
||||||
@@ -1 +1,2 @@
|
|||||||
export * from './Test';
|
export * from './components/PaperTemplate';
|
||||||
|
export * from './components/InvoicePaperTemplate';
|
||||||
|
|||||||
19
shared/pdf-templates/src/lib/layout/Box.tsx
Normal file
19
shared/pdf-templates/src/lib/layout/Box.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import React, { forwardRef, Ref } from 'react';
|
||||||
|
import { SystemProps, x } from '@xstyled/emotion';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
export interface BoxProps
|
||||||
|
extends SystemProps,
|
||||||
|
IProps,
|
||||||
|
Omit<React.HTMLProps<HTMLDivElement>, 'color' | 'as'> { }
|
||||||
|
|
||||||
|
export const Box = forwardRef(
|
||||||
|
({ className, ...rest }: BoxProps, ref: Ref<HTMLDivElement>) => {
|
||||||
|
const Element = x.div;
|
||||||
|
|
||||||
|
return <Element className={className} ref={ref} {...rest} />;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Box.displayName = '@bigcapital/Box';
|
||||||
58
shared/pdf-templates/src/lib/layout/Group.tsx
Normal file
58
shared/pdf-templates/src/lib/layout/Group.tsx
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { SystemProps } from '@xstyled/emotion';
|
||||||
|
import { Box } from './Box';
|
||||||
|
import { filterFalsyChildren } from './utils';
|
||||||
|
|
||||||
|
export type GroupPosition = 'right' | 'center' | 'left' | 'apart';
|
||||||
|
|
||||||
|
export const GROUP_POSITIONS = {
|
||||||
|
left: 'flex-start',
|
||||||
|
center: 'center',
|
||||||
|
right: 'flex-end',
|
||||||
|
apart: 'space-between',
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface GroupProps
|
||||||
|
extends SystemProps,
|
||||||
|
Omit<React.ComponentPropsWithoutRef<'div'>, 'color'> {
|
||||||
|
/** Defines justify-content property */
|
||||||
|
position?: GroupPosition;
|
||||||
|
|
||||||
|
/** Defined flex-wrap property */
|
||||||
|
noWrap?: boolean;
|
||||||
|
|
||||||
|
/** Defines flex-grow property for each element, true -> 1, false -> 0 */
|
||||||
|
grow?: boolean;
|
||||||
|
|
||||||
|
/** Space between elements */
|
||||||
|
spacing?: number;
|
||||||
|
|
||||||
|
/** Defines align-items css property */
|
||||||
|
align?: React.CSSProperties['alignItems'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Group({
|
||||||
|
position = 'left',
|
||||||
|
spacing = 20,
|
||||||
|
align = 'center',
|
||||||
|
noWrap,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: GroupProps) {
|
||||||
|
const filteredChildren = filterFalsyChildren(children);
|
||||||
|
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
33
shared/pdf-templates/src/lib/layout/Stack.tsx
Normal file
33
shared/pdf-templates/src/lib/layout/Stack.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { x, SystemProps } from '@xstyled/emotion';
|
||||||
|
|
||||||
|
export interface StackProps
|
||||||
|
extends SystemProps,
|
||||||
|
Omit<React.ComponentPropsWithoutRef<'div'>, 'color'> {
|
||||||
|
/** Key of theme.spacing or number to set gap in px */
|
||||||
|
spacing?: number;
|
||||||
|
|
||||||
|
/** align-items CSS property */
|
||||||
|
align?: React.CSSProperties['alignItems'];
|
||||||
|
|
||||||
|
/** justify-content CSS property */
|
||||||
|
justify?: React.CSSProperties['justifyContent'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Stack({
|
||||||
|
spacing = 20,
|
||||||
|
align = 'stretch',
|
||||||
|
justify = 'top',
|
||||||
|
...restProps
|
||||||
|
}: StackProps) {
|
||||||
|
return (
|
||||||
|
<x.div
|
||||||
|
display={'flex'}
|
||||||
|
flexDirection="column"
|
||||||
|
justifyContent="justify"
|
||||||
|
gap={`${spacing}px`}
|
||||||
|
alignItems={align}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
5
shared/pdf-templates/src/lib/layout/utils.ts
Normal file
5
shared/pdf-templates/src/lib/layout/utils.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export const filterFalsyChildren = (children: React.ReactNode) => {
|
||||||
|
return React.Children.toArray(children).filter(Boolean);
|
||||||
|
};
|
||||||
4
shared/pdf-templates/src/lib/main.ts
Normal file
4
shared/pdf-templates/src/lib/main.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export * from './layout/Stack';
|
||||||
|
export * from './layout/Group';
|
||||||
|
export * from './layout/Box';
|
||||||
|
export * from './text/Text';
|
||||||
5
shared/pdf-templates/src/lib/text/Text.tsx
Normal file
5
shared/pdf-templates/src/lib/text/Text.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { x } from '@xstyled/emotion';
|
||||||
|
|
||||||
|
export const Text = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
return <x.div>{children}</x.div>;
|
||||||
|
};
|
||||||
1
shared/pdf-templates/src/vite-env.d.ts
vendored
1
shared/pdf-templates/src/vite-env.d.ts
vendored
@@ -1 +0,0 @@
|
|||||||
/// <reference types="vite/client" />
|
|
||||||
Reference in New Issue
Block a user