@stll/folio-react 0.0.1-placeholder.0 → 0.1.1

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.
@@ -0,0 +1,310 @@
1
+ import { r as useFolioUI } from "./folio-ui-o_rftArH.js";
2
+ import { useEffect, useId, useState } from "react";
3
+ import { TWIPS_PER_INCH } from "@stll/folio-core/utils/units";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ //#region src/components/dialogs/PageSetupDialog.tsx
6
+ /**
7
+ * Page Setup Dialog
8
+ *
9
+ * Modal for editing page layout properties:
10
+ * - Page size (Letter, A4, Legal, etc.)
11
+ * - Orientation (portrait/landscape)
12
+ * - Margins (top, bottom, left, right) in inches
13
+ */
14
+ /** Common page sizes in twips (width x height in portrait orientation) */
15
+ const PAGE_SIZES = [
16
+ {
17
+ label: "Letter (8.5\" × 11\")",
18
+ width: 12240,
19
+ height: 15840
20
+ },
21
+ {
22
+ label: "A4 (8.27\" × 11.69\")",
23
+ width: 11906,
24
+ height: 16838
25
+ },
26
+ {
27
+ label: "Legal (8.5\" × 14\")",
28
+ width: 12240,
29
+ height: 20160
30
+ },
31
+ {
32
+ label: "A3 (11.69\" × 16.54\")",
33
+ width: 16838,
34
+ height: 23811
35
+ },
36
+ {
37
+ label: "A5 (5.83\" × 8.27\")",
38
+ width: 8391,
39
+ height: 11906
40
+ },
41
+ {
42
+ label: "B5 (6.93\" × 9.84\")",
43
+ width: 9979,
44
+ height: 14175
45
+ },
46
+ {
47
+ label: "Executive (7.25\" × 10.5\")",
48
+ width: 10440,
49
+ height: 15120
50
+ }
51
+ ];
52
+ function twipsToInches(twips) {
53
+ return Math.round(twips / TWIPS_PER_INCH * 100) / 100;
54
+ }
55
+ function inchesToTwips(inches) {
56
+ return Math.round(inches * TWIPS_PER_INCH);
57
+ }
58
+ /** Find matching page size preset, ignoring orientation */
59
+ function findPageSizeIndex(w, h) {
60
+ const pw = Math.min(w, h);
61
+ const ph = Math.max(w, h);
62
+ return PAGE_SIZES.findIndex((s) => Math.abs(s.width - pw) < 20 && Math.abs(s.height - ph) < 20);
63
+ }
64
+ const DEFAULT_WIDTH = 12240;
65
+ const DEFAULT_HEIGHT = 15840;
66
+ const DEFAULT_MARGIN = 1440;
67
+ function PageSetupDialog({ isOpen, onClose, onApply, currentProps }) {
68
+ const { Root: Dialog, Portal: DialogPortal, Backdrop: DialogBackdrop, Popup: DialogPopup, Title: DialogTitle, Close: DialogClose } = useFolioUI().Dialog;
69
+ const id = useId();
70
+ const [pageWidth, setPageWidth] = useState(DEFAULT_WIDTH);
71
+ const [pageHeight, setPageHeight] = useState(DEFAULT_HEIGHT);
72
+ const [orientation, setOrientation] = useState("portrait");
73
+ const [marginTop, setMarginTop] = useState(DEFAULT_MARGIN);
74
+ const [marginBottom, setMarginBottom] = useState(DEFAULT_MARGIN);
75
+ const [marginLeft, setMarginLeft] = useState(DEFAULT_MARGIN);
76
+ const [marginRight, setMarginRight] = useState(DEFAULT_MARGIN);
77
+ useEffect(() => {
78
+ if (!isOpen) return;
79
+ const w = currentProps?.pageWidth || DEFAULT_WIDTH;
80
+ const h = currentProps?.pageHeight || DEFAULT_HEIGHT;
81
+ const orient = currentProps?.orientation || (w > h ? "landscape" : "portrait");
82
+ setPageWidth(w);
83
+ setPageHeight(h);
84
+ setOrientation(orient);
85
+ setMarginTop(currentProps?.marginTop ?? DEFAULT_MARGIN);
86
+ setMarginBottom(currentProps?.marginBottom ?? DEFAULT_MARGIN);
87
+ setMarginLeft(currentProps?.marginLeft ?? DEFAULT_MARGIN);
88
+ setMarginRight(currentProps?.marginRight ?? DEFAULT_MARGIN);
89
+ }, [isOpen, currentProps]);
90
+ const handlePageSizeChange = (index) => {
91
+ if (index < 0) return;
92
+ const size = PAGE_SIZES[index];
93
+ if (!size) return;
94
+ if (orientation === "landscape") {
95
+ setPageWidth(size.height);
96
+ setPageHeight(size.width);
97
+ } else {
98
+ setPageWidth(size.width);
99
+ setPageHeight(size.height);
100
+ }
101
+ };
102
+ const handleOrientationChange = (newOrientation) => {
103
+ if (newOrientation === orientation) return;
104
+ setOrientation(newOrientation);
105
+ setPageWidth(pageHeight);
106
+ setPageHeight(pageWidth);
107
+ };
108
+ const handleApply = () => {
109
+ onApply({
110
+ pageWidth,
111
+ pageHeight,
112
+ orientation,
113
+ marginTop,
114
+ marginBottom,
115
+ marginLeft,
116
+ marginRight
117
+ });
118
+ onClose();
119
+ };
120
+ const sizeIndex = findPageSizeIndex(pageWidth, pageHeight);
121
+ const labelCls = "w-20 text-muted-foreground text-[13px]";
122
+ const inputCls = "border-input bg-background text-foreground flex-1 rounded border px-2 py-1.5 text-[13px] outline-none";
123
+ const sectionLabelCls = "text-muted-foreground text-xs font-semibold uppercase tracking-wide";
124
+ const fieldIds = {
125
+ size: `${id}-ps-size`,
126
+ orientation: `${id}-ps-orientation`,
127
+ marginTop: `${id}-ps-margin-top`,
128
+ marginBottom: `${id}-ps-margin-bottom`,
129
+ marginLeft: `${id}-ps-margin-left`,
130
+ marginRight: `${id}-ps-margin-right`
131
+ };
132
+ return /* @__PURE__ */ jsx(Dialog, {
133
+ open: isOpen,
134
+ onOpenChange: (open) => {
135
+ if (!open) onClose();
136
+ },
137
+ children: /* @__PURE__ */ jsxs(DialogPortal, { children: [/* @__PURE__ */ jsx(DialogBackdrop, { className: "fixed inset-0 z-[10000] bg-black/50" }), /* @__PURE__ */ jsxs(DialogPopup, {
138
+ className: "bg-popover fixed start-1/2 top-1/2 z-[10001] w-full max-w-[480px] min-w-[400px] -translate-x-1/2 -translate-y-1/2 rounded-lg border shadow-xl",
139
+ children: [
140
+ /* @__PURE__ */ jsx(DialogTitle, {
141
+ className: "border-b px-5 py-3 text-base font-semibold",
142
+ children: "Page Setup"
143
+ }),
144
+ /* @__PURE__ */ jsxs("div", {
145
+ className: "flex flex-col gap-3.5 px-5 py-4",
146
+ children: [
147
+ /* @__PURE__ */ jsx("div", {
148
+ className: sectionLabelCls,
149
+ children: "Page Size"
150
+ }),
151
+ /* @__PURE__ */ jsxs("div", {
152
+ className: "flex items-center gap-3",
153
+ children: [/* @__PURE__ */ jsx("label", {
154
+ htmlFor: fieldIds.size,
155
+ className: labelCls,
156
+ children: "Size"
157
+ }), /* @__PURE__ */ jsxs("select", {
158
+ id: fieldIds.size,
159
+ className: inputCls,
160
+ value: sizeIndex,
161
+ onChange: (e) => handlePageSizeChange(Number(e.target.value)),
162
+ children: [PAGE_SIZES.map((size, i) => /* @__PURE__ */ jsx("option", {
163
+ value: i,
164
+ children: size.label
165
+ }, size.label)), sizeIndex < 0 && /* @__PURE__ */ jsx("option", {
166
+ value: -1,
167
+ children: "Custom"
168
+ })]
169
+ })]
170
+ }),
171
+ /* @__PURE__ */ jsxs("div", {
172
+ className: "flex items-center gap-3",
173
+ children: [/* @__PURE__ */ jsx("label", {
174
+ htmlFor: fieldIds.orientation,
175
+ className: labelCls,
176
+ children: "Orientation"
177
+ }), /* @__PURE__ */ jsxs("select", {
178
+ id: fieldIds.orientation,
179
+ className: inputCls,
180
+ value: orientation,
181
+ onChange: (e) => handleOrientationChange(e.target.value),
182
+ children: [/* @__PURE__ */ jsx("option", {
183
+ value: "portrait",
184
+ children: "Portrait"
185
+ }), /* @__PURE__ */ jsx("option", {
186
+ value: "landscape",
187
+ children: "Landscape"
188
+ })]
189
+ })]
190
+ }),
191
+ /* @__PURE__ */ jsx("div", {
192
+ className: `${sectionLabelCls} mt-1`,
193
+ children: "Margins"
194
+ }),
195
+ /* @__PURE__ */ jsxs("div", {
196
+ className: "flex items-center gap-3",
197
+ children: [
198
+ /* @__PURE__ */ jsx("label", {
199
+ htmlFor: fieldIds.marginTop,
200
+ className: labelCls,
201
+ children: "Top"
202
+ }),
203
+ /* @__PURE__ */ jsx("input", {
204
+ id: fieldIds.marginTop,
205
+ type: "number",
206
+ className: inputCls,
207
+ min: 0,
208
+ max: 10,
209
+ step: .1,
210
+ value: twipsToInches(marginTop),
211
+ onChange: (e) => setMarginTop(inchesToTwips(Number(e.target.value) || 0))
212
+ }),
213
+ /* @__PURE__ */ jsx("span", {
214
+ className: "text-muted-foreground w-4 text-[11px]",
215
+ children: "in"
216
+ })
217
+ ]
218
+ }),
219
+ /* @__PURE__ */ jsxs("div", {
220
+ className: "flex items-center gap-3",
221
+ children: [
222
+ /* @__PURE__ */ jsx("label", {
223
+ htmlFor: fieldIds.marginBottom,
224
+ className: labelCls,
225
+ children: "Bottom"
226
+ }),
227
+ /* @__PURE__ */ jsx("input", {
228
+ id: fieldIds.marginBottom,
229
+ type: "number",
230
+ className: inputCls,
231
+ min: 0,
232
+ max: 10,
233
+ step: .1,
234
+ value: twipsToInches(marginBottom),
235
+ onChange: (e) => setMarginBottom(inchesToTwips(Number(e.target.value) || 0))
236
+ }),
237
+ /* @__PURE__ */ jsx("span", {
238
+ className: "text-muted-foreground w-4 text-[11px]",
239
+ children: "in"
240
+ })
241
+ ]
242
+ }),
243
+ /* @__PURE__ */ jsxs("div", {
244
+ className: "flex items-center gap-3",
245
+ children: [
246
+ /* @__PURE__ */ jsx("label", {
247
+ htmlFor: fieldIds.marginLeft,
248
+ className: labelCls,
249
+ children: "Left"
250
+ }),
251
+ /* @__PURE__ */ jsx("input", {
252
+ id: fieldIds.marginLeft,
253
+ type: "number",
254
+ className: inputCls,
255
+ min: 0,
256
+ max: 10,
257
+ step: .1,
258
+ value: twipsToInches(marginLeft),
259
+ onChange: (e) => setMarginLeft(inchesToTwips(Number(e.target.value) || 0))
260
+ }),
261
+ /* @__PURE__ */ jsx("span", {
262
+ className: "text-muted-foreground w-4 text-[11px]",
263
+ children: "in"
264
+ })
265
+ ]
266
+ }),
267
+ /* @__PURE__ */ jsxs("div", {
268
+ className: "flex items-center gap-3",
269
+ children: [
270
+ /* @__PURE__ */ jsx("label", {
271
+ htmlFor: fieldIds.marginRight,
272
+ className: labelCls,
273
+ children: "Right"
274
+ }),
275
+ /* @__PURE__ */ jsx("input", {
276
+ id: fieldIds.marginRight,
277
+ type: "number",
278
+ className: inputCls,
279
+ min: 0,
280
+ max: 10,
281
+ step: .1,
282
+ value: twipsToInches(marginRight),
283
+ onChange: (e) => setMarginRight(inchesToTwips(Number(e.target.value) || 0))
284
+ }),
285
+ /* @__PURE__ */ jsx("span", {
286
+ className: "text-muted-foreground w-4 text-[11px]",
287
+ children: "in"
288
+ })
289
+ ]
290
+ })
291
+ ]
292
+ }),
293
+ /* @__PURE__ */ jsxs("div", {
294
+ className: "flex justify-end gap-2 border-t px-5 py-3",
295
+ children: [/* @__PURE__ */ jsx(DialogClose, {
296
+ className: "border-input rounded border px-4 py-1.5 text-[13px]",
297
+ children: "Cancel"
298
+ }), /* @__PURE__ */ jsx("button", {
299
+ className: "bg-primary text-primary-foreground rounded px-4 py-1.5 text-[13px] font-medium",
300
+ onClick: handleApply,
301
+ type: "button",
302
+ children: "Apply"
303
+ })]
304
+ })
305
+ ]
306
+ })] })
307
+ });
308
+ }
309
+ //#endregion
310
+ export { PageSetupDialog };
@@ -0,0 +1,157 @@
1
+ import { r as useFolioUI } from "./folio-ui-o_rftArH.js";
2
+ import { useCallback, useEffect, useId, useState } from "react";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ //#region src/components/dialogs/TablePropertiesDialog.tsx
5
+ /**
6
+ * Table Properties Dialog — width type, width value, alignment.
7
+ */
8
+ function TablePropertiesDialog({ isOpen, onClose, onApply, currentProps }) {
9
+ const { Root: Dialog, Portal: DialogPortal, Backdrop: DialogBackdrop, Popup: DialogPopup, Title: DialogTitle, Close: DialogClose } = useFolioUI().Dialog;
10
+ const id = useId();
11
+ const [width, setWidth] = useState(currentProps?.width ?? 0);
12
+ const [widthType, setWidthType] = useState(currentProps?.widthType ?? "auto");
13
+ const [justification, setJustification] = useState(currentProps?.justification ?? "left");
14
+ useEffect(() => {
15
+ if (isOpen) {
16
+ setWidth(currentProps?.width ?? 0);
17
+ setWidthType(currentProps?.widthType ?? "auto");
18
+ setJustification(currentProps?.justification ?? "left");
19
+ }
20
+ }, [isOpen, currentProps]);
21
+ const handleApply = useCallback(() => {
22
+ const props = { justification: justification === "left" || justification === "center" || justification === "right" ? justification : "left" };
23
+ if (widthType === "auto") {
24
+ props.width = null;
25
+ props.widthType = "auto";
26
+ } else {
27
+ props.width = width;
28
+ props.widthType = widthType;
29
+ }
30
+ onApply(props);
31
+ onClose();
32
+ }, [
33
+ width,
34
+ widthType,
35
+ justification,
36
+ onApply,
37
+ onClose
38
+ ]);
39
+ const labelCls = "w-20 text-muted-foreground text-[13px]";
40
+ const inputCls = "border-input bg-background text-foreground flex-1 rounded border px-2 py-1.5 text-[13px] outline-none";
41
+ const fieldIds = {
42
+ widthType: `${id}-tp-width-type`,
43
+ width: `${id}-tp-width`,
44
+ align: `${id}-tp-align`
45
+ };
46
+ return /* @__PURE__ */ jsx(Dialog, {
47
+ open: isOpen,
48
+ onOpenChange: (open) => {
49
+ if (!open) onClose();
50
+ },
51
+ children: /* @__PURE__ */ jsxs(DialogPortal, { children: [/* @__PURE__ */ jsx(DialogBackdrop, { className: "fixed inset-0 z-[10000] bg-black/50" }), /* @__PURE__ */ jsxs(DialogPopup, {
52
+ className: "bg-popover fixed start-1/2 top-1/2 z-[10001] w-full max-w-[440px] min-w-[360px] -translate-x-1/2 -translate-y-1/2 rounded-lg border shadow-xl",
53
+ children: [
54
+ /* @__PURE__ */ jsx(DialogTitle, {
55
+ className: "border-b px-5 py-3 text-base font-semibold",
56
+ children: "Table Properties"
57
+ }),
58
+ /* @__PURE__ */ jsxs("div", {
59
+ className: "flex flex-col gap-3 px-5 py-4",
60
+ children: [
61
+ /* @__PURE__ */ jsxs("div", {
62
+ className: "flex items-center gap-3",
63
+ children: [/* @__PURE__ */ jsx("label", {
64
+ className: labelCls,
65
+ htmlFor: fieldIds.widthType,
66
+ children: "Width type"
67
+ }), /* @__PURE__ */ jsxs("select", {
68
+ className: inputCls,
69
+ id: fieldIds.widthType,
70
+ onChange: (e) => setWidthType(e.target.value),
71
+ value: widthType,
72
+ children: [
73
+ /* @__PURE__ */ jsx("option", {
74
+ value: "auto",
75
+ children: "Auto"
76
+ }),
77
+ /* @__PURE__ */ jsx("option", {
78
+ value: "dxa",
79
+ children: "Fixed (twips)"
80
+ }),
81
+ /* @__PURE__ */ jsx("option", {
82
+ value: "pct",
83
+ children: "Percentage"
84
+ })
85
+ ]
86
+ })]
87
+ }),
88
+ widthType !== "auto" && /* @__PURE__ */ jsxs("div", {
89
+ className: "flex items-center gap-3",
90
+ children: [
91
+ /* @__PURE__ */ jsx("label", {
92
+ className: labelCls,
93
+ htmlFor: fieldIds.width,
94
+ children: "Width"
95
+ }),
96
+ /* @__PURE__ */ jsx("input", {
97
+ className: inputCls,
98
+ id: fieldIds.width,
99
+ min: 0,
100
+ onChange: (e) => setWidth(Number(e.target.value) || 0),
101
+ step: widthType === "pct" ? 5 : 100,
102
+ type: "number",
103
+ value: width
104
+ }),
105
+ /* @__PURE__ */ jsx("span", {
106
+ className: "text-muted-foreground text-[11px]",
107
+ children: widthType === "pct" ? "(50ths of %)" : "tw"
108
+ })
109
+ ]
110
+ }),
111
+ /* @__PURE__ */ jsxs("div", {
112
+ className: "flex items-center gap-3",
113
+ children: [/* @__PURE__ */ jsx("label", {
114
+ className: labelCls,
115
+ htmlFor: fieldIds.align,
116
+ children: "Alignment"
117
+ }), /* @__PURE__ */ jsxs("select", {
118
+ className: inputCls,
119
+ id: fieldIds.align,
120
+ onChange: (e) => setJustification(e.target.value),
121
+ value: justification,
122
+ children: [
123
+ /* @__PURE__ */ jsx("option", {
124
+ value: "left",
125
+ children: "Left"
126
+ }),
127
+ /* @__PURE__ */ jsx("option", {
128
+ value: "center",
129
+ children: "Center"
130
+ }),
131
+ /* @__PURE__ */ jsx("option", {
132
+ value: "right",
133
+ children: "Right"
134
+ })
135
+ ]
136
+ })]
137
+ })
138
+ ]
139
+ }),
140
+ /* @__PURE__ */ jsxs("div", {
141
+ className: "flex justify-end gap-2 border-t px-5 py-3",
142
+ children: [/* @__PURE__ */ jsx(DialogClose, {
143
+ className: "border-input rounded border px-4 py-1.5 text-[13px]",
144
+ children: "Cancel"
145
+ }), /* @__PURE__ */ jsx("button", {
146
+ className: "bg-primary text-primary-foreground rounded px-4 py-1.5 text-[13px] font-medium",
147
+ onClick: handleApply,
148
+ type: "button",
149
+ children: "Apply"
150
+ })]
151
+ })
152
+ ]
153
+ })] })
154
+ });
155
+ }
156
+ //#endregion
157
+ export { TablePropertiesDialog };