PluginProbe
AI Builder – Generate pages, blocks, images & translate with AI / 2.7.2
AI Builder – Generate pages, blocks, images & translate with AI v2.7.2
2.7.10 2.7.9 2.7.8 2.0.8 2.0.9 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.3.0 2.3.10 All 122 releases
ai-builder / assets / js / src / editor-blocks / snackbar / index.js

index.js in AI Builder – Generate pages, blocks, images & translate with AI 2.7.2, at assets/js/src/editor-blocks/snackbar/index.js

375 lines 11.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 const { registerBlockType } = wp.blocks;
2 const { __ } = wp.i18n;
3 const { PanelBody, TextControl, Button, Notice, ToggleControl, RangeControl } =
4 wp.components;
5 const { InspectorControls, useBlockProps } = wp.blockEditor || wp.editor;
6
7 // Use HTML5 color input instead of WordPress ColorPicker to avoid compatibility issues
8 const SafeColorPicker = ({ color, onChange, disableAlpha }) => (
9 <div style={{ position: "relative" }}>
10 <input
11 type="color"
12 value={color || "#4f46e5"}
13 onChange={(e) => onChange && onChange(e.target.value)}
14 style={{
15 width: "100%",
16 height: "40px",
17 border: "1px solid #ddd",
18 borderRadius: "4px",
19 cursor: "pointer",
20 }}
21 />
22 </div>
23 );
24
25 registerBlockType("ai-builder/aibui-snackbar", {
26 title: __("Snackbar (AI Builder)", "ai-builder"),
27 icon: "info",
28 category: "widgets",
29 supports: { align: ["wide", "full"], html: false },
30 attributes: {
31 message: { type: "string", default: "This is a snackbar message" },
32 buttons: {
33 type: "array",
34 default: [
35 {
36 id: "btn1",
37 text: "Action",
38 textColor: "#ffffff",
39 backgroundColor: "#4f46e5",
40 link: "",
41 autoClose: true,
42 },
43 ],
44 },
45 globalAutoClose: { type: "boolean", default: true },
46 globalCloseDelay: { type: "number", default: 5000 },
47 },
48 edit: (props) => {
49 const { attributes, setAttributes } = props;
50 const { message, buttons, globalAutoClose, globalCloseDelay } = attributes;
51 const blockProps = useBlockProps({ className: "aibui-snackbar" });
52
53 const addButton = () => {
54 const newButton = {
55 id: `btn${Date.now()}`,
56 text: "New Button",
57 textColor: "#ffffff",
58 backgroundColor: "#4f46e5",
59 link: "",
60 autoClose: true,
61 };
62 setAttributes({
63 buttons: [...buttons, newButton],
64 });
65 };
66
67 const removeButton = (buttonId) => {
68 setAttributes({
69 buttons: buttons.filter((btn) => btn.id !== buttonId),
70 });
71 };
72
73 const updateButton = (buttonId, field, value) => {
74 setAttributes({
75 buttons: buttons.map((btn) =>
76 btn.id === buttonId ? { ...btn, [field]: value } : btn
77 ),
78 });
79 };
80
81 return (
82 <div {...blockProps}>
83 <InspectorControls>
84 <PanelBody title={__("Snackbar Settings", "ai-builder")} initialOpen>
85 <TextControl
86 label={__("Message", "ai-builder")}
87 value={message}
88 onChange={(val) => setAttributes({ message: val || "" })}
89 />
90
91 <ToggleControl
92 label={__("Global auto-close", "ai-builder")}
93 checked={globalAutoClose}
94 onChange={(val) => setAttributes({ globalAutoClose: !!val })}
95 help={__(
96 "When enabled, the snackbar will auto-hide after the specified delay",
97 "ai-builder"
98 )}
99 />
100
101 <TextControl
102 label={__("Auto-hide delay (seconds)", "ai-builder")}
103 value={Math.floor(globalCloseDelay / 1000)}
104 onChange={(val) => {
105 const numValue = parseInt(val) || 0;
106 const clampedValue = Math.max(0, Math.min(60, numValue));
107 setAttributes({ globalCloseDelay: clampedValue * 1000 });
108 }}
109 type="number"
110 min="0"
111 max="60"
112 help={__("", "ai-builder")}
113 />
114
115 <div style={{ marginTop: "16px" }}>
116 <h4>{__("Buttons", "ai-builder")}</h4>
117 {buttons.map((button, index) => (
118 <div
119 key={button.id}
120 style={{
121 border: "1px solid #e0e0e0",
122 borderRadius: "8px",
123 padding: "12px",
124 marginBottom: "12px",
125 backgroundColor: "#f9f9f9",
126 }}
127 >
128 <div
129 style={{
130 display: "flex",
131 justifyContent: "space-between",
132 alignItems: "center",
133 marginBottom: "8px",
134 }}
135 >
136 <strong>
137 {__("Button", "ai-builder")} {index + 1}
138 </strong>
139 <Button
140 isDestructive
141 isSmall
142 onClick={() => removeButton(button.id)}
143 >
144 {__("Remove", "ai-builder")}
145 </Button>
146 </div>
147
148 <TextControl
149 label={__("Button Text", "ai-builder")}
150 value={button.text}
151 onChange={(val) =>
152 updateButton(button.id, "text", val || "")
153 }
154 />
155
156 <TextControl
157 label={__("Link (optional)", "ai-builder")}
158 value={button.link}
159 onChange={(val) =>
160 updateButton(button.id, "link", val || "")
161 }
162 help={__("Leave empty for no link", "ai-builder")}
163 />
164
165 <div style={{ marginTop: "12px" }}>
166 <p style={{ margin: "0 0 8px" }}>
167 {__("Text Color", "ai-builder")}
168 </p>
169 <SafeColorPicker
170 color={button.textColor}
171 onChange={(val) =>
172 updateButton(button.id, "textColor", val || "#ffffff")
173 }
174 />
175 </div>
176
177 <div style={{ marginTop: "12px" }}>
178 <p style={{ margin: "0 0 8px" }}>
179 {__("Background Color", "ai-builder")}
180 </p>
181 <SafeColorPicker
182 color={button.backgroundColor}
183 onChange={(val) =>
184 updateButton(
185 button.id,
186 "backgroundColor",
187 val || "#4f46e5"
188 )
189 }
190 />
191 </div>
192 <div style={{ marginTop: "12px" }}>
193 <ToggleControl
194 label={__("Auto-close on click", "ai-builder")}
195 checked={button.autoClose !== false}
196 onChange={(val) =>
197 updateButton(button.id, "autoClose", !!val)
198 }
199 style={{ marginTop: "8px" }}
200 help={__(
201 "When enabled, clicking this button will close the snackbar",
202 "ai-builder"
203 )}
204 />
205 </div>
206 </div>
207 ))}
208
209 <Button isPrimary onClick={addButton}>
210 {__("Add Button", "ai-builder")}
211 </Button>
212 </div>
213 </PanelBody>
214 </InspectorControls>
215
216 <SnackbarPreview message={message} buttons={buttons} />
217 </div>
218 );
219 },
220 save: (props) => {
221 const { attributes } = props;
222 const { message, buttons, globalAutoClose, globalCloseDelay } = attributes;
223 const blockProps = wp.blockEditor
224 ? wp.blockEditor.useBlockProps.save({ className: "aibui-snackbar" })
225 : {};
226
227 return (
228 <div {...blockProps}>
229 <div
230 className="aibui-snackbar-container"
231 data-global-auto-close={globalAutoClose ? "1" : "0"}
232 data-global-close-delay={globalCloseDelay}
233 data-buttons={JSON.stringify(buttons)}
234 >
235 <div className="aibui-snackbar-content">
236 <span className="aibui-snackbar-message">{message}</span>
237 <div className="aibui-snackbar-buttons">
238 {buttons.map((button) => {
239 const buttonElement = (
240 <button
241 key={button.id}
242 className="aibui-snackbar-btn aibui-snackbar-action-btn"
243 style={{
244 color: button.textColor,
245 backgroundColor: button.backgroundColor,
246 border: "none",
247 borderRadius: "6px",
248 padding: "8px 16px",
249 marginLeft: "8px",
250 cursor: "pointer",
251 fontSize: "14px",
252 fontWeight: "500",
253 transition: "all 0.2s ease",
254 }}
255 onMouseEnter={(e) => {
256 e.target.style.opacity = "0.9";
257 }}
258 onMouseLeave={(e) => {
259 e.target.style.opacity = "1";
260 }}
261 >
262 {button.text}
263 </button>
264 );
265
266 return button.link ? (
267 <a
268 key={button.id}
269 href={button.link}
270 target="_blank"
271 rel="noopener noreferrer"
272 style={{ textDecoration: "none" }}
273 >
274 {buttonElement}
275 </a>
276 ) : (
277 buttonElement
278 );
279 })}
280 </div>
281 </div>
282 </div>
283 </div>
284 );
285 },
286 });
287
288 function SnackbarPreview({ message, buttons }) {
289 return (
290 <div
291 className="aibui-snackbar-container"
292 style={{
293 position: "fixed",
294 bottom: "20px",
295 left: "20px",
296 right: "20px",
297 maxWidth: "600px",
298 margin: "0 auto",
299 zIndex: 1000,
300 }}
301 >
302 <div
303 className="aibui-snackbar-content"
304 style={{
305 backgroundColor: "#333",
306 color: "#fff",
307 padding: "16px 20px",
308 borderRadius: "8px",
309 display: "flex",
310 alignItems: "center",
311 justifyContent: "space-between",
312 boxShadow: "0 4px 12px rgba(0,0,0,0.3)",
313 border: "1px solid rgba(255,255,255,0.1)",
314 }}
315 >
316 <span
317 className="aibui-snackbar-message"
318 style={{
319 flex: 1,
320 fontSize: "14px",
321 lineHeight: "1.4",
322 }}
323 >
324 {message}
325 </span>
326 <div
327 className="aibui-snackbar-buttons"
328 style={{
329 display: "flex",
330 alignItems: "center",
331 marginLeft: "16px",
332 }}
333 >
334 {buttons.map((button) => {
335 const buttonElement = (
336 <button
337 key={button.id}
338 className="aibui-snackbar-btn"
339 style={{
340 color: button.textColor,
341 backgroundColor: button.backgroundColor,
342 border: "none",
343 borderRadius: "6px",
344 padding: "8px 16px",
345 marginLeft: "8px",
346 cursor: "pointer",
347 fontSize: "14px",
348 fontWeight: "500",
349 transition: "all 0.2s ease",
350 }}
351 >
352 {button.text}
353 </button>
354 );
355
356 return button.link ? (
357 <a
358 key={button.id}
359 href={button.link}
360 target="_blank"
361 rel="noopener noreferrer"
362 style={{ textDecoration: "none" }}
363 >
364 {buttonElement}
365 </a>
366 ) : (
367 buttonElement
368 );
369 })}
370 </div>
371 </div>
372 </div>
373 );
374 }
375