@bygghammar/cms-platform 0.5.0 → 0.7.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bygghammar/cms-platform",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Shared CMS platform: blocks, block types, i18n core, UI primitives and design-token contract used by every Bygghammar/Voyara site.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -72,7 +72,14 @@
72
72
  "./backoffice/forms/ConditionBuilder": "./src/backoffice/forms/ConditionBuilder.tsx",
73
73
  "./backoffice/forms/RuleSimulator": "./src/backoffice/forms/RuleSimulator.tsx",
74
74
  "./backoffice/forms/SmartTags": "./src/backoffice/forms/SmartTags.tsx",
75
- "./backoffice/forms/ActivityLog": "./src/backoffice/forms/ActivityLog.tsx"
75
+ "./backoffice/forms/ActivityLog": "./src/backoffice/forms/ActivityLog.tsx",
76
+ "./backoffice/ProgressBar": "./src/backoffice/ProgressBar.tsx",
77
+ "./backoffice/PreviewLightbox": "./src/backoffice/PreviewLightbox.tsx",
78
+ "./backoffice/RichTextEditor": "./src/backoffice/RichTextEditor.tsx",
79
+ "./backoffice/media/ConfirmDialog": "./src/backoffice/media/ConfirmDialog.tsx",
80
+ "./backoffice/media/MediaCleanup": "./src/backoffice/media/MediaCleanup.tsx",
81
+ "./backoffice/media/MediaReplaceDialog": "./src/backoffice/media/MediaReplaceDialog.tsx",
82
+ "./backoffice/media/ReuploadOverwrite": "./src/backoffice/media/ReuploadOverwrite.tsx"
76
83
  },
77
84
  "dependencies": {
78
85
  "fflate": "^0.8.3"
@@ -85,7 +92,18 @@
85
92
  "sonner": ">=2",
86
93
  "@supabase/supabase-js": ">=2",
87
94
  "@tanstack/react-start": ">=1",
88
- "@tanstack/react-router": ">=1"
95
+ "@tanstack/react-router": ">=1",
96
+ "@tiptap/react": ">=3",
97
+ "@tiptap/starter-kit": ">=3",
98
+ "@tiptap/pm": ">=3",
99
+ "@tiptap/extensions": ">=3",
100
+ "@tiptap/extension-image": ">=3",
101
+ "@tiptap/extension-text-align": ">=3",
102
+ "@tiptap/extension-highlight": ">=3",
103
+ "@tiptap/extension-subscript": ">=3",
104
+ "@tiptap/extension-superscript": ">=3",
105
+ "@tiptap/extension-text-style": ">=3",
106
+ "@tiptap/extension-table": ">=3"
89
107
  },
90
108
  "peerDependenciesMeta": {
91
109
  "@supabase/supabase-js": {
@@ -96,6 +114,39 @@
96
114
  },
97
115
  "@tanstack/react-router": {
98
116
  "optional": true
117
+ },
118
+ "@tiptap/react": {
119
+ "optional": true
120
+ },
121
+ "@tiptap/starter-kit": {
122
+ "optional": true
123
+ },
124
+ "@tiptap/pm": {
125
+ "optional": true
126
+ },
127
+ "@tiptap/extensions": {
128
+ "optional": true
129
+ },
130
+ "@tiptap/extension-image": {
131
+ "optional": true
132
+ },
133
+ "@tiptap/extension-text-align": {
134
+ "optional": true
135
+ },
136
+ "@tiptap/extension-highlight": {
137
+ "optional": true
138
+ },
139
+ "@tiptap/extension-subscript": {
140
+ "optional": true
141
+ },
142
+ "@tiptap/extension-superscript": {
143
+ "optional": true
144
+ },
145
+ "@tiptap/extension-text-style": {
146
+ "optional": true
147
+ },
148
+ "@tiptap/extension-table": {
149
+ "optional": true
99
150
  }
100
151
  }
101
152
  }
@@ -0,0 +1,66 @@
1
+ import { useEffect, useState } from "react";
2
+ import { Lightbox } from "../ui/Lightbox";
3
+
4
+ const DESIGN_WIDTH = 1280;
5
+
6
+ /**
7
+ * Backoffice preview lightbox: renders a route inside a scaled iframe.
8
+ * Closing follows the shared site behaviour — Escape, backdrop click, or a
9
+ * click on the preview itself (the frame is look-only, covered by a
10
+ * transparent click catcher because iframe clicks never bubble out).
11
+ */
12
+ export function PreviewLightbox({
13
+ open,
14
+ onClose,
15
+ src,
16
+ title,
17
+ }: {
18
+ open: boolean;
19
+ onClose: () => void;
20
+ src: string;
21
+ title: string;
22
+ }) {
23
+ const [scale, setScale] = useState(1);
24
+ const [frameHeight, setFrameHeight] = useState(800);
25
+
26
+ useEffect(() => {
27
+ if (!open) return;
28
+ const measure = () => {
29
+ const availW = Math.max(320, window.innerWidth - 96);
30
+ const availH = Math.max(320, window.innerHeight - 120);
31
+ setScale(Math.min(1, availW / DESIGN_WIDTH));
32
+ setFrameHeight(Math.round(availH / Math.min(1, availW / DESIGN_WIDTH)));
33
+ };
34
+ measure();
35
+ window.addEventListener("resize", measure);
36
+ return () => window.removeEventListener("resize", measure);
37
+ }, [open]);
38
+
39
+ if (!open) return null;
40
+
41
+ return (
42
+ <Lightbox open={open} onClose={onClose} label={title} closeOnContentClick>
43
+ <div
44
+ className="relative bg-white shadow-2xl overflow-hidden"
45
+ style={{
46
+ width: DESIGN_WIDTH * scale,
47
+ height: frameHeight * scale,
48
+ }}
49
+ >
50
+ <iframe
51
+ src={src}
52
+ title={title}
53
+ className="absolute left-0 top-0 border-0"
54
+ style={{
55
+ width: DESIGN_WIDTH,
56
+ height: frameHeight,
57
+ transform: `scale(${scale})`,
58
+ transformOrigin: "top left",
59
+ }}
60
+ />
61
+ {/* Click catcher: iframe clicks can't bubble to the overlay. */}
62
+ <div className="absolute inset-0 cursor-pointer" onClick={onClose} />
63
+ </div>
64
+ </Lightbox>
65
+ );
66
+ }
@@ -0,0 +1,36 @@
1
+ type Props = {
2
+ /** 0–100, or null for an indeterminate bar */
3
+ value: number | null;
4
+ label?: string;
5
+ detail?: string;
6
+ };
7
+
8
+ /** Slim determinate/indeterminate progress bar used by the backoffice long-running tools. */
9
+ export function ProgressBar({ value, label, detail }: Props) {
10
+ const pct = value === null ? null : Math.max(0, Math.min(100, value));
11
+ return (
12
+ <div className="w-full">
13
+ {label || detail ? (
14
+ <div className="mb-2 flex items-baseline justify-between gap-4">
15
+ <span className="text-[0.65rem] uppercase tracking-[0.22em] text-ink/60">{label}</span>
16
+ <span className="text-[0.65rem] tabular-nums text-ink/45">
17
+ {detail}
18
+ {pct !== null ? `${detail ? " · " : ""}${Math.round(pct)}%` : ""}
19
+ </span>
20
+ </div>
21
+ ) : null}
22
+ <div className="h-1.5 w-full overflow-hidden bg-ink/10">
23
+ {pct === null ? (
24
+ <div className="h-full w-1/3 animate-[progress-slide_1.2s_ease-in-out_infinite] bg-sea" />
25
+ ) : (
26
+ <div
27
+ className="h-full bg-sea transition-[width] duration-300 ease-out"
28
+ style={{ width: `${pct}%` }}
29
+ />
30
+ )}
31
+ </div>
32
+ </div>
33
+ );
34
+ }
35
+
36
+ export default ProgressBar;