@squaredr/fieldcraft-pro 0.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.
@@ -0,0 +1,409 @@
1
+ import { requireLicense } from '@squaredr/fieldcraft-pro-license';
2
+ import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
3
+ import { cleanPreset, playfulPreset, clinicalPreset, highContrastPreset, modernPreset, darkPreset, FormEngineRenderer } from '@squaredr/fieldcraft-react';
4
+ import { jsxs, jsx } from 'react/jsx-runtime';
5
+
6
+ // src/theme-editor/ThemeEditor.tsx
7
+
8
+ // src/theme-editor/preview-schema.ts
9
+ var PREVIEW_SCHEMA = {
10
+ id: "theme-preview",
11
+ version: "1.0.0",
12
+ title: "Theme Preview",
13
+ description: "See how your theme looks on a real form.",
14
+ sections: [
15
+ {
16
+ id: "s1",
17
+ title: "Contact Information",
18
+ questions: [
19
+ {
20
+ id: "name",
21
+ type: "short_text",
22
+ label: "Full Name",
23
+ required: true,
24
+ placeholder: "Jane Doe"
25
+ },
26
+ {
27
+ id: "email",
28
+ type: "email",
29
+ label: "Email Address",
30
+ required: true,
31
+ placeholder: "jane@example.com"
32
+ },
33
+ {
34
+ id: "department",
35
+ type: "dropdown",
36
+ label: "Department",
37
+ options: [
38
+ { label: "Engineering", value: "engineering" },
39
+ { label: "Design", value: "design" },
40
+ { label: "Marketing", value: "marketing" }
41
+ ]
42
+ },
43
+ {
44
+ id: "rating",
45
+ type: "rating",
46
+ label: "How would you rate this experience?",
47
+ config: { type: "rating", max: 5 }
48
+ },
49
+ {
50
+ id: "notes",
51
+ type: "long_text",
52
+ label: "Additional Notes",
53
+ placeholder: "Any other feedback..."
54
+ }
55
+ ]
56
+ }
57
+ ],
58
+ submitAction: { type: "adapter" }
59
+ };
60
+ var PRESETS = {
61
+ clean: cleanPreset,
62
+ dark: darkPreset,
63
+ modern: modernPreset,
64
+ "high-contrast": highContrastPreset,
65
+ clinical: clinicalPreset,
66
+ playful: playfulPreset
67
+ };
68
+ var SECTIONS = [
69
+ {
70
+ id: "colors",
71
+ label: "Colors",
72
+ themeKey: "colors",
73
+ fields: [
74
+ { kind: "color", key: "primary", label: "Primary" },
75
+ { kind: "color", key: "primaryForeground", label: "Primary Foreground" },
76
+ { kind: "color", key: "secondary", label: "Secondary" },
77
+ { kind: "color", key: "secondaryForeground", label: "Secondary Foreground" },
78
+ { kind: "color", key: "error", label: "Error" },
79
+ { kind: "color", key: "errorForeground", label: "Error Foreground" },
80
+ { kind: "color", key: "warning", label: "Warning" },
81
+ { kind: "color", key: "success", label: "Success" },
82
+ { kind: "color", key: "surface", label: "Surface" },
83
+ { kind: "color", key: "background", label: "Background" },
84
+ { kind: "color", key: "text", label: "Text" },
85
+ { kind: "color", key: "textMuted", label: "Text Muted" },
86
+ { kind: "color", key: "textDisabled", label: "Text Disabled" },
87
+ { kind: "color", key: "border", label: "Border" },
88
+ { kind: "color", key: "borderFocus", label: "Border Focus" },
89
+ { kind: "color", key: "inputBackground", label: "Input Background" }
90
+ ]
91
+ },
92
+ {
93
+ id: "typography",
94
+ label: "Typography",
95
+ themeKey: "typography",
96
+ fields: [
97
+ { kind: "text", key: "fontFamily", label: "Font Family", placeholder: "Inter, system-ui, sans-serif" },
98
+ {
99
+ kind: "select",
100
+ key: "scale",
101
+ label: "Scale",
102
+ options: [
103
+ { label: "Compact", value: "compact" },
104
+ { label: "Comfortable", value: "comfortable" },
105
+ { label: "Spacious", value: "spacious" }
106
+ ]
107
+ },
108
+ { kind: "text", key: "questionSize", label: "Question Size", placeholder: "1.125rem" },
109
+ { kind: "text", key: "labelSize", label: "Label Size", placeholder: "0.875rem" },
110
+ { kind: "text", key: "helpTextSize", label: "Help Text Size", placeholder: "0.8125rem" },
111
+ { kind: "text", key: "bodySize", label: "Body Size", placeholder: "0.9375rem" }
112
+ ]
113
+ },
114
+ {
115
+ id: "shape",
116
+ label: "Shape",
117
+ themeKey: "shape",
118
+ fields: [
119
+ {
120
+ kind: "select",
121
+ key: "radius",
122
+ label: "Radius",
123
+ options: [
124
+ { label: "None", value: "none" },
125
+ { label: "Small", value: "sm" },
126
+ { label: "Medium", value: "md" },
127
+ { label: "Large", value: "lg" },
128
+ { label: "Full", value: "full" }
129
+ ]
130
+ },
131
+ { kind: "text", key: "inputRadius", label: "Input Radius", placeholder: "8px" },
132
+ { kind: "text", key: "buttonRadius", label: "Button Radius", placeholder: "8px" },
133
+ { kind: "text", key: "cardRadius", label: "Card Radius", placeholder: "12px" }
134
+ ]
135
+ },
136
+ {
137
+ id: "spacing",
138
+ label: "Spacing",
139
+ themeKey: "spacing",
140
+ fields: [
141
+ { kind: "number", key: "base", label: "Base", min: 4, max: 32, suffix: "px" },
142
+ { kind: "number", key: "sectionGap", label: "Section Gap", min: 0, max: 64, suffix: "px" },
143
+ { kind: "number", key: "fieldGap", label: "Field Gap", min: 0, max: 64, suffix: "px" },
144
+ { kind: "number", key: "inputPaddingX", label: "Input Padding X", min: 0, max: 32, suffix: "px" },
145
+ { kind: "number", key: "inputPaddingY", label: "Input Padding Y", min: 0, max: 32, suffix: "px" }
146
+ ]
147
+ },
148
+ {
149
+ id: "layout",
150
+ label: "Layout",
151
+ themeKey: "layout",
152
+ fields: [
153
+ { kind: "text", key: "maxWidth", label: "Max Width", placeholder: "640px" },
154
+ {
155
+ kind: "select",
156
+ key: "alignment",
157
+ label: "Alignment",
158
+ options: [
159
+ { label: "Left", value: "left" },
160
+ { label: "Center", value: "center" }
161
+ ]
162
+ },
163
+ {
164
+ kind: "select",
165
+ key: "progressPosition",
166
+ label: "Progress Position",
167
+ options: [
168
+ { label: "Top", value: "top" },
169
+ { label: "Bottom", value: "bottom" },
170
+ { label: "None", value: "none" }
171
+ ]
172
+ },
173
+ {
174
+ kind: "select",
175
+ key: "sectionLayout",
176
+ label: "Section Layout",
177
+ options: [
178
+ { label: "Card", value: "card" },
179
+ { label: "Flat", value: "flat" },
180
+ { label: "Bordered", value: "bordered" }
181
+ ]
182
+ }
183
+ ]
184
+ }
185
+ ];
186
+ function ThemeEditorInner({
187
+ initialTheme,
188
+ onChange,
189
+ onSave,
190
+ height,
191
+ width,
192
+ className,
193
+ toolbarExtra,
194
+ showPreview = true
195
+ }) {
196
+ const [theme, setTheme] = useState(initialTheme ?? cleanPreset);
197
+ const [activeSection, setActiveSection] = useState("colors");
198
+ const [presetKey, setPresetKey] = useState("custom");
199
+ const themeRef = useRef(theme);
200
+ themeRef.current = theme;
201
+ useEffect(() => {
202
+ function handleKeyDown(e) {
203
+ if ((e.metaKey || e.ctrlKey) && e.key === "s") {
204
+ e.preventDefault();
205
+ onSave?.(themeRef.current);
206
+ }
207
+ }
208
+ window.addEventListener("keydown", handleKeyDown);
209
+ return () => window.removeEventListener("keydown", handleKeyDown);
210
+ }, [onSave]);
211
+ const updateField = useCallback(
212
+ (section, key, value) => {
213
+ setTheme((prev) => {
214
+ const next = {
215
+ ...prev,
216
+ [section]: { ...prev[section], [key]: value }
217
+ };
218
+ onChange?.(next);
219
+ return next;
220
+ });
221
+ setPresetKey("custom");
222
+ },
223
+ [onChange]
224
+ );
225
+ const loadPreset = useCallback(
226
+ (key) => {
227
+ const preset = PRESETS[key];
228
+ if (preset) {
229
+ setTheme(preset);
230
+ setPresetKey(key);
231
+ onChange?.(preset);
232
+ }
233
+ },
234
+ [onChange]
235
+ );
236
+ const exportJson = useCallback(() => {
237
+ const blob = new Blob([JSON.stringify(theme, null, 2)], { type: "application/json" });
238
+ const url = URL.createObjectURL(blob);
239
+ const a = document.createElement("a");
240
+ a.href = url;
241
+ a.download = "fieldcraft-theme.json";
242
+ a.click();
243
+ URL.revokeObjectURL(url);
244
+ }, [theme]);
245
+ const importJson = useCallback(() => {
246
+ const input = document.createElement("input");
247
+ input.type = "file";
248
+ input.accept = ".json";
249
+ input.onchange = () => {
250
+ const file = input.files?.[0];
251
+ if (!file) return;
252
+ const reader = new FileReader();
253
+ reader.onload = () => {
254
+ try {
255
+ const parsed = JSON.parse(reader.result);
256
+ setTheme(parsed);
257
+ setPresetKey("custom");
258
+ onChange?.(parsed);
259
+ } catch {
260
+ }
261
+ };
262
+ reader.readAsText(file);
263
+ };
264
+ input.click();
265
+ }, [onChange]);
266
+ const currentSection = useMemo(
267
+ () => SECTIONS.find((s) => s.id === activeSection),
268
+ [activeSection]
269
+ );
270
+ const sectionValues = theme[currentSection.themeKey] ?? {};
271
+ return /* @__PURE__ */ jsxs(
272
+ "div",
273
+ {
274
+ className: `fcte-root${className ? ` ${className}` : ""}`,
275
+ style: {
276
+ height: height ?? "100%",
277
+ width: width ?? "100%"
278
+ },
279
+ children: [
280
+ /* @__PURE__ */ jsxs("div", { className: "fcte-toolbar", children: [
281
+ /* @__PURE__ */ jsxs("div", { className: "fcte-toolbar__left", children: [
282
+ /* @__PURE__ */ jsx("span", { className: "fcte-toolbar__title", children: "Theme Editor" }),
283
+ /* @__PURE__ */ jsxs(
284
+ "select",
285
+ {
286
+ value: presetKey,
287
+ onChange: (e) => loadPreset(e.target.value),
288
+ className: "fcte-toolbar__preset",
289
+ children: [
290
+ /* @__PURE__ */ jsx("option", { value: "custom", disabled: true, children: "Custom" }),
291
+ Object.keys(PRESETS).map((k) => /* @__PURE__ */ jsx("option", { value: k, children: k.charAt(0).toUpperCase() + k.slice(1).replace("-", " ") }, k))
292
+ ]
293
+ }
294
+ )
295
+ ] }),
296
+ /* @__PURE__ */ jsxs("div", { className: "fcte-toolbar__right", children: [
297
+ toolbarExtra,
298
+ /* @__PURE__ */ jsx("button", { onClick: importJson, className: "fcte-btn fcte-btn--secondary", children: "Import" }),
299
+ /* @__PURE__ */ jsx("button", { onClick: exportJson, className: "fcte-btn fcte-btn--secondary", children: "Export" }),
300
+ onSave && /* @__PURE__ */ jsx("button", { onClick: () => onSave(theme), className: "fcte-btn fcte-btn--primary", children: "Save" })
301
+ ] })
302
+ ] }),
303
+ /* @__PURE__ */ jsxs("div", { className: "fcte-body", children: [
304
+ /* @__PURE__ */ jsxs("div", { className: "fcte-editor", children: [
305
+ /* @__PURE__ */ jsx("div", { className: "fcte-tabs", children: SECTIONS.map((s) => /* @__PURE__ */ jsx(
306
+ "button",
307
+ {
308
+ className: `fcte-tab${activeSection === s.id ? " fcte-tab--active" : ""}`,
309
+ onClick: () => setActiveSection(s.id),
310
+ children: s.label
311
+ },
312
+ s.id
313
+ )) }),
314
+ /* @__PURE__ */ jsx("div", { className: "fcte-fields", children: currentSection.fields.map((field) => {
315
+ const val = sectionValues[field.key];
316
+ if (field.kind === "color") {
317
+ return /* @__PURE__ */ jsxs("div", { className: "fcte-field", children: [
318
+ /* @__PURE__ */ jsx("label", { className: "fcte-field__label", children: field.label }),
319
+ /* @__PURE__ */ jsxs("div", { className: "fcte-field__color-row", children: [
320
+ /* @__PURE__ */ jsx(
321
+ "input",
322
+ {
323
+ type: "color",
324
+ value: typeof val === "string" ? val : "#000000",
325
+ onChange: (e) => updateField(currentSection.themeKey, field.key, e.target.value),
326
+ className: "fcte-field__swatch"
327
+ }
328
+ ),
329
+ /* @__PURE__ */ jsx(
330
+ "input",
331
+ {
332
+ type: "text",
333
+ value: typeof val === "string" ? val : "",
334
+ onChange: (e) => updateField(currentSection.themeKey, field.key, e.target.value),
335
+ className: "fcte-field__text",
336
+ spellCheck: false
337
+ }
338
+ )
339
+ ] })
340
+ ] }, field.key);
341
+ }
342
+ if (field.kind === "select") {
343
+ return /* @__PURE__ */ jsxs("div", { className: "fcte-field", children: [
344
+ /* @__PURE__ */ jsx("label", { className: "fcte-field__label", children: field.label }),
345
+ /* @__PURE__ */ jsx(
346
+ "select",
347
+ {
348
+ value: typeof val === "string" ? val : "",
349
+ onChange: (e) => updateField(currentSection.themeKey, field.key, e.target.value),
350
+ className: "fcte-field__select",
351
+ children: field.options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
352
+ }
353
+ )
354
+ ] }, field.key);
355
+ }
356
+ if (field.kind === "number") {
357
+ return /* @__PURE__ */ jsxs("div", { className: "fcte-field", children: [
358
+ /* @__PURE__ */ jsx("label", { className: "fcte-field__label", children: field.label }),
359
+ /* @__PURE__ */ jsxs("div", { className: "fcte-field__number-row", children: [
360
+ /* @__PURE__ */ jsx(
361
+ "input",
362
+ {
363
+ type: "number",
364
+ value: typeof val === "number" ? val : 0,
365
+ onChange: (e) => updateField(currentSection.themeKey, field.key, Number(e.target.value)),
366
+ min: field.min,
367
+ max: field.max,
368
+ className: "fcte-field__number"
369
+ }
370
+ ),
371
+ field.suffix && /* @__PURE__ */ jsx("span", { className: "fcte-field__suffix", children: field.suffix })
372
+ ] })
373
+ ] }, field.key);
374
+ }
375
+ return /* @__PURE__ */ jsxs("div", { className: "fcte-field", children: [
376
+ /* @__PURE__ */ jsx("label", { className: "fcte-field__label", children: field.label }),
377
+ /* @__PURE__ */ jsx(
378
+ "input",
379
+ {
380
+ type: "text",
381
+ value: typeof val === "string" ? val : "",
382
+ onChange: (e) => updateField(currentSection.themeKey, field.key, e.target.value),
383
+ placeholder: field.placeholder,
384
+ className: "fcte-field__text",
385
+ spellCheck: false
386
+ }
387
+ )
388
+ ] }, field.key);
389
+ }) })
390
+ ] }),
391
+ showPreview && /* @__PURE__ */ jsx("div", { className: "fcte-preview", children: /* @__PURE__ */ jsx("div", { className: "fcte-preview__inner", children: /* @__PURE__ */ jsx(
392
+ FormEngineRenderer,
393
+ {
394
+ schema: PREVIEW_SCHEMA,
395
+ theme,
396
+ onSubmit: () => {
397
+ }
398
+ }
399
+ ) }) })
400
+ ] })
401
+ ]
402
+ }
403
+ );
404
+ }
405
+
406
+ // src/theme-editor/ThemeEditor.tsx
407
+ var ThemeEditor = requireLicense(ThemeEditorInner, "ThemeEditor");
408
+
409
+ export { PREVIEW_SCHEMA, ThemeEditor, ThemeEditorInner };