8000 feat: add settings and save button to the CloudEditor · composify-js/composify@f43e83d · GitHub
[go: up one dir, main page]

Skip to content

Commit f43e83d

Browse files
committed
feat: add settings and save button to the CloudEditor
1 parent 6c0f5d3 commit f43e83d

File tree

10 files changed

+363
-17
lines changed

10 files changed

+363
-17
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
.CloudEditor-Controls {
2+
display: flex;
3+
gap: 0.375rem;
4+
}
5+
6+
.CloudEditor-SettingsButton {
7+
padding: 0.4375rem 0.75rem;
8+
font-size: 0.75rem;
9+
font-weight: 500;
10+
line-height: 1.4;
11+
border-radius: 0.25rem;
12+
cursor: pointer;
13+
color: var(--composify-palette-on-surface);
14+
background-color: var(--composify-palette-surface);
15+
border: 1px solid var(--composify-palette-outline);
16+
}
17+
18+
.CloudEditor-SettingsButton:hover {
19+
background-color: var(--composify-palette-surface-container);
20+
transition: background-color 0.15s linear;
21+
}
22+
23+
.CloudEditor-SaveButton {
24+
padding: 0.4375rem 0.75rem;
25+
font-size: 0.75rem;
26+
font-weight: 500;
27+
line-height: 1.4;
28+
border-radius: 0.25rem;
29+
cursor: pointer;
30+
color: var(--composify-palette-on-primary);
31+
background-color: var(--composify-palette-primary);
32+
border: 1px solid var(--composify-palette-primary);
33+
}
34+
35+
.CloudEditor-SaveButton:hover {
36+
opacity: 0.9;
37+
< ED47 span class=pl-c1>transition: opacity 0.15s linear;
38+
}
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
import { type FC, type PropsWithChildren, useMemo } from 'react';
2+
import { Catalog } from '../../renderer';
3+
import { CloudEditor } from './CloudEditor';
4+
5+
type StackProps = PropsWithChildren<{
6+
flexDirection?: 'row' | 'column';
7+
alignItems?: 'flex-start' | 'flex-end' | 'center' | 'stretch';
8+
justifyContent?: 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' | 'space-evenly';
9+
gap?: number;
10+
size?: {
11+
width?: number | string;
12+
minWidth?: number | string;
13+
maxWidth?: number | string;
14+
height?: number | string;
15+
minHeight?: number | string;
16+
maxHeight?: number | string;
17+
};
18+
padding?: { top: number; right: number; bottom: number; left: number };
19+
margin?: { top: number; right: number; bottom: number; left: number };
20+
backgroundColor?: string;
21+
}>;
22+
23+
const Stack: FC<StackProps> = ({
24+
flexDirection,
25+
alignItems,
26+
justifyContent,
27+
gap,
28+
size,
29+
padding,
30+
margin,
31+
backgroundColor,
32+
...props
33+
}) => {
34+
const style = useMemo(
35+
() => ({
36+
display: 'flex',
37+
flexDirection,
38+
alignItems,
39+
justifyContent,
40+
gap,
41+
width: size?.width,
42+
minWidth: size?.minWidth,
43+
maxWidth: size?.maxWidth,
44+
height: size?.height,
45+
minHeight: size?.minHeight,
46+
maxHeight: size?.maxHeight,
47+
paddingLeft: padding?.left,
48+
paddingRight: padding?.right,
49+
paddingTop: padding?.top,
50+
paddingBottom: padding?.bottom,
51+
marginLeft: margin?.left,
52+
marginRight: margin?.right,
53+
marginTop: margin?.top,
54+
marginBottom: margin?.bottom,
55+
backgroundColor,
56+
}),
57+
[flexDirection, alignItems, justifyContent, gap, size, padding, margin, backgroundColor]
58+
);
59+
60+
return <div style={style} {...props} />;
61+
};
62+
63+
type TextProps = {
64+
children: string;
65+
textAlign: 'left' | 'center' | 'right';
66+
};
67+
68+
const Text: FC<TextProps> = ({ children, textAlign }) => <span style={{ textAlign }}>{children}</span>;
69+
70+
Catalog.register('Stack', {
71+
component: Stack,
72+
props: {
73+
flexDirection: {
74+
label: 'Flex Direction',
75+
type: 'radio',
76+
options: [
77+
{ label: 'Row', value: 'row' },
78+
{ label: 'Column', value: 'column' },
79+
],
80+
default: 'row',
81+
},
82+
justifyContent: {
83+
label: 'Justify Content',
84+
type: 'select',
85+
options: [
86+
{ label: 'Start', value: 'flex-start' },
87+
{ label: 'End', value: 'flex-end' },
88+
{ label: 'Center', value: 'center' },
89+
{ label: 'Space Between', value: 'space-between' },
90+
{ label: 'Space Around', value: 'space-around' },
91+
{ label: 'Space Evenly', value: 'space-evenly' },
92+
],
93+
default: 'flex-start',
94+
optional: true,
95+
},
96+
alignItems: {
97+
label: 'Align Items',
98+
type: 'select',
99+
options: [
100+
{ label: 'Start', value: 'flex-start' },
101+
{ label: 'End', value: 'flex-end' },
102+
{ label: 'Center', value: 'center' },
103+
{ label: 'Stretch', value: 'stretch' },
104+
],
105+
default: 'stretch',
106+
optional: true,
107+
},
108+
gap: {
109+
label: 'Gap',
110+
type: 'number',
111+
default: 0,
112+
optional: true,
113+
},
114+
size: {
115+
label: 'Size',
116+
type: 'object',
117+
fields: {
118+
width: {
119+
label: 'Width',
120+
type: 'number',
121+
default: 100,
122+
optional: true,
123+
},
124+
minWidth: {
125+
label: 'Min Width',
126+
type: 'number',
127+
default: 0,
128+
optional: true,
129+
},
130+
maxWidth: {
131+
label: 'Max Width',
132+
type: 'number',
133+
default: 1000,
134+
optional: true,
135+
},
136+
height: {
137+
label: 'Height',
138+
type: 'number',
139+
default: 100,
140+
optional: true,
141+
},
142+
minHeight: {
143+
label: 'Min Height',
144+
type: 'number',
145+
default: 0,
146+
optional: true,
147+
},
148+
maxHeight: {
149+
label: 'Max Height',
150+
type: 'number',
151+
default: 1000,
152+
optional: true,
153+
},
154+
},
155+
optional: true,
156+
},
157+
padding: {
158+
label: 'Padding',
159+
type: 'object',
160+
fields: {
161+
top: {
162+
label: 'Top',
163+
type: 'number',
164+
default: 0,
165+
},
166+
right: {
167+
label: 'Right',
168+
type: 'number',
169+
default: 0,
170+
},
171+
bottom: {
172+
label: 'Bottom',
173+
type: 'number',
174+
default: 0,
175+
},
176+
left: {
177+
label: 'Left',
178+
type: 'number',
179+
default: 0,
180+
},
181+
},
182+
optional: true,
183+
},
184+
margin: {
185+
label: 'Margin',
186+
type: 'object',
187+
fields: {
188+
top: {
189+
label: 'Top',
190+
type: 'number',
191+
default: 0,
192+
},
193+
right: {
194+
label: 'Right',
195+
type: 'number',
196+
default: 0,
197+
},
198+
bottom: {
199+
label: 'Bottom',
200+
type: 'number',
201+
default: 0,
202+
},
203+
left: {
204+
label: 'Left',
205+
type: 'number',
206+
default: 0,
207+
},
208+
},
209+
optional: true,
210+
},
211+
backgroundColor: {
212+
label: 'Background Color',
213+
type: 'text',
214+
default: '#ffffff',
215+
optional: true,
216+
},
217+
children: {
218+
label: 'Children',
219+
type: 'node',
220+
},
221+
},
222+
});
223+
224+
Catalog.register('Text', {
225+
component: Text,
226+
props: {
227+
children: {
228+
label: 'Content',
229+
type: 'text',
230+
default: 'Lorem ipsum',
231+
},
232+
textAlign: {
233+
label: 'Text Align',
234+
type: 'radio',
235+
options: [
236+
{ label: 'Left', value: 'left' },
237+
{ label: 'Center', value: 'center' },
238+
{ label: 'Right', value: 'right' },
239+
],
240+
default: 'left',
241+
},
242+
},
243+
});
244+
245+
export const BasicUsage = () => {
246+
return <CloudEditor />;
247+
};

packages/react/src/editor/CloudEditor/CloudEditor.tsx

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,50 @@
1-
import { useEffect, useState, type FC } from 'react';
1+
import { useCallback, useEffect, useRef, useState, type FC } from 'react';
22
import { Parser } from '../../renderer';
3-
import { Bridge, GuestEventType, HostEventType } from '../../utils';
3+
import { Bridge, getClassNameFactory, GuestEventType, HostEventType } from '../../utils';
44
import { type EditingRef } from '../EditingContext';
55
import { Editor } from '../Editor';
66
import { type VisualEditorProps } from '../VisualEditor';
7+
import styles from './CloudEditor.module.css';
8+
9+
const getClassName = getClassNameFactory('CloudEditor', styles);
710

811
type Props = VisualEditorProps;
912

1013
export const CloudEditor: FC<Props> = ({ viewports }) =>< 474D /span> {
14+
const bridgeRef = useRef<Bridge | null>(null);
15+
1116
const [editingRef, setEditingRef] = useState<EditingRef | null>(null);
1217
const [title, setTitle] = useState('Untitled');
1318
const [initialized, setInitialized] = useState(false);
1419

20+
const handleClickSettings = useCallback(() => {
21+
if (!bridgeRef.current) {
22+
return;
23+
}
24+
25+
bridgeRef< C473 /span>.current.emit({ type: GuestEventType.SettingsClicked });
26+
}, []);
27+
28+
const handleClickSave = useCallback(() => {
29+
if (!bridgeRef.current || !editingRef) {
30+
return;
31+
}
32+
33+
bridgeRef.current.emit({
34+
type: GuestEventType.SaveClicked,
35+
content: editingRef.getSource(),
36+
});
37+
}, [editingRef]);
38+
1539
useEffect(() => {
1640
if (!editingRef) {
1741
return;
1842
}
1943

20-
const bridge = new Bridge(window.parent);
44+
bridgeRef.current = new Bridge(window.parent);
2145

2246
const { replaceRoot, getSource } = editingRef;
23-
const { on, emit, dispose } = bridge;
47+
const { on, emit, dispose } = bridgeRef.current;
2448

2549
on(HostEventType.Initialize, data => {
2650
setTitle(data.title);
@@ -44,5 +68,22 @@ export const CloudEditor: FC<Props> = ({ viewports }) => {
4468
return dispose;
4569
}, [editingRef, initialized]);
4670

47-
return <Editor ref={setEditingRef} title={title} source="<></>" viewports={viewports} renderControl={() => null} />;
71+
return (
72+
<Editor
73+
ref={setEditingRef}
74+
title={title}
75+
source="<></>"
76+
viewports={viewports}
77+
renderControl={() => (
78+
<div className={getClassName('Controls')}>
79+
<button type="button" className={getClassName('SettingsButton')} onClick={handleClickSettings}>
80+
Settings
81+
</button>
82+
<button type="button" className={getClassName('SaveButton')} onClick={handleClickSave}>
83+
Save
84+
</button>
85+
</div>
86+
)}
87+
/>
88+
);
4889
};

packages/react/src/editor/Editor/Editor.module.css

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22
flex: 1;
33
display: flex;
44
height: 100vh;
5-
6-
font-family: system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Noto Sans,Ubuntu,Cantarell,Helvetica Neue;
7-
line-height: 1.4;
85
}
96

107
.Editor-Divider {

0 commit comments

Comments
 (0)
0