@stormlmd/form-builder 0.1.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 (50) hide show
  1. package/README.md +73 -0
  2. package/eslint.config.js +23 -0
  3. package/index.html +13 -0
  4. package/package.json +60 -0
  5. package/public/vite.svg +1 -0
  6. package/src/App.css +42 -0
  7. package/src/App.tsx +83 -0
  8. package/src/assets/react.svg +1 -0
  9. package/src/components/FieldRegistry.ts +34 -0
  10. package/src/components/FormContainer.tsx +25 -0
  11. package/src/components/FormRenderer.tsx +121 -0
  12. package/src/components/builder/DraggableTool.tsx +66 -0
  13. package/src/components/builder/DroppableCanvas.tsx +51 -0
  14. package/src/components/builder/EditorWrapper.tsx +87 -0
  15. package/src/components/builder/FormBuilder.tsx +313 -0
  16. package/src/components/builder/FormChildrenRenderer.tsx +68 -0
  17. package/src/components/builder/IntegrationSettings.tsx +110 -0
  18. package/src/components/builder/PropertiesModal.tsx +75 -0
  19. package/src/components/builder/PropertiesPanel.tsx +858 -0
  20. package/src/components/builder/SortableNode.tsx +53 -0
  21. package/src/components/builder/Toolbox.tsx +123 -0
  22. package/src/components/fields/CheckboxField.tsx +41 -0
  23. package/src/components/fields/DateField.tsx +56 -0
  24. package/src/components/fields/FileUploadField.tsx +45 -0
  25. package/src/components/fields/LabelField.tsx +20 -0
  26. package/src/components/fields/NumberField.tsx +39 -0
  27. package/src/components/fields/RichTextField.tsx +39 -0
  28. package/src/components/fields/SelectField.tsx +64 -0
  29. package/src/components/fields/TextField.tsx +44 -0
  30. package/src/components/layout/FormCol.tsx +30 -0
  31. package/src/components/layout/FormDivider.tsx +19 -0
  32. package/src/components/layout/FormPaper.tsx +85 -0
  33. package/src/components/layout/FormRepeater.tsx +130 -0
  34. package/src/components/layout/FormRow.tsx +61 -0
  35. package/src/components/layout/FormTab.tsx +33 -0
  36. package/src/components/layout/FormTable.tsx +47 -0
  37. package/src/components/layout/FormTableCell.tsx +47 -0
  38. package/src/components/layout/FormTabs.tsx +77 -0
  39. package/src/components/layout/LayoutPlaceholder.tsx +85 -0
  40. package/src/components/registerComponents.ts +30 -0
  41. package/src/index.css +75 -0
  42. package/src/index.ts +5 -0
  43. package/src/main.tsx +10 -0
  44. package/src/store/FormStore.ts +811 -0
  45. package/src/utils/apiTransformer.ts +206 -0
  46. package/src/utils/idGenerator.ts +3 -0
  47. package/tsconfig.app.json +28 -0
  48. package/tsconfig.json +7 -0
  49. package/tsconfig.node.json +26 -0
  50. package/vite.config.ts +46 -0
@@ -0,0 +1,130 @@
1
+ import React from 'react';
2
+ import { Box, Button, Typography, Paper, IconButton } from '@mui/material';
3
+ import DeleteIcon from '@mui/icons-material/Delete';
4
+ import AddIcon from '@mui/icons-material/Add';
5
+ import { observer } from 'mobx-react-lite';
6
+ import { formStore } from '../../store/FormStore';
7
+ import type { FieldProps } from '../FieldRegistry';
8
+ import { LayoutPlaceholder } from './LayoutPlaceholder';
9
+ import { FormChildrenRenderer } from '../builder/FormChildrenRenderer';
10
+
11
+ export const FormRepeater: React.FC<FieldProps> = observer(({ node, path, isEditing }) => {
12
+ const { name, label, addLabel = 'Add Item' } = node.props || {};
13
+ const fullPath = path && name ? `${path}.${name}` : name;
14
+ const items: any[] = (fullPath ? formStore.getValue(fullPath) : []) || [];
15
+ const childrenIds = node.children?.map(c => c.id) || [];
16
+
17
+ const handleAdd = () => {
18
+ if (!fullPath) return;
19
+ const newItems = [...items, {}];
20
+ formStore.updateValue(fullPath, newItems);
21
+ };
22
+
23
+ const handleRemove = (index: number) => {
24
+ if (!fullPath) return;
25
+ const newItems = [...items];
26
+ newItems.splice(index, 1);
27
+ formStore.updateValue(fullPath, newItems);
28
+ };
29
+
30
+ // Note: FormRepeater structure is complex. It renders a list of *values*.
31
+ // And for each value, it renders a set of *fields* (children of repeater node).
32
+ // The DRAG & DROP here typically refers to reordering the *fields* in the schema (the template),
33
+ // OR reordering the *items* in the data array?
34
+ // In "Builder" mode, we are editing the template. So we want to reorder the *fields*.
35
+
36
+ // So inside each item Paper, we render the children fields.
37
+ // If we wrap THAT in SortableContext, we can reorder fields.
38
+ // However, since we render fields Multiple times (once per item),
39
+ // dnd-kit might get confused if IDs are not unique across the entire DOM.
40
+ // But SortableNode uses schema node ID. If we render multiple SortableNodes with SAME ID (one per repeater item),
41
+ // dnd-kit WILL break.
42
+
43
+ // In EDITING mode, typically we don't need to see ALL items.
44
+ // Or we should only allow editing schema in a specific way.
45
+ // For now, let's just wrap the inner children in SortableContext.
46
+ // BUT we must be careful about ID duplication if we render multiple items.
47
+ // Actually, in Builder mode, maybe we should only render ONE item as a preview?
48
+ // Or just render the children directly without mapping over data?
49
+
50
+ // Let's stick to the current implementation but if isEditing is true,
51
+ // maybe we should ensure we only have one "template" instance to avoid ID clash?
52
+ // OR, we just don't support dragging INSIDE a repeater instance for now.
53
+ // Support dragging the repeater ITSELF (which is handled by parent).
54
+
55
+ // Let's add SortableContext for the children of the repeater template.
56
+ // To avoid ID clash, we should only enable SortableNode in the FIRST item if there are multiple.
57
+ // Or, simpler: In Builder mode, force Repeater to show at least 1 item, and maybe ONLY 1 dummy item?
58
+
59
+ // Let's try to just wrap it, but we might hit the ID issue.
60
+ // If we have 2 items, we have 2 rendered "Title" fields with ID "node_123". Dnd-kit will duplicate ids.
61
+
62
+ // PROPOSAL: In isEditing mode, FormRepeater only renders ONE dummy item.
63
+
64
+ // Force only 1 item in Builder mode to prevent ID collisions since we render generic "template" fields
65
+ const displayItems = isEditing ? [{}] : items;
66
+ // If items is empty in preview, show nothing? Or 0 items.
67
+ // But in builder, if items is empty, we MUST show 1 dummy item to allow dropping.
68
+ // The current logic `isEditing ? [{}] : items` handles this well.
69
+
70
+ return (
71
+ <Box mb={1}>
72
+ {label && <Typography variant="h6" mb={0.5}>{label}</Typography>}
73
+
74
+ {displayItems.length === 0 && isEditing && (
75
+ <Box p={1} bgcolor="#f5f5f5" color="text.secondary" sx={{ border: '1px dashed #ccc', borderRadius: 1, textAlign: 'center' }}>
76
+ Repeater Template
77
+ </Box>
78
+ )}
79
+
80
+ {displayItems.map((_item, index) => (
81
+ <Paper key={index} variant="outlined" sx={{ p: 1, mb: 0.5, position: 'relative' }}>
82
+ <Box position="absolute" right={4} top={4} zIndex={5}>
83
+ <IconButton size="small" onClick={() => handleRemove(index)} color="error" disabled={isEditing}>
84
+ <DeleteIcon fontSize="inherit" />
85
+ </IconButton>
86
+ </Box>
87
+
88
+ <Box sx={{ display: 'flex', flexWrap: 'wrap' }}>
89
+ {isEditing ? (
90
+ <LayoutPlaceholder
91
+ isEditing={isEditing}
92
+ label="Item Template"
93
+ isEmpty={childrenIds.length === 0}
94
+ sx={{ p: 0.5, width: '100%' }}
95
+ >
96
+ <FormChildrenRenderer
97
+ parentId={node.id}
98
+ children={node.children}
99
+ isEditing={true}
100
+ layout="horizontal"
101
+ path={`${fullPath}[${index}]`}
102
+ />
103
+ </LayoutPlaceholder>
104
+ ) : (
105
+ <FormChildrenRenderer
106
+ parentId={node.id}
107
+ children={node.children}
108
+ isEditing={false}
109
+ layout="horizontal"
110
+ path={`${fullPath}[${index}]`}
111
+ />
112
+ )}
113
+ </Box>
114
+ </Paper>
115
+ ))}
116
+
117
+ {!isEditing && (
118
+ <Button
119
+ startIcon={<AddIcon />}
120
+ variant="outlined"
121
+ size="small"
122
+ onClick={handleAdd}
123
+ sx={{ mt: 0.5 }}
124
+ >
125
+ {addLabel}
126
+ </Button>
127
+ )}
128
+ </Box>
129
+ );
130
+ });
@@ -0,0 +1,61 @@
1
+ import React from 'react';
2
+ import { observer } from 'mobx-react-lite';
3
+ import type { FieldProps } from '../FieldRegistry';
4
+ import { LayoutPlaceholder } from './LayoutPlaceholder';
5
+ import { FormChildrenRenderer } from '../builder/FormChildrenRenderer';
6
+
7
+ export const FormRow: React.FC<FieldProps> = observer(({ node, path, isEditing }) => {
8
+ const childrenIds = node.children?.map(c => c.id) || [];
9
+
10
+ if (isEditing) {
11
+ return (
12
+ <LayoutPlaceholder isEditing={isEditing} label="Row" isEmpty={childrenIds.length === 0}>
13
+ <FormChildrenRenderer
14
+ parentId={node.id}
15
+ children={node.children}
16
+ isEditing={true}
17
+ layout="horizontal"
18
+ path={path}
19
+ />
20
+ </LayoutPlaceholder>
21
+ );
22
+ }
23
+
24
+ return (
25
+ <FormChildrenRenderer
26
+ parentId={node.id}
27
+ children={node.children}
28
+ isEditing={false}
29
+ layout="horizontal"
30
+ path={path}
31
+ />
32
+ );
33
+ });
34
+
35
+ export const FormCol: React.FC<FieldProps> = observer(({ node, path, isEditing }) => {
36
+ const childrenIds = node.children?.map(c => c.id) || [];
37
+
38
+ if (isEditing) {
39
+ return (
40
+ <LayoutPlaceholder isEditing={isEditing} label="Column" isEmpty={childrenIds.length === 0}>
41
+ <FormChildrenRenderer
42
+ parentId={node.id}
43
+ children={node.children}
44
+ isEditing={true}
45
+ layout="vertical"
46
+ path={path}
47
+ />
48
+ </LayoutPlaceholder>
49
+ );
50
+ }
51
+
52
+ return (
53
+ <FormChildrenRenderer
54
+ parentId={node.id}
55
+ children={node.children}
56
+ isEditing={false}
57
+ layout="vertical"
58
+ path={path}
59
+ />
60
+ );
61
+ });
@@ -0,0 +1,33 @@
1
+ import React from 'react';
2
+ import { observer } from 'mobx-react-lite';
3
+ import type { FieldProps } from '../FieldRegistry';
4
+ import { FormChildrenRenderer } from '../builder/FormChildrenRenderer';
5
+ import { LayoutPlaceholder } from './LayoutPlaceholder';
6
+
7
+ export const FormTab: React.FC<FieldProps> = observer(({ node, path, isEditing }) => {
8
+ const childrenIds = node.children?.map(c => c.id) || [];
9
+
10
+ const content = (
11
+ <FormChildrenRenderer
12
+ parentId={node.id}
13
+ children={node.children}
14
+ isEditing={!!isEditing}
15
+ path={path}
16
+ layout="vertical"
17
+ />
18
+ );
19
+
20
+ if (isEditing) {
21
+ return (
22
+ <LayoutPlaceholder isEditing={isEditing} label={node.props?.label || "Tab Content"} isEmpty={childrenIds.length === 0} sx={{ p: 1 }}>
23
+ {content}
24
+ </LayoutPlaceholder>
25
+ );
26
+ }
27
+
28
+ return (
29
+ <div style={{ padding: 0 }}>
30
+ {content}
31
+ </div>
32
+ );
33
+ });
@@ -0,0 +1,47 @@
1
+ import React from 'react';
2
+ import { Box } from '@mui/material';
3
+ import { observer } from 'mobx-react-lite';
4
+ import type { FieldProps } from '../FieldRegistry';
5
+ import { FormRenderer } from '../FormRenderer';
6
+ import { LayoutPlaceholder } from './LayoutPlaceholder';
7
+
8
+ export const FormTable: React.FC<FieldProps> = observer(({ node, path, isEditing }) => {
9
+ const { rows = 2, cols = 2 } = node.props || {};
10
+
11
+ console.log(`FormTable Render: ${node.id}`, {
12
+ childrenCount: node.children?.length,
13
+ rows,
14
+ cols,
15
+ childTypes: node.children?.map(c => c.type)
16
+ });
17
+
18
+ const gridStyle = {
19
+ display: 'grid',
20
+ gridTemplateColumns: `repeat(${cols}, 1fr)`,
21
+ gridTemplateRows: `repeat(${rows}, minmax(32px, auto))`,
22
+ gap: '4px',
23
+ width: '100%',
24
+ backgroundColor: isEditing ? 'rgba(0, 0, 0, 0.02)' : 'transparent', // Subtle background in edit mode
25
+ };
26
+
27
+ const tableContent = (
28
+ <Box sx={gridStyle}>
29
+ {node.children?.map((child, index) => (
30
+ <Box key={child.id} sx={{ width: '100%', height: '100%' }}>
31
+ <FormRenderer node={child} path={`${path}[${index}]`} isEditing={isEditing} />
32
+ </Box>
33
+ ))}
34
+ </Box>
35
+ );
36
+
37
+ if (isEditing) {
38
+ // Import LayoutPlaceholder at the top if not already
39
+ return (
40
+ <LayoutPlaceholder isEditing={isEditing} label="Table" isEmpty={false}>
41
+ {tableContent}
42
+ </LayoutPlaceholder>
43
+ );
44
+ }
45
+
46
+ return tableContent;
47
+ });
@@ -0,0 +1,47 @@
1
+ import React from 'react';
2
+ import { observer } from 'mobx-react-lite';
3
+ import type { FieldProps } from '../FieldRegistry';
4
+ import { LayoutPlaceholder } from './LayoutPlaceholder';
5
+ import { FormChildrenRenderer } from '../builder/FormChildrenRenderer';
6
+
7
+ export const FormTableCell: React.FC<FieldProps> = observer(({ node, path, isEditing }) => {
8
+ const childrenIds = node.children?.map(c => c.id) || [];
9
+ const isEmpty = childrenIds.length === 0;
10
+
11
+ if (isEditing) {
12
+ return (
13
+ <LayoutPlaceholder
14
+ isEditing={isEditing}
15
+ label="Cell"
16
+ isEmpty={isEmpty}
17
+ sx={{
18
+ height: '100%',
19
+ border: '1px dashed #bbb', // Darker border
20
+ backgroundColor: isEmpty ? '#f9f9f9' : 'transparent', // Light background if empty
21
+ '&:hover': {
22
+ borderColor: '#2196f3',
23
+ backgroundColor: '#f0f7ff'
24
+ }
25
+ }}
26
+ >
27
+ <FormChildrenRenderer
28
+ parentId={node.id}
29
+ children={node.children}
30
+ isEditing={true}
31
+ layout="vertical"
32
+ path={path}
33
+ />
34
+ </LayoutPlaceholder>
35
+ );
36
+ }
37
+
38
+ return (
39
+ <FormChildrenRenderer
40
+ parentId={node.id}
41
+ children={node.children}
42
+ isEditing={false}
43
+ layout="vertical"
44
+ path={path}
45
+ />
46
+ );
47
+ });
@@ -0,0 +1,77 @@
1
+ import React from 'react';
2
+ import { Box, Tabs, Tab, Paper } from '@mui/material';
3
+ import { observer } from 'mobx-react-lite';
4
+ import type { FieldProps } from '../FieldRegistry';
5
+ import { FormRenderer } from '../FormRenderer';
6
+ import { formStore } from '../../store/FormStore';
7
+ import { LayoutPlaceholder } from './LayoutPlaceholder';
8
+
9
+ export const FormTabs: React.FC<FieldProps> = observer(({ node, path, isEditing }) => {
10
+ const [activeTab, setActiveTab] = React.useState(0);
11
+ const allTabs = node.children || [];
12
+ const tabs = isEditing
13
+ ? allTabs
14
+ : allTabs.filter(tabNode => {
15
+ if (!tabNode.condition) return true;
16
+ return formStore.evaluateCondition(tabNode.condition);
17
+ });
18
+
19
+ const { elevation: elevationProp = 1, variant: variantProp } = node.props || {};
20
+ const variant = isEditing ? (variantProp ?? 'outlined') : (variantProp ?? 'transparent');
21
+ const elevation = variant === 'elevation' ? elevationProp : 0;
22
+
23
+ const handleTabChange = (_: React.SyntheticEvent, newValue: number) => {
24
+ setActiveTab(newValue);
25
+ if (tabs[newValue]) {
26
+ formStore.selectNode(tabs[newValue].id);
27
+ }
28
+ };
29
+
30
+ if (!isEditing && tabs.length === 0) {
31
+ return null;
32
+ }
33
+
34
+ if (activeTab >= tabs.length && tabs.length > 0) {
35
+ setActiveTab(0);
36
+ }
37
+
38
+ const currentTabNode = tabs[activeTab];
39
+
40
+ return (
41
+ <Paper
42
+ variant={variant}
43
+ elevation={variant === 'elevation' ? elevation : 0}
44
+ sx={{
45
+ mb: 2,
46
+ display: 'flex',
47
+ flexDirection: 'column',
48
+ backgroundColor: variant === 'transparent' ? 'transparent' : undefined,
49
+ backgroundImage: variant === 'transparent' ? 'none' : undefined,
50
+ boxShadow: variant === 'transparent' ? 'none !important' : undefined,
51
+ border: variant === 'transparent' ? 'none !important' : undefined,
52
+ overflow: 'hidden'
53
+ }}
54
+ >
55
+ <Tabs
56
+ value={tabs.length > 0 ? activeTab : false}
57
+ onChange={handleTabChange}
58
+ sx={{ borderBottom: 1, borderColor: 'divider', minHeight: 48 }}
59
+ variant="scrollable"
60
+ scrollButtons="auto"
61
+ >
62
+ {tabs.map((tabNode, index) => (
63
+ <Tab key={tabNode.id} label={tabNode.props?.label || `Tab ${index + 1}`} />
64
+ ))}
65
+ </Tabs>
66
+ <Box p={0} flexGrow={1}>
67
+ {tabs.length === 0 && isEditing ? (
68
+ <LayoutPlaceholder isEditing={isEditing} label="Tabs" isEmpty={true} sx={{ minHeight: 120 }}>
69
+ <Box />
70
+ </LayoutPlaceholder>
71
+ ) : (
72
+ currentTabNode && <FormRenderer key={currentTabNode.id} node={currentTabNode} path={path} isEditing={isEditing} />
73
+ )}
74
+ </Box>
75
+ </Paper>
76
+ );
77
+ });
@@ -0,0 +1,85 @@
1
+ import React from 'react';
2
+ import { Box, Typography, Chip } from '@mui/material';
3
+
4
+ interface LayoutPlaceholderProps {
5
+ isEditing?: boolean;
6
+ label: string;
7
+ children: React.ReactNode;
8
+ sx?: any;
9
+ isEmpty?: boolean;
10
+ }
11
+
12
+ import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline';
13
+
14
+ export const LayoutPlaceholder: React.FC<LayoutPlaceholderProps> = ({
15
+ isEditing,
16
+ label,
17
+ children,
18
+ sx = {},
19
+ isEmpty
20
+ }) => {
21
+ if (!isEditing) {
22
+ return <>{children}</>;
23
+ }
24
+
25
+ return (
26
+ <Box sx={{
27
+ position: 'relative',
28
+ border: '1px dashed #ccc',
29
+ borderRadius: 1,
30
+ p: 2,
31
+ pt: 4, // Space for label
32
+ minHeight: isEmpty ? 120 : 'auto',
33
+ width: '100%',
34
+ transition: 'all 0.2s',
35
+ boxSizing: 'border-box',
36
+ backgroundColor: isEmpty ? 'rgba(0, 0, 0, 0.01)' : 'transparent',
37
+ '&:hover': {
38
+ borderColor: '#1976d2',
39
+ backgroundColor: 'rgba(25, 118, 210, 0.04)'
40
+ },
41
+ ...sx
42
+ }}>
43
+ <Chip
44
+ label={label}
45
+ size="small"
46
+ sx={{
47
+ position: 'absolute',
48
+ top: 0,
49
+ left: 0,
50
+ borderRadius: '4px 0 4px 0',
51
+ height: 20,
52
+ fontSize: '0.7rem',
53
+ backgroundColor: '#eee',
54
+ color: '#666',
55
+ zIndex: 1
56
+ }}
57
+ />
58
+ {children}
59
+ {isEmpty && (
60
+ <Box
61
+ display="flex"
62
+ flexDirection="column"
63
+ alignItems="center"
64
+ justifyContent="center"
65
+ height="100%"
66
+ position="absolute"
67
+ top={0}
68
+ left={0}
69
+ right={0}
70
+ bottom={0}
71
+ zIndex={0}
72
+ sx={{ opacity: 0.6 }}
73
+ >
74
+ <AddCircleOutlineIcon sx={{ fontSize: 40, mb: 1, color: 'text.secondary' }} />
75
+ <Typography variant="body2" color="text.secondary" fontWeight="medium">
76
+ Empty {label}
77
+ </Typography>
78
+ <Typography variant="caption" color="text.secondary">
79
+ Drag items from toolbox here
80
+ </Typography>
81
+ </Box>
82
+ )}
83
+ </Box>
84
+ );
85
+ };
@@ -0,0 +1,30 @@
1
+ import { registerComponent } from './FieldRegistry';
2
+ import { TextField } from './fields/TextField';
3
+ import { NumberField } from './fields/NumberField';
4
+ import { CheckboxField } from './fields/CheckboxField';
5
+ import { SelectField } from './fields/SelectField';
6
+ import { DateField } from './fields/DateField';
7
+ import { FileUploadField } from './fields/FileUploadField';
8
+ import { RichTextField } from './fields/RichTextField';
9
+ import { FormRow } from './layout/FormRow';
10
+ import { FormCol } from './layout/FormCol';
11
+ import { FormTabs } from './layout/FormTabs';
12
+ import { FormTab } from './layout/FormTab';
13
+ import { FormRepeater } from './layout/FormRepeater';
14
+ import { FormPaper } from './layout/FormPaper';
15
+
16
+ export const registerAllComponents = () => {
17
+ registerComponent('text', TextField);
18
+ registerComponent('number', NumberField);
19
+ registerComponent('checkbox', CheckboxField);
20
+ registerComponent('select', SelectField);
21
+ registerComponent('date', DateField);
22
+ registerComponent('file', FileUploadField);
23
+ registerComponent('richtext', RichTextField);
24
+ registerComponent('row', FormRow);
25
+ registerComponent('col', FormCol);
26
+ registerComponent('tabs', FormTabs);
27
+ registerComponent('tab', FormTab);
28
+ registerComponent('repeater', FormRepeater);
29
+ registerComponent('paper', FormPaper);
30
+ };
package/src/index.css ADDED
@@ -0,0 +1,75 @@
1
+ :root {
2
+ font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
3
+ line-height: 1.5;
4
+ font-weight: 400;
5
+
6
+ color: #213547;
7
+ background-color: #ffffff;
8
+
9
+ font-synthesis: none;
10
+ text-rendering: optimizeLegibility;
11
+ -webkit-font-smoothing: antialiased;
12
+ -moz-osx-font-smoothing: grayscale;
13
+ }
14
+
15
+ a {
16
+ font-weight: 500;
17
+ color: #646cff;
18
+ text-decoration: inherit;
19
+ }
20
+
21
+ a:hover {
22
+ color: #535bf2;
23
+ }
24
+
25
+ body {
26
+ margin: 0;
27
+ min-width: 320px;
28
+ min-height: 100vh;
29
+ background-color: #f0f2f5;
30
+ }
31
+
32
+ h1 {
33
+ font-size: 3.2em;
34
+ line-height: 1.1;
35
+ }
36
+
37
+ h6 {
38
+ color: black !important;
39
+ }
40
+
41
+ button {
42
+ border-radius: 8px;
43
+ border: 1px solid transparent;
44
+ padding: 0.6em 1.2em;
45
+ font-size: 1em;
46
+ font-weight: 500;
47
+ font-family: inherit;
48
+ background-color: #1a1a1a;
49
+ cursor: pointer;
50
+ transition: border-color 0.25s;
51
+ }
52
+
53
+ button:hover {
54
+ border-color: #646cff;
55
+ }
56
+
57
+ button:focus,
58
+ button:focus-visible {
59
+ outline: 4px auto -webkit-focus-ring-color;
60
+ }
61
+
62
+ @media (prefers-color-scheme: light) {
63
+ :root {
64
+ color: #213547;
65
+ background-color: #ffffff;
66
+ }
67
+
68
+ a:hover {
69
+ color: #747bff;
70
+ }
71
+
72
+ button {
73
+ background-color: #f9f9f9;
74
+ }
75
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from './store/FormStore';
2
+ export * from './components/FormRenderer';
3
+ export * from './components/FieldRegistry';
4
+ export * from './components/registerComponents';
5
+ export { FormBuilder } from './components/builder/FormBuilder';
package/src/main.tsx ADDED
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import './index.css'
4
+ import App from './App.tsx'
5
+
6
+ createRoot(document.getElementById('root')!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ )