@skalfa/skalfa-app 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/.env.example +44 -0
  2. package/README.md +28 -0
  3. package/app/auth/edit/page.tsx +65 -0
  4. package/app/auth/login/page.tsx +63 -0
  5. package/app/auth/me/page.tsx +58 -0
  6. package/app/auth/register/page.tsx +69 -0
  7. package/app/auth/verify/page.tsx +53 -0
  8. package/app/dashboard/layout.tsx +47 -0
  9. package/app/dashboard/page.tsx +9 -0
  10. package/app/dashboard/user/page.tsx +77 -0
  11. package/app/index.ts +14 -0
  12. package/app/layout.tsx +38 -0
  13. package/app/page.tsx +13 -0
  14. package/barrels.json +6 -0
  15. package/blueprints/starter.blueprint.json +103 -0
  16. package/components/base.components/accordion/Accordion.component.tsx +82 -0
  17. package/components/base.components/breadcrumb/Breadcrumb.component.tsx +80 -0
  18. package/components/base.components/button/Button.component.tsx +91 -0
  19. package/components/base.components/button/IconButton.component.tsx +88 -0
  20. package/components/base.components/button/button.decorate.ts +82 -0
  21. package/components/base.components/card/AlertCard.component.tsx +69 -0
  22. package/components/base.components/card/Card.component.tsx +25 -0
  23. package/components/base.components/card/DashboardCard.component.tsx +44 -0
  24. package/components/base.components/card/GalleryCard.component.tsx +50 -0
  25. package/components/base.components/card/ProductCard.component.tsx +65 -0
  26. package/components/base.components/card/ProfileCard.component.tsx +71 -0
  27. package/components/base.components/carousel/Carousel.component.tsx +113 -0
  28. package/components/base.components/chip/Chip.component.tsx +39 -0
  29. package/components/base.components/document/DocumentViewer.component.tsx +164 -0
  30. package/components/base.components/document/ExportExcel.component.tsx +340 -0
  31. package/components/base.components/document/ImportExcel.component.tsx +315 -0
  32. package/components/base.components/document/PrintTable.component.tsx +204 -0
  33. package/components/base.components/document/RenderPDF.component.tsx +416 -0
  34. package/components/base.components/index.ts +85 -0
  35. package/components/base.components/input/Checkbox.component.tsx +109 -0
  36. package/components/base.components/input/Input.component.tsx +332 -0
  37. package/components/base.components/input/InputCheckbox.component.tsx +174 -0
  38. package/components/base.components/input/InputCurrency.component.tsx +163 -0
  39. package/components/base.components/input/InputDate.component.tsx +352 -0
  40. package/components/base.components/input/InputDatetime.component.tsx +260 -0
  41. package/components/base.components/input/InputDocument.component.tsx +352 -0
  42. package/components/base.components/input/InputImage.component.tsx +533 -0
  43. package/components/base.components/input/InputMap.component.tsx +318 -0
  44. package/components/base.components/input/InputNumber.component.tsx +192 -0
  45. package/components/base.components/input/InputOtp.component.tsx +169 -0
  46. package/components/base.components/input/InputPassword.component.tsx +236 -0
  47. package/components/base.components/input/InputRadio.component.tsx +175 -0
  48. package/components/base.components/input/InputTime.component.tsx +276 -0
  49. package/components/base.components/input/InputValues.component.tsx +68 -0
  50. package/components/base.components/input/Radio.component.tsx +102 -0
  51. package/components/base.components/input/Select.component.tsx +541 -0
  52. package/components/base.components/modal/BottomSheet.component.tsx +246 -0
  53. package/components/base.components/modal/FloatingPage.component.tsx +104 -0
  54. package/components/base.components/modal/Modal.component.tsx +96 -0
  55. package/components/base.components/modal/ModalConfirm.component.tsx +218 -0
  56. package/components/base.components/modal/Toast.component.tsx +126 -0
  57. package/components/base.components/nav/Bottombar.component.tsx +116 -0
  58. package/components/base.components/nav/Footer.component.tsx +144 -0
  59. package/components/base.components/nav/Headbar.component.tsx +104 -0
  60. package/components/base.components/nav/Navbar.component.tsx +100 -0
  61. package/components/base.components/nav/Sidebar.component.tsx +301 -0
  62. package/components/base.components/nav/Tabbar.component.tsx +60 -0
  63. package/components/base.components/nav/Wizard.component.tsx +73 -0
  64. package/components/base.components/supervision/FormSupervision.component.tsx +434 -0
  65. package/components/base.components/supervision/TableSupervision.component.tsx +697 -0
  66. package/components/base.components/table/ControlBar.component.tsx +497 -0
  67. package/components/base.components/table/FilterComponent.tsx +518 -0
  68. package/components/base.components/table/Pagination.component.tsx +159 -0
  69. package/components/base.components/table/Table.component.tsx +469 -0
  70. package/components/base.components/typography/TypographyArticle.component.tsx +26 -0
  71. package/components/base.components/typography/TypographyColumn.component.tsx +20 -0
  72. package/components/base.components/typography/TypographyContent.component.tsx +20 -0
  73. package/components/base.components/typography/TypographyTips.component.tsx +20 -0
  74. package/components/base.components/wrap/Draggable.component.tsx +303 -0
  75. package/components/base.components/wrap/IDBProvider.tsx +12 -0
  76. package/components/base.components/wrap/Image.component.tsx +10 -0
  77. package/components/base.components/wrap/OutsideClick.component.tsx +48 -0
  78. package/components/base.components/wrap/ScrollContainer.component.tsx +104 -0
  79. package/components/base.components/wrap/ShortcutProvider.tsx +57 -0
  80. package/components/base.components/wrap/Swipe.component.tsx +93 -0
  81. package/components/construct.components/example.tsx +1 -0
  82. package/components/construct.components/index.ts +5 -0
  83. package/components/index.ts +3 -0
  84. package/components/structure.components/example.tsx +1 -0
  85. package/components/structure.components/index.ts +5 -0
  86. package/contexts/AppProvider.tsx +12 -0
  87. package/contexts/Auth.context.tsx +64 -0
  88. package/contexts/Toggle.context.tsx +44 -0
  89. package/contexts/index.ts +7 -0
  90. package/eslint.config.mjs +34 -0
  91. package/langs/index.ts +1 -0
  92. package/langs/validation.langs.ts +17 -0
  93. package/next.config.ts +17 -0
  94. package/package.json +43 -0
  95. package/postcss.config.mjs +12 -0
  96. package/public/204.svg +19 -0
  97. package/public/500.svg +39 -0
  98. package/public/images/avatar.jpg +0 -0
  99. package/public/images/example.png +0 -0
  100. package/schema/idb/app.schema.ts +9 -0
  101. package/schema/index.ts +5 -0
  102. package/styles/globals.css +231 -0
  103. package/styles/tailwind.safelist +69 -0
  104. package/tailwind.config.ts +10 -0
  105. package/tsconfig.json +35 -0
  106. package/utils/commands/barrels.ts +28 -0
  107. package/utils/commands/blueprint.ts +421 -0
  108. package/utils/commands/light.ts +21 -0
  109. package/utils/commands/logger.ts +42 -0
  110. package/utils/commands/stubs/table-blueprint.stub +13 -0
  111. package/utils/commands/use-pdf.ts +29 -0
  112. package/utils/index.ts +3 -0
@@ -0,0 +1,204 @@
1
+ "use client";
2
+
3
+ import { useEffect, useMemo, useState } from "react";
4
+ import { FetchControlType, useTable } from "@utils";
5
+ import { TableComponent, IconButtonComponent, FilterComponent, NodeSchema, PageSchema, RenderPDFPreview, ButtonComponent, RenderPDF } from "@components";
6
+ import { faArrowLeft, faArrowRight } from "@fortawesome/free-solid-svg-icons";
7
+
8
+
9
+ export type PrintColumn = {
10
+ selector : string;
11
+ source : string | null;
12
+ width : number;
13
+ };
14
+
15
+ export type ColumnControl = {
16
+ selector : string;
17
+ label : string;
18
+ };
19
+
20
+
21
+
22
+ function usePrintColumns(data: any[], columnControl?: ColumnControl[]) {
23
+ const fields = useMemo(() => {
24
+ if (!data?.[0]) return [];
25
+ return Object.keys(data[0]);
26
+ }, [data]);
27
+
28
+ const [columns, setColumns] = useState<PrintColumn[]>([]);
29
+
30
+ useEffect(() => {
31
+ if (!data?.[0] || columns.length) return;
32
+
33
+ if (columnControl?.length) {
34
+ setColumns(
35
+ columnControl.map((c) => ({
36
+ selector: c.label,
37
+ source: c.selector,
38
+ width: 120,
39
+ }))
40
+ );
41
+ return;
42
+ }
43
+
44
+ setColumns(
45
+ fields.map((f) => ({
46
+ selector: f,
47
+ source: f,
48
+ width: 120,
49
+ }))
50
+ );
51
+ }, [data, fields, columnControl]);
52
+
53
+ const move = (selector: string, dir: "left" | "right") => {
54
+ setColumns(prev => {
55
+ const idx = prev.findIndex(c => c.selector === selector);
56
+ const target = dir === "left" ? idx - 1 : idx + 1;
57
+ if (idx < 0 || target < 0 || target >= prev.length) return prev;
58
+ const next = [...prev];
59
+ [next[idx], next[target]] = [next[target], next[idx]];
60
+ return next.map((c) => ({ ...c, selector: c.selector }));
61
+ });
62
+ };
63
+
64
+ return { columns, move };
65
+ }
66
+
67
+
68
+ function buildPrintSchema({ title, columns, data, columnControl }: {
69
+ title ?: string;
70
+ columns : PrintColumn[];
71
+ data : any[];
72
+ columnControl ?: ColumnControl[];
73
+ }) : PageSchema[] {
74
+ const active = columns.filter(c => c.source);
75
+
76
+ const getHeaderLabel = (source: string | null) => {
77
+ if (!source) return "";
78
+
79
+ const found = columnControl?.find(c => c.selector === source);
80
+
81
+ return found?.label ?? source;
82
+ };
83
+
84
+ const content: NodeSchema[] = [];
85
+
86
+ if (title) {
87
+ content.push({
88
+ type : "text",
89
+ content : title,
90
+ style : { fontSize: 16, fontWeight: "bold", marginBottom: 12 },
91
+ });
92
+ }
93
+
94
+ content.push({
95
+ type : "table",
96
+ content : [
97
+ {
98
+ type : "tr" as const,
99
+ content : active.map<NodeSchema>(c => ({
100
+ type : "th" as const,
101
+ content : getHeaderLabel(c.source),
102
+ style : { width: c.width },
103
+ })),
104
+ },
105
+ ...data.map<NodeSchema>(row => ({
106
+ type : "tr" as const,
107
+ content : active.map<NodeSchema>(c => ({
108
+ type : "td" as const,
109
+ content : String(row[c.source!] ?? ""),
110
+ style : { width: c.width },
111
+ })),
112
+ })),
113
+ ],
114
+ });
115
+
116
+ return [{ page: { size: "A4", margin: 40, content } }];
117
+ }
118
+
119
+
120
+
121
+ export function PrintTable({ fetchControl, columnControl, title }: { fetchControl: FetchControlType; columnControl?: ColumnControl[]; title?: string; }) {
122
+ const { data } = useTable(fetchControl, undefined, undefined, false);
123
+ const rows = data?.data ?? [];
124
+
125
+ const { columns, move } = usePrintColumns(rows, columnControl);
126
+
127
+ const schema = useMemo(() => buildPrintSchema({
128
+ title: title,
129
+ columns,
130
+ data: rows,
131
+ columnControl,
132
+ }), [columns, rows, columnControl]);
133
+
134
+ return (
135
+ <>
136
+ <div className="p-4">
137
+ <FilterComponent
138
+ columns={data?.data[0] && Object.keys(data?.data[0])?.map((c) => ({ label: c, selector: c }))}
139
+ onChange={() => {}}
140
+ value={[]}
141
+ />
142
+ </div>
143
+
144
+ <div className="p-4">
145
+ <TableComponent
146
+ controlBar={false}
147
+ pagination={false}
148
+ noIndex
149
+ columns={columns.map(c => ({ selector: c.selector, label: c.selector }))}
150
+ data={[
151
+ Object.fromEntries(
152
+ columns.map((c, key) => [
153
+ c.selector,
154
+ <div className="flex gap-1" key={key}>
155
+ {key > 0 && (
156
+ <IconButtonComponent
157
+ icon={faArrowLeft}
158
+ size="xs"
159
+ variant="outline"
160
+ className="!text-foreground"
161
+ onClick={() => move(c.selector, "left")}
162
+ />
163
+ )}
164
+
165
+ {key < (columns.length - 1) && (
166
+ <IconButtonComponent
167
+ icon={faArrowRight}
168
+ size="xs"
169
+ variant="outline"
170
+ className="!text-foreground"
171
+ onClick={() => move(c.selector, "right")}
172
+ />
173
+ )}
174
+ </div>,
175
+ ])
176
+ ),
177
+ ]}
178
+ className="row::bg-transparent row::border-0 row::gap-0 row::!hover:bg-white column::p-2 column::border-l head-column::p-2 head-column::border-l"
179
+ />
180
+ </div>
181
+
182
+ <RenderPDFPreview schema={schema} className="w-full max-h-[400px] overflow-y-auto flex justify-center bg-background mt-4" />
183
+
184
+
185
+ <div className="px-4 mt-8">
186
+ <ButtonComponent label=" Download PDF" block onClick={async () => {
187
+ const bytes = await RenderPDF({ content: schema });
188
+
189
+ const arrayBuffer = bytes.buffer instanceof ArrayBuffer ? bytes.buffer : bytes.slice().buffer;
190
+
191
+ const blob = new Blob([arrayBuffer], { type: "application/pdf" });
192
+ const url = URL.createObjectURL(blob);
193
+
194
+ const a = document.createElement("a");
195
+ a.href = url;
196
+ a.download = title ? `${title}.pdf` : "print.pdf";
197
+ a.click();
198
+
199
+ URL.revokeObjectURL(url);
200
+ }} />
201
+ </div>
202
+ </>
203
+ );
204
+ }
@@ -0,0 +1,416 @@
1
+ "use client"
2
+
3
+ import { PDFDocument, StandardFonts, PDFPage, rgb } from 'pdf-lib'
4
+ import { useEffect, useRef } from 'react'
5
+
6
+ export const PaperSize = {
7
+ LETTER: { width: 612, height: 792 },
8
+ A4: { width: 595, height: 842 },
9
+ }
10
+
11
+ export type RenderPDFProps = {
12
+ content: PageSchema[]
13
+ }
14
+
15
+ export type PageSchema = {
16
+ page: {
17
+ size?: keyof typeof PaperSize | { width: number; height: number }
18
+ margin?: number
19
+ content: NodeSchema[]
20
+ }
21
+ }
22
+
23
+ export type Style = {
24
+ width?: number
25
+ height?: number
26
+
27
+ padding?: number
28
+ paddingTop?: number
29
+ paddingRight?: number
30
+ paddingBottom?: number
31
+ paddingLeft?: number
32
+ paddingX?: number
33
+ paddingY?: number
34
+
35
+ marginTop?: number
36
+ marginBottom?: number
37
+
38
+ fontSize?: number
39
+ fontWeight?: "normal" | "bold"
40
+ lineHeight?: number
41
+ letterSpacing?: number
42
+ color?: string
43
+ opacity?: number
44
+ align?: "left" | "center" | "right"
45
+ textTransform?: "uppercase" | "lowercase" | "capitalize"
46
+
47
+ backgroundColor?: string
48
+ borderColor?: string
49
+ borderWidth?: number
50
+
51
+ underline?: boolean
52
+
53
+ textAlign?: "left" | "center" | "right"
54
+ }
55
+
56
+ export type NodeSchema =
57
+ | { type: "view"; style?: Style; content: NodeSchema[] }
58
+ | { type: "text"; content: string; style?: Style }
59
+ | { type: "image"; src: string | Uint8Array | ArrayBuffer; style?: Style }
60
+ | { type: "table"; content: NodeSchema[] }
61
+ | { type: "tr"; content: NodeSchema[], style?: Style }
62
+ | { type: 'td' | 'th'; content: NodeSchema[] | string; style?: Style }
63
+
64
+ // ==================================================
65
+ // Layout Context
66
+ // ==================================================
67
+
68
+ class LayoutContext {
69
+ x: number
70
+ y: number
71
+ constructor(
72
+ public width: number,
73
+ public height: number,
74
+ public margin: number
75
+ ) {
76
+ this.x = margin
77
+ this.y = height - margin
78
+ }
79
+
80
+ needBreak(h: number) {
81
+ return this.y - h < this.margin
82
+ }
83
+
84
+ reset() {
85
+ this.x = this.margin
86
+ this.y = this.height - this.margin
87
+ }
88
+ }
89
+
90
+ // ==================================================
91
+ // Helpers
92
+ // ==================================================
93
+
94
+ function resolvePadding(style?: Style) {
95
+ const p = style?.padding ?? 0
96
+ const px = style?.paddingX ?? p
97
+ const py = style?.paddingY ?? p
98
+
99
+ return {
100
+ top: style?.paddingTop ?? py,
101
+ bottom: style?.paddingBottom ?? py,
102
+ left: style?.paddingLeft ?? px,
103
+ right: style?.paddingRight ?? px,
104
+ }
105
+ }
106
+
107
+ function resolveText(text: string, style?: Style) {
108
+ if (!style?.textTransform) return text
109
+ if (style.textTransform === "uppercase") return text.toUpperCase()
110
+ if (style.textTransform === "lowercase") return text.toLowerCase()
111
+ if (style.textTransform === "capitalize")
112
+ return text.replace(/\b\w/g, c => c.toUpperCase())
113
+ return text
114
+ }
115
+
116
+ function hexToRgb(hex?: string) {
117
+ if (!hex) return undefined
118
+ const h = hex.replace("#", "")
119
+ return rgb(
120
+ parseInt(h.slice(0, 2), 16) / 255,
121
+ parseInt(h.slice(2, 4), 16) / 255,
122
+ parseInt(h.slice(4, 6), 16) / 255
123
+ )
124
+ }
125
+
126
+ async function embedImage(pdf: PDFDocument, bytes: Uint8Array | ArrayBuffer) {
127
+ const data = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes)
128
+ try {
129
+ return await pdf.embedPng(data)
130
+ } catch {
131
+ return await pdf.embedJpg(data)
132
+ }
133
+ }
134
+
135
+ async function resolveImageSource(
136
+ src: string | Uint8Array | ArrayBuffer
137
+ ): Promise<Uint8Array> {
138
+ if (src instanceof Uint8Array) return src
139
+ if (src instanceof ArrayBuffer) return new Uint8Array(src)
140
+
141
+ const res = await fetch(src)
142
+ if (!res.ok) {
143
+ throw new Error(`Failed to load image: ${src}`)
144
+ }
145
+
146
+ const buffer = await res.arrayBuffer()
147
+ return new Uint8Array(buffer)
148
+ }
149
+
150
+
151
+ function normalizeContent(
152
+ content: string | NodeSchema[]
153
+ ): NodeSchema[] {
154
+ if (typeof content === "string") {
155
+ return [
156
+ {
157
+ type: "text",
158
+ content
159
+ }
160
+ ]
161
+ }
162
+ return content
163
+ }
164
+
165
+
166
+ // ==================================================
167
+ // Render Engine
168
+ // ==================================================
169
+
170
+ export async function RenderPDF(
171
+ { content }: RenderPDFProps
172
+ ): Promise<Uint8Array> {
173
+
174
+ const pdf = await PDFDocument.create()
175
+
176
+ const fontRegular = await pdf.embedFont(StandardFonts.Courier)
177
+ const fontBold = await pdf.embedFont(StandardFonts.CourierBold)
178
+
179
+ for (const p of content) {
180
+ const size =
181
+ typeof p.page.size === "string"
182
+ ? PaperSize[p.page.size]
183
+ : p.page.size ?? PaperSize.A4
184
+
185
+ const margin = p.page.margin ?? 40
186
+
187
+ let page: PDFPage = pdf.addPage([size.width, size.height])
188
+ const ctx = new LayoutContext(size.width, size.height, margin)
189
+
190
+ const draw = async (node: NodeSchema) => {
191
+
192
+ // ===================== VIEW =====================
193
+ if (node.type === "view") {
194
+ const pad = resolvePadding(node.style)
195
+ const startY = ctx.y
196
+
197
+ ctx.y -= pad.top
198
+ ctx.x += pad.left
199
+
200
+ for (const c of node.content) await draw(c)
201
+
202
+ const endY = ctx.y
203
+ const boxHeight = startY - endY
204
+
205
+ if (node.style?.backgroundColor) {
206
+ page.drawRectangle({
207
+ x: ctx.margin,
208
+ y: endY,
209
+ width: size.width - ctx.margin * 2,
210
+ height: boxHeight,
211
+ color: hexToRgb(node.style.backgroundColor),
212
+ })
213
+ }
214
+
215
+ if (node.style?.borderWidth && node.style?.borderColor) {
216
+ page.drawRectangle({
217
+ x: ctx.margin,
218
+ y: endY,
219
+ width: size.width - ctx.margin * 2,
220
+ height: boxHeight,
221
+ borderColor: hexToRgb(node.style.borderColor),
222
+ borderWidth: node.style.borderWidth,
223
+ })
224
+ }
225
+
226
+ ctx.x -= pad.left
227
+ ctx.y -= pad.bottom
228
+ return
229
+ }
230
+
231
+ // ===================== TEXT =====================
232
+ if (node.type === "text") {
233
+ const style = node.style
234
+ const fs = style?.fontSize ?? 12
235
+ const lh = style?.lineHeight ?? fs + 4
236
+
237
+ if (ctx.needBreak(lh)) {
238
+ page = pdf.addPage([size.width, size.height])
239
+ ctx.reset()
240
+ }
241
+
242
+ const font =
243
+ style?.fontWeight === "bold"
244
+ ? fontBold
245
+ : fontRegular
246
+
247
+ const text = resolveText(node.content, style)
248
+ const color = hexToRgb(style?.color)
249
+
250
+ let x = ctx.x
251
+ if (style?.align === "center") {
252
+ const w = font.widthOfTextAtSize(text, fs)
253
+ x = (size.width - w) / 2
254
+ }
255
+ if (style?.align === "right") {
256
+ const w = font.widthOfTextAtSize(text, fs)
257
+ x = size.width - ctx.margin - w
258
+ }
259
+
260
+ page.drawText(text, {
261
+ x,
262
+ y: ctx.y - fs,
263
+ size: fs,
264
+ font,
265
+ color,
266
+ opacity: style?.opacity,
267
+ })
268
+
269
+ if (style?.underline) {
270
+ const w = font.widthOfTextAtSize(text, fs)
271
+ page.drawLine({
272
+ start: { x, y: ctx.y - fs - 2 },
273
+ end: { x: x + w, y: ctx.y - fs - 2 },
274
+ thickness: 1,
275
+ })
276
+ }
277
+
278
+ ctx.y -= lh + (style?.marginBottom ?? 0)
279
+ return
280
+ }
281
+
282
+ // ===================== IMAGE =====================
283
+ if (node.type === "image") {
284
+ const bytes = await resolveImageSource(node.src)
285
+ const img = await embedImage(pdf, bytes)
286
+ const base = img.scale(1)
287
+
288
+ let w = node.style?.width ?? base.width
289
+ let h = node.style?.height ?? base.height
290
+
291
+ if (node.style?.width && !node.style?.height)
292
+ h = (base.height / base.width) * w
293
+
294
+ if (node.style?.height && !node.style?.width)
295
+ w = (base.width / base.height) * h
296
+
297
+ if (ctx.needBreak(h)) {
298
+ page = pdf.addPage([size.width, size.height])
299
+ ctx.reset()
300
+ }
301
+
302
+ page.drawImage(img, {
303
+ x: ctx.x,
304
+ y: ctx.y - h,
305
+ width: w,
306
+ height: h,
307
+ })
308
+
309
+ ctx.y -= h + (node.style?.marginBottom ?? 0)
310
+ return
311
+ }
312
+
313
+
314
+ // ===================== TABLE =====================
315
+ if (node.type === "table") {
316
+ for (const r of node.content) await draw(r)
317
+ ctx.y -= 8
318
+ return
319
+ }
320
+
321
+ if (node.type === "tr") {
322
+ const rowH = node.style?.height || 20;
323
+
324
+ if (ctx.needBreak(rowH)) {
325
+ page = pdf.addPage([size.width, size.height])
326
+ ctx.reset()
327
+ }
328
+
329
+ const tableWidth = size.width - ctx.margin * 2
330
+ const colCount = node.content.length
331
+ const colWidth = tableWidth / colCount
332
+
333
+ const originalX = ctx.x
334
+ let x = ctx.margin
335
+
336
+ for (const cell of node.content) {
337
+ if (cell.type !== "td" && cell.type !== "th") continue
338
+
339
+ const children = normalizeContent(cell.content)
340
+ const pad = resolvePadding(cell.style)
341
+ const startY = ctx.y
342
+
343
+ ctx.x = x + pad.left
344
+ ctx.y -= pad.top
345
+
346
+ for (const child of children) {
347
+ await draw(child)
348
+ }
349
+
350
+ ctx.y = startY
351
+ x += colWidth
352
+ }
353
+
354
+ ctx.x = originalX
355
+ ctx.y -= rowH
356
+ return
357
+ }
358
+
359
+
360
+
361
+
362
+ }
363
+
364
+ for (const n of p.page.content) await draw(n)
365
+ }
366
+
367
+ return await pdf.save()
368
+ }
369
+
370
+ export function RenderPDFPreview({ schema, className }: { schema: PageSchema[], className?: string }) {
371
+ const canvasRef = useRef<HTMLCanvasElement>(null);
372
+
373
+ useEffect(() => {
374
+ let cancelled = false;
375
+
376
+ (async () => {
377
+ const bytes = await RenderPDF({ content: schema });
378
+ const pdfjs = await import("pdfjs-dist/legacy/build/pdf.mjs");
379
+ pdfjs.GlobalWorkerOptions.workerSrc = "/pdf.worker.min.mjs";
380
+
381
+ const pdf = await pdfjs.getDocument({ data: bytes }).promise;
382
+ if (cancelled) return;
383
+
384
+ const page = await pdf.getPage(1);
385
+ const dpr = 1;
386
+ const viewport = page.getViewport({ scale: 1 });
387
+
388
+ const canvas = canvasRef.current!;
389
+ const ctx = canvas.getContext("2d")!;
390
+ canvas.style.width = `${viewport.width}px`;
391
+ canvas.style.height = `${viewport.height}px`
392
+ canvas.width = viewport.width;
393
+ canvas.height = viewport.height;
394
+
395
+ const scaledViewport = page.getViewport({ scale: dpr });
396
+
397
+ const renderTask = page.render({
398
+ canvas,
399
+ canvasContext: ctx,
400
+ viewport: scaledViewport,
401
+ });
402
+
403
+ await renderTask.promise;
404
+ })();
405
+
406
+ return () => {
407
+ cancelled = true;
408
+ };
409
+ }, [schema]);
410
+
411
+ return <>
412
+ <div className={className}>
413
+ <canvas ref={canvasRef} className="w-full border" />
414
+ </div>
415
+ </>
416
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * @file Automatically generated by barrelsby.
3
+ */
4
+
5
+ export * from "./accordion/Accordion.component";
6
+ export * from "./breadcrumb/Breadcrumb.component";
7
+ export * from "./button/Button.component";
8
+ export * from "./button/button.decorate";
9
+ export * from "./button/IconButton.component";
10
+ export * from "./card/AlertCard.component";
11
+ export * from "./card/Card.component";
12
+ export * from "./card/DashboardCard.component";
13
+ export * from "./card/GalleryCard.component";
14
+ export * from "./card/ProductCard.component";
15
+ export * from "./card/ProfileCard.component";
16
+ export * from "./carousel/Carousel.component";
17
+ export * from "./chip/Chip.component";
18
+ export * from "./document/DocumentViewer.component";
19
+ export * from "./document/ExportExcel.component";
20
+ export * from "./document/ImportExcel.component";
21
+ export * from "./document/PrintTable.component";
22
+ export * from "./document/RenderPDF.component";
23
+ export * from "./input/Checkbox.component";
24
+ export * from "./input/Input.component";
25
+ export * from "./input/InputCheckbox.component";
26
+ export * from "./input/InputCurrency.component";
27
+ export * from "./input/InputDate.component";
28
+ export * from "./input/InputDatetime.component";
29
+ export * from "./input/InputDocument.component";
30
+ export * from "./input/InputImage.component";
31
+ export * from "./input/InputMap.component";
32
+ export * from "./input/InputNumber.component";
33
+ export * from "./input/InputOtp.component";
34
+ export * from "./input/InputPassword.component";
35
+ export * from "./input/InputRadio.component";
36
+ export * from "./input/InputTime.component";
37
+ export * from "./input/InputValues.component";
38
+ export * from "./input/Radio.component";
39
+ export * from "./input/Select.component";
40
+ export * from "./modal/BottomSheet.component";
41
+ export * from "./modal/FloatingPage.component";
42
+ export * from "./modal/Modal.component";
43
+ export * from "./modal/ModalConfirm.component";
44
+ export * from "./modal/Toast.component";
45
+ export * from "./nav/Bottombar.component";
46
+ export * from "./nav/Footer.component";
47
+ export * from "./nav/Headbar.component";
48
+ export * from "./nav/Navbar.component";
49
+ export * from "./nav/Sidebar.component";
50
+ export * from "./nav/Tabbar.component";
51
+ export * from "./nav/Wizard.component";
52
+ export * from "./supervision/FormSupervision.component";
53
+ export * from "./supervision/TableSupervision.component";
54
+ export * from "./table/ControlBar.component";
55
+ export * from "./table/FilterComponent";
56
+ export * from "./table/Pagination.component";
57
+ export * from "./table/Table.component";
58
+ export * from "./typography/TypographyArticle.component";
59
+ export * from "./typography/TypographyColumn.component";
60
+ export * from "./typography/TypographyContent.component";
61
+ export * from "./typography/TypographyTips.component";
62
+ export * from "./wrap/Draggable.component";
63
+ export * from "./wrap/IDBProvider";
64
+ export * from "./wrap/Image.component";
65
+ export * from "./wrap/OutsideClick.component";
66
+ export * from "./wrap/ScrollContainer.component";
67
+ export * from "./wrap/ShortcutProvider";
68
+ export * from "./wrap/Swipe.component";
69
+
70
+ import { registry } from "@utils";
71
+ import { TableComponent } from "./table/Table.component.js";
72
+ import { ButtonComponent } from "./button/Button.component.js";
73
+ import { IconButtonComponent } from "./button/IconButton.component.js";
74
+ import { SelectComponent } from "./input/Select.component.js";
75
+ import { ModalComponent } from "./modal/Modal.component.js";
76
+ import { FilterComponent } from "./table/FilterComponent.js";
77
+ import { useToggleContext } from "../../contexts/index.js";
78
+
79
+ registry.register("TableComponent", TableComponent);
80
+ registry.register("ButtonComponent", ButtonComponent);
81
+ registry.register("IconButtonComponent", IconButtonComponent);
82
+ registry.register("SelectComponent", SelectComponent);
83
+ registry.register("ModalComponent", ModalComponent);
84
+ registry.register("FilterComponent", FilterComponent);
85
+ registry.register("useToggleContext", useToggleContext);