PluginProbe
Tableberg – Simple Gutenberg Table Block / 1.1.5
Tableberg – Simple Gutenberg Table Block v1.1.5
1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.5 1.0.4 1.0.3 1.0.2 1.0.1 trunk 0.0.2 0.2.1 0.3.2 0.3.3 0.4.1 0.5.0 0.5.1 0.5.2 0.5.3 0.5.4 0.5.5 0.5.6 0.5.7 All 42 releases
tableberg / src / blocks / button / element.tsx

element.tsx in Tableberg – Simple Gutenberg Table Block 1.1.5, at src/blocks/button/element.tsx

336 lines 10.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { useRef } from "react";
2 import { RichText } from "@wordpress/block-editor";
3 import { __ } from "@wordpress/i18n";
4 import classNames from "classnames";
5 import { mergeAttrsWithDefaultsAndApplyBindings } from "@tableberg/shared/utils/merge-attrs-with-defaults-and-apply-bindings";
6
7 import { CellKey } from "../../attributes";
8 import { useTableStore } from "../../store";
9 import { useClickOutside } from "../../hooks/useClickOutside";
10 import { ElementRendererProps } from "../../elements";
11 import { ButtonElementControls } from "./controls";
12 import { BindableAttribute, ElementBindings } from "../../dynamic-data/types";
13 import { useDynamicDataBindings } from "../../dynamic-data/hooks/useDynamicData";
14 import { HighlightedText } from "../../components/HighlightedText";
15 import { useBlockCardUpdateShim } from "../../hooks/block-editor-compat";
16 import {
17 elementAlignmentToJustifyContent,
18 ElementAlignment,
19 } from "../../alignment";
20 import { isProAvailable } from "../../pro-status";
21
22 export interface ButtonElementAttributes {
23 content: string;
24 id: string;
25 align: ElementAlignment;
26 link: {
27 url: string;
28 target: "_blank" | "_self";
29 };
30 styles: {
31 backgroundColor: string;
32 textColor: string;
33 backgroundHoverColor: string;
34 textHoverColor: string;
35
36 textAlign: "left" | "center" | "right";
37 width: "auto" | "25%" | "50%" | "75%" | "100%";
38
39 padding: {
40 top: string;
41 right: string;
42 bottom: string;
43 left: string;
44 };
45
46 borderRadius: {
47 topLeft: string;
48 topRight: string;
49 bottomRight: string;
50 bottomLeft: string;
51 };
52
53 fontSize: string;
54 };
55 }
56
57 export const buttonAttrDefaults: ButtonElementAttributes = {
58 content: "",
59 id: "",
60 align: "left",
61 link: {
62 url: "",
63 target: "_self",
64 },
65 styles: {
66 backgroundColor: "#000000",
67 textColor: "#ffffff",
68 backgroundHoverColor: "",
69 textHoverColor: "",
70 textAlign: "center",
71 width: "auto",
72 padding: {
73 top: "var(--wp--preset--spacing--20)",
74 right: "var(--wp--preset--spacing--20)",
75 bottom: "var(--wp--preset--spacing--20)",
76 left: "var(--wp--preset--spacing--20)",
77 },
78 borderRadius: {
79 topLeft: "4px",
80 topRight: "4px",
81 bottomRight: "4px",
82 bottomLeft: "4px",
83 },
84 fontSize: "1.38rem",
85 },
86 };
87
88 export interface ButtonElementType {
89 name: "button";
90 attributes: ButtonElementAttributes;
91 bindings?: ElementBindings;
92 }
93
94 function getLegacyCustomWidthStyle(
95 width: ButtonElementAttributes["styles"]["width"]
96 ): string | undefined {
97 switch (width) {
98 case "25%":
99 return "calc(25% - (var(--wp--style--block-gap, 0.5em) * 0.75))";
100 case "50%":
101 return "calc(50% - (var(--wp--style--block-gap, 0.5em) * 0.5))";
102 case "75%":
103 return "calc(75% - (var(--wp--style--block-gap, 0.5em) * 0.25))";
104 case "100%":
105 return "100%";
106 default:
107 return undefined;
108 }
109 }
110
111 export function ButtonElement({
112 attributes,
113 bindings,
114 cellCoords,
115 elementIndex,
116 }: ElementRendererProps<ButtonElementAttributes>) {
117 const wrapperRef = useRef<HTMLDivElement>(null);
118
119 const sortPreviewMode = useTableStore(state => state.sortPreviewMode);
120 const isElementSelected = useTableStore(state => state.isElementSelected);
121 const setSelectedElement = useTableStore(state => state.setSelectedElement);
122 const clearSelectedElement = useTableStore(
123 state => state.clearSelectedElement
124 );
125 const updateBlockCard = useBlockCardUpdateShim();
126
127 const isSelected = isElementSelected(cellCoords, elementIndex);
128
129 const { values: previewValues, isLoading: isDynamicLoading } =
130 useDynamicDataBindings(bindings);
131
132 useClickOutside({
133 ref: wrapperRef,
134 onClickOutside: () => {
135 if (isSelected) {
136 clearSelectedElement();
137 }
138 },
139 });
140
141 const buttonAttributes = mergeAttrsWithDefaultsAndApplyBindings(
142 attributes,
143 buttonAttrDefaults,
144 bindings,
145 previewValues,
146 __("(No data)", "tableberg")
147 );
148
149 const hasDynamicBindings = !!bindings && Object.keys(bindings).length > 0;
150 const contentIsBound = !!bindings?.content;
151 const { content, align, styles } = buttonAttributes;
152 const customWidth = getLegacyCustomWidthStyle(styles.width);
153
154 const wrapperStyleCss: Record<string, string | undefined> = {
155 "display": "flex",
156 "justifyContent": elementAlignmentToJustifyContent(align),
157 "width": customWidth ? "100%" : undefined,
158 "--tableberg-button-background-color": styles.backgroundColor,
159 "--tableberg-button-text-color": styles.textColor,
160 "--tableberg-button-hover-background-color":
161 styles.backgroundHoverColor || styles.backgroundColor,
162 "--tableberg-button-text-hover-color":
163 styles.textHoverColor || styles.textColor,
164 };
165
166 const customWidthWrapperStyleCss: Record<string, string | undefined> = {
167 width: customWidth,
168 maxWidth: customWidth ? "none" : undefined,
169 minWidth: customWidth ? "0" : undefined,
170 flexBasis: styles.width === "100%" ? "100%" : undefined,
171 };
172
173 const buttonStyleCss: Record<string, string | undefined> = {
174 display: "inline-block",
175 textAlign: styles.textAlign,
176 fontSize: styles.fontSize,
177 paddingTop: styles.padding.top,
178 paddingRight: styles.padding.right,
179 paddingBottom: styles.padding.bottom,
180 paddingLeft: styles.padding.left,
181 borderTopLeftRadius: styles.borderRadius.topLeft,
182 borderTopRightRadius: styles.borderRadius.topRight,
183 borderBottomRightRadius: styles.borderRadius.bottomRight,
184 borderBottomLeftRadius: styles.borderRadius.bottomLeft,
185 width: customWidth ? "100%" : undefined,
186 };
187
188 return (
189 <>
190 {isSelected && (
191 <ButtonElementControls
192 attributes={buttonAttributes}
193 bindings={bindings}
194 cellCoords={cellCoords}
195 elementIndex={elementIndex}
196 />
197 )}
198 <div
199 ref={wrapperRef}
200 style={wrapperStyleCss}
201 onClick={e => {
202 e.stopPropagation();
203 if (!sortPreviewMode) {
204 setSelectedElement(cellCoords, elementIndex);
205 if (wrapperRef.current) {
206 updateBlockCard(
207 wrapperRef.current,
208 "Button",
209 "A button element within a Tableberg cell"
210 );
211 }
212 }
213 }}
214 >
215 <div style={customWidthWrapperStyleCss}>
216 <ButtonElementContent
217 content={content}
218 cellCoords={cellCoords}
219 elementIndex={elementIndex}
220 contentIsBound={contentIsBound}
221 hasDynamicBindings={hasDynamicBindings}
222 isDynamicLoading={isDynamicLoading}
223 buttonStyleCss={buttonStyleCss}
224 />
225 </div>
226 </div>
227 </>
228 );
229 }
230
231 function ButtonElementContent({
232 content,
233 cellCoords,
234 elementIndex,
235 contentIsBound,
236 hasDynamicBindings,
237 isDynamicLoading,
238 buttonStyleCss,
239 }: {
240 content: string;
241 cellCoords: CellKey;
242 elementIndex: number;
243 contentIsBound: boolean;
244 hasDynamicBindings: boolean;
245 isDynamicLoading: boolean;
246 buttonStyleCss: Record<string, string | undefined>;
247 }) {
248 const sortPreviewMode = useTableStore(state => state.sortPreviewMode);
249 const updateCellElement = useTableStore(state => state.updateCellElement);
250
251 const searchTerm = useTableStore(state => state.searchTerm);
252 const searchConfig = useTableStore(state => state.table.search);
253 const isPro = isProAvailable();
254
255 const shouldHighlight =
256 isPro &&
257 searchConfig?.enabled &&
258 searchConfig?.highlightColor &&
259 searchTerm.trim();
260
261 const baseClassName = classNames(
262 "wp-block-button__link",
263 "wp-element-button"
264 );
265
266 const className = classNames(baseClassName, {
267 "tableberg-dynamic-content": hasDynamicBindings,
268 });
269
270 if (isDynamicLoading) {
271 return (
272 <div className={className} style={buttonStyleCss}>
273 {__("Loading...", "tableberg")}
274 </div>
275 );
276 }
277
278 if (shouldHighlight) {
279 return (
280 <div className={className} style={buttonStyleCss}>
281 <HighlightedText
282 content={content}
283 searchTerm={searchTerm}
284 highlightColor={searchConfig!.highlightColor}
285 />
286 </div>
287 );
288 }
289
290 if (sortPreviewMode || contentIsBound) {
291 return (
292 <div className={className} style={buttonStyleCss}>
293 {content}
294 </div>
295 );
296 }
297
298 return (
299 <RichText
300 className={baseClassName}
301 tagName="div"
302 style={buttonStyleCss}
303 placeholder={__("Button text", "tableberg")}
304 value={content}
305 onChange={content =>
306 updateCellElement(cellCoords, elementIndex, {
307 attributes: { content },
308 })
309 }
310 />
311 );
312 }
313
314 export const buttonBindableAttributes: BindableAttribute[] = [
315 {
316 path: "content",
317 label: __("Button Text", "tableberg"),
318 },
319 {
320 path: "link.url",
321 label: __("Link URL", "tableberg"),
322 },
323 {
324 path: "id",
325 label: __("HTML ID", "tableberg"),
326 },
327 {
328 path: "styles.backgroundColor",
329 label: __("Background Color", "tableberg"),
330 },
331 {
332 path: "styles.textColor",
333 label: __("Text Color", "tableberg"),
334 },
335 ];
336