PluginProbe
AI Builder – Generate pages, blocks, images & translate with AI / 2.1.5
AI Builder – Generate pages, blocks, images & translate with AI v2.1.5
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 / table / index.js

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

335 lines 10.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, ToggleControl, RangeControl, TextareaControl, Notice } =
4 wp.components;
5
6 // Use HTML5 color input instead of WordPress ColorPicker to avoid compatibility issues
7 const SafeColorPicker = ({ color, onChange, disableAlpha }) => (
8 <div style={{ position: "relative" }}>
9 <input
10 type="color"
11 value={color || "#4f46e5"}
12 onChange={(e) => onChange && onChange(e.target.value)}
13 style={{
14 width: "100%",
15 height: "40px",
16 border: "1px solid #ddd",
17 borderRadius: "4px",
18 cursor: "pointer",
19 }}
20 />
21 </div>
22 );
23 const { InspectorControls, useBlockProps } = wp.blockEditor || wp.editor;
24
25 // CSS is enqueued via PHP
26
27 // Helper function to create a light version of a color
28 const lightenColor = (color, opacity = 0.03) => {
29 if (!color) return "rgba(79, 70, 229, 0.03)";
30
31 // Convert hex to RGB
32 const hex = color.replace("#", "");
33 const r = parseInt(hex.substr(0, 2), 16);
34 const g = parseInt(hex.substr(2, 2), 16);
35 const b = parseInt(hex.substr(4, 2), 16);
36
37 const result = `rgba(${r}, ${g}, ${b}, ${opacity})`;
38 console.log(`lightenColor(${color}, ${opacity}) = ${result}`);
39 return result;
40 };
41
42 const SAMPLE = {
43 columns: ["Name", "Email", "Role"],
44 rows: [
45 ["Jane Doe", "jane@example.com", "Editor"],
46 ["John Smith", "john@example.com", "Author"],
47 ["Alice", "alice@example.com", "Admin"],
48 ],
49 };
50
51 registerBlockType("ai-builder/aibui-table", {
52 title: __("Table (AI Builder)", "ai-builder"),
53 icon: "table-col-after",
54 category: "widgets",
55 supports: { align: ["wide", "full"], html: false },
56 attributes: {
57 dataJson: { type: "string", default: JSON.stringify(SAMPLE, null, 2) },
58 searchable: { type: "boolean", default: true },
59 sortable: { type: "boolean", default: true },
60 pageSize: { type: "number", default: 5 },
61 primaryColor: { type: "string", default: "#4f46e5" },
62 },
63 edit: (props) => {
64 const { attributes, setAttributes } = props;
65 const { dataJson, searchable, sortable, pageSize, primaryColor } =
66 attributes;
67 const blockProps = useBlockProps({ className: "aibui-table" });
68
69 let parsed = SAMPLE;
70 let parseError = "";
71 try {
72 const obj = JSON.parse(dataJson || "{}");
73 if (obj && Array.isArray(obj.columns) && Array.isArray(obj.rows)) {
74 parsed = obj;
75 }
76 } catch (e) {
77 parseError = e.message || "";
78 }
79
80 return (
81 <div {...blockProps}>
82 <InspectorControls>
83 <PanelBody title={__("Settings", "ai-builder")} initialOpen>
84 <ToggleControl
85 label={__("Searchable", "ai-builder")}
86 checked={searchable}
87 onChange={(val) => setAttributes({ searchable: !!val })}
88 />
89 <ToggleControl
90 label={__("Sortable", "ai-builder")}
91 checked={sortable}
92 onChange={(val) => setAttributes({ sortable: !!val })}
93 />
94 <RangeControl
95 label={__("Page size", "ai-builder")}
96 value={pageSize}
97 onChange={(val) => setAttributes({ pageSize: Number(val) || 5 })}
98 min={3}
99 max={50}
100 />
101 <div style={{ marginTop: 12 }}>
102 <p style={{ margin: "0 0 8px" }}>
103 {__("Primary color", "ai-builder")}
104 </p>
105 <div
106 style={{
107 display: "flex",
108 alignItems: "center",
109 gap: "8px",
110 marginBottom: "8px",
111 padding: "8px",
112 border: "1px solid #e5e7eb",
113 borderRadius: "6px",
114 backgroundColor: "#f9fafb",
115 }}
116 >
117 <div
118 style={{
119 width: "24px",
120 height: "24px",
121 backgroundColor: primaryColor || "#4f46e5",
122 borderRadius: "4px",
123 border: "1px solid #e5e7eb",
124 }}
125 />
126 <span style={{ fontSize: "12px", color: "#6b7280" }}>
127 {primaryColor || "#4f46e5"}
128 </span>
129 </div>
130 <SafeColorPicker
131 color={primaryColor}
132 onChange={(colorValue) => {
133 console.log("Setting primaryColor to:", colorValue);
134 setAttributes({ primaryColor: colorValue || "#4f46e5" });
135 }}
136 disableAlpha
137 />
138 </div>
139 <TextareaControl
140 label={__("Data (JSON)", "ai-builder")}
141 help='{ "columns": ["Col1","Col2"], "rows": [["A","B"],["C","D"]] }'
142 value={dataJson}
143 onChange={(val) => setAttributes({ dataJson: val })}
144 rows={10}
145 />
146 {parseError ? (
147 <Notice status="warning" isDismissible={false}>
148 {__("JSON parse error: ", "ai-builder") + parseError}
149 </Notice>
150 ) : null}
151 </PanelBody>
152 </InspectorControls>
153
154 <TablePreview
155 data={parsed}
156 searchable={searchable}
157 sortable={sortable}
158 pageSize={pageSize}
159 primaryColor={primaryColor}
160 />
161 </div>
162 );
163 },
164 save: (props) => {
165 const { attributes } = props;
166 const { dataJson, searchable, sortable, pageSize, primaryColor } =
167 attributes;
168 let parsed = SAMPLE;
169 try {
170 const obj = JSON.parse(dataJson || "{}");
171 if (obj && Array.isArray(obj.columns) && Array.isArray(obj.rows)) {
172 parsed = obj;
173 }
174 } catch (e) {}
175 const blockProps = wp.blockEditor
176 ? wp.blockEditor.useBlockProps.save({ className: "aibui-table" })
177 : {};
178 return (
179 <div {...blockProps}>
180 <div
181 className="aibui-table-container"
182 data-searchable={searchable ? "1" : "0"}
183 data-sortable={sortable ? "1" : "0"}
184 data-page-size={Number(pageSize) || 5}
185 style={{
186 "--tb-primary": primaryColor || "#4f46e5",
187 "--tb-primary-light": lightenColor(primaryColor, 0.08),
188 "--tb-primary-lighter": lightenColor(primaryColor, 0.03),
189 }}
190 >
191 <TableMarkup
192 data={parsed}
193 searchable={searchable}
194 sortable={sortable}
195 primaryColor={primaryColor}
196 lightColor={lightenColor(primaryColor, 0.08)}
197 lighterColor={lightenColor(primaryColor, 0.03)}
198 />
199 <div className="aibui-table-footer">
200 <div className="aibui-table-pagination">
201 <button className="aibui-btn">{"<"}</button>
202 <span className="aibui-page-indicator">1 / …</span>
203 <button className="aibui-btn">{">"}</button>
204 </div>
205 </div>
206 </div>
207 </div>
208 );
209 },
210 });
211
212 function TablePreview({ data, searchable, sortable, pageSize, primaryColor }) {
213 return (
214 <div
215 className="aibui-table-container"
216 data-searchable={searchable ? "1" : "0"}
217 data-sortable={sortable ? "1" : "0"}
218 data-page-size={Number(pageSize) || 5}
219 style={{
220 "--tb-primary": primaryColor || "#4f46e5",
221 "--tb-primary-light": lightenColor(primaryColor, 0.08),
222 "--tb-primary-lighter": lightenColor(primaryColor, 0.03),
223 }}
224 >
225 <TableMarkup
226 data={data}
227 searchable={searchable}
228 sortable={sortable}
229 primaryColor={primaryColor}
230 lightColor={lightenColor(primaryColor, 0.08)}
231 lighterColor={lightenColor(primaryColor, 0.03)}
232 />
233 <div className="aibui-table-footer">
234 <div className="aibui-table-pagination">
235 <button className="aibui-btn">{"<"}</button>
236 <span className="aibui-page-indicator">1 / …</span>
237 <button className="aibui-btn">{">"}</button>
238 </div>
239 </div>
240 </div>
241 );
242 }
243
244 function TableMarkup({
245 data,
246 searchable,
247 sortable,
248 primaryColor,
249 lightColor,
250 lighterColor,
251 }) {
252 const columns = Array.isArray(data.columns) ? data.columns : [];
253 const rows = Array.isArray(data.rows) ? data.rows : [];
254 return (
255 <div
256 className="aibui-table-wrap"
257 style={{
258 backgroundColor: lighterColor || "rgba(79, 70, 229, 0.03)",
259 border: "1px solid rgba(0,0,0,0.08)",
260 }}
261 >
262 {searchable ? (
263 <div className="aibui-table-toolbar">
264 <input
265 className="aibui-input"
266 type="search"
267 placeholder={__("Search…", "ai-builder")}
268 />
269 </div>
270 ) : null}
271 <div className="aibui-table-scroll">
272 <table className="aibui-table-el">
273 <thead>
274 <tr>
275 {columns.map((c, i) => (
276 <th
277 key={i}
278 style={{
279 backgroundColor: lightColor || "rgba(79, 70, 229, 0.08)",
280 borderBottom: "1px solid rgba(0,0,0,0.08)",
281 }}
282 >
283 {sortable ? (
284 <button className="aibui-th-btn" type="button">
285 <span>{c}</span>
286 <span className="aibui-sort-icons" aria-hidden>
287 <svg
288 className="aibui-sort-icon aibui-sort-icon--up"
289 width="10"
290 height="10"
291 viewBox="0 0 10 10"
292 >
293 <path d="M5 2 L2 5 H8 Z" fill="#9ca3af" />
294 </svg>
295 <svg
296 className="aibui-sort-icon aibui-sort-icon--down"
297 width="10"
298 height="10"
299 viewBox="0 0 10 10"
300 >
301 <path d="M5 8 L2 5 H8 Z" fill="#9ca3af" />
302 </svg>
303 </span>
304 </button>
305 ) : (
306 <span>{c}</span>
307 )}
308 </th>
309 ))}
310 </tr>
311 </thead>
312 <tbody>
313 {rows.map((r, ri) => (
314 <tr
315 key={ri}
316 style={{
317 backgroundColor:
318 ri % 2 === 0
319 ? "#ffffff"
320 : lighterColor || "rgba(79, 70, 229, 0.03)",
321 borderBottom: "1px solid rgba(0,0,0,0.06)",
322 }}
323 >
324 {columns.map((_, ci) => (
325 <td key={ci}>{r && r[ci] != null ? String(r[ci]) : ""}</td>
326 ))}
327 </tr>
328 ))}
329 </tbody>
330 </table>
331 </div>
332 </div>
333 );
334 }
335