PluginProbe
Tableberg – Simple Gutenberg Table Block / 1.2.0
Tableberg – Simple Gutenberg Table Block v1.2.0
1.2.0 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 All 43 releases
tableberg / src / blocks / button / element.tsx

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

379 lines 11.4 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 type ButtonVariant = "fill" | "outline";
23
24 export interface ButtonElementAttributes {
25 content: string;
26 id: string;
27 align: ElementAlignment;
28 link: {
29 url: string;
30 target: "_blank" | "_self";
31 };
32 styles: {
33 /** Filled like the core Buttons block, or just an outline of it. */
34 variant: ButtonVariant;
35 backgroundColor: string;
36 textColor: string;
37 backgroundHoverColor: string;
38 textHoverColor: string;
39
40 textAlign: "left" | "center" | "right";
41 width: "auto" | "25%" | "50%" | "75%" | "100%";
42
43 padding: {
44 top: string;
45 right: string;
46 bottom: string;
47 left: string;
48 };
49
50 borderRadius: {
51 topLeft: string;
52 topRight: string;
53 bottomRight: string;
54 bottomLeft: string;
55 };
56
57 fontSize: string;
58 };
59 }
60
61 export const buttonAttrDefaults: ButtonElementAttributes = {
62 content: "",
63 id: "",
64 align: "left",
65 link: {
66 url: "",
67 target: "_self",
68 },
69 styles: {
70 variant: "fill",
71 backgroundColor: "#000000",
72 textColor: "#ffffff",
73 backgroundHoverColor: "",
74 textHoverColor: "",
75 textAlign: "center",
76 width: "auto",
77 padding: {
78 top: "var(--wp--preset--spacing--20)",
79 right: "var(--wp--preset--spacing--20)",
80 bottom: "var(--wp--preset--spacing--20)",
81 left: "var(--wp--preset--spacing--20)",
82 },
83 borderRadius: {
84 topLeft: "4px",
85 topRight: "4px",
86 bottomRight: "4px",
87 bottomLeft: "4px",
88 },
89 // Inherited from the table unless the user sets one.
90 fontSize: "",
91 },
92 };
93
94 export interface ButtonElementType {
95 name: "button";
96 attributes: ButtonElementAttributes;
97 bindings?: ElementBindings;
98 }
99
100 /**
101 * What a button paints with, in either variant.
102 *
103 * Filled is the plain case: the background colour behind the text colour.
104 * Outline swaps that around, the way the core Buttons block does: nothing
105 * behind the text, and the button's colour moves to the text and a border
106 * around it. Hover then fills it in, so the text colour is readable again.
107 */
108 export function getButtonVariantStyles(
109 styles: Pick<
110 ButtonElementAttributes["styles"],
111 | "variant"
112 | "backgroundColor"
113 | "textColor"
114 | "backgroundHoverColor"
115 | "textHoverColor"
116 >
117 ) {
118 const isOutline = styles.variant === "outline";
119
120 return {
121 vars: {
122 "--tableberg-button-background-color": isOutline
123 ? "transparent"
124 : styles.backgroundColor,
125 "--tableberg-button-text-color": isOutline
126 ? styles.backgroundColor
127 : styles.textColor,
128 "--tableberg-button-hover-background-color":
129 styles.backgroundHoverColor || styles.backgroundColor,
130 "--tableberg-button-text-hover-color":
131 styles.textHoverColor || styles.textColor,
132 },
133 border: isOutline
134 ? `2px solid ${styles.backgroundColor}`
135 : undefined,
136 };
137 }
138
139 function getLegacyCustomWidthStyle(
140 width: ButtonElementAttributes["styles"]["width"]
141 ): string | undefined {
142 switch (width) {
143 case "25%":
144 return "calc(25% - (var(--wp--style--block-gap, 0.5em) * 0.75))";
145 case "50%":
146 return "calc(50% - (var(--wp--style--block-gap, 0.5em) * 0.5))";
147 case "75%":
148 return "calc(75% - (var(--wp--style--block-gap, 0.5em) * 0.25))";
149 case "100%":
150 return "100%";
151 default:
152 return undefined;
153 }
154 }
155
156 export function ButtonElement({
157 attributes,
158 bindings,
159 cellCoords,
160 elementIndex,
161 }: ElementRendererProps<ButtonElementAttributes>) {
162 const wrapperRef = useRef<HTMLDivElement>(null);
163
164 const sortPreviewMode = useTableStore(state => state.sortPreviewMode);
165 const isElementSelected = useTableStore(state => state.isElementSelected);
166 const setSelectedElement = useTableStore(state => state.setSelectedElement);
167 const clearSelectedElement = useTableStore(
168 state => state.clearSelectedElement
169 );
170 const updateBlockCard = useBlockCardUpdateShim();
171
172 const isSelected = isElementSelected(cellCoords, elementIndex);
173
174 const { values: previewValues, isLoading: isDynamicLoading } =
175 useDynamicDataBindings(bindings);
176
177 useClickOutside({
178 ref: wrapperRef,
179 onClickOutside: () => {
180 if (isSelected) {
181 clearSelectedElement();
182 }
183 },
184 });
185
186 const buttonAttributes = mergeAttrsWithDefaultsAndApplyBindings(
187 attributes,
188 buttonAttrDefaults,
189 bindings,
190 previewValues,
191 __("(No data)", "tableberg")
192 );
193
194 const hasDynamicBindings = !!bindings && Object.keys(bindings).length > 0;
195 const contentIsBound = !!bindings?.content;
196 const { content, align, styles } = buttonAttributes;
197 const customWidth = getLegacyCustomWidthStyle(styles.width);
198
199 const variantStyles = getButtonVariantStyles(styles);
200
201 const wrapperStyleCss: Record<string, string | undefined> = {
202 "display": "flex",
203 "justifyContent": elementAlignmentToJustifyContent(align),
204 "width": customWidth ? "100%" : undefined,
205 ...variantStyles.vars,
206 };
207
208 const customWidthWrapperStyleCss: Record<string, string | undefined> = {
209 width: customWidth,
210 maxWidth: customWidth ? "none" : undefined,
211 minWidth: customWidth ? "0" : undefined,
212 flexBasis: styles.width === "100%" ? "100%" : undefined,
213 };
214
215 const buttonStyleCss: Record<string, string | undefined> = {
216 display: "inline-block",
217 border: variantStyles.border,
218 textAlign: styles.textAlign,
219 fontSize: styles.fontSize,
220 paddingTop: styles.padding.top,
221 paddingRight: styles.padding.right,
222 paddingBottom: styles.padding.bottom,
223 paddingLeft: styles.padding.left,
224 borderTopLeftRadius: styles.borderRadius.topLeft,
225 borderTopRightRadius: styles.borderRadius.topRight,
226 borderBottomRightRadius: styles.borderRadius.bottomRight,
227 borderBottomLeftRadius: styles.borderRadius.bottomLeft,
228 width: customWidth ? "100%" : undefined,
229 };
230
231 return (
232 <>
233 {isSelected && (
234 <ButtonElementControls
235 attributes={buttonAttributes}
236 bindings={bindings}
237 cellCoords={cellCoords}
238 elementIndex={elementIndex}
239 />
240 )}
241 <div
242 ref={wrapperRef}
243 style={wrapperStyleCss}
244 onClick={e => {
245 e.stopPropagation();
246 if (!sortPreviewMode) {
247 setSelectedElement(cellCoords, elementIndex);
248 if (wrapperRef.current) {
249 updateBlockCard(
250 wrapperRef.current,
251 "Button",
252 "A button element within a Tableberg cell"
253 );
254 }
255 }
256 }}
257 >
258 <div style={customWidthWrapperStyleCss}>
259 <ButtonElementContent
260 content={content}
261 cellCoords={cellCoords}
262 elementIndex={elementIndex}
263 contentIsBound={contentIsBound}
264 hasDynamicBindings={hasDynamicBindings}
265 isDynamicLoading={isDynamicLoading}
266 buttonStyleCss={buttonStyleCss}
267 />
268 </div>
269 </div>
270 </>
271 );
272 }
273
274 function ButtonElementContent({
275 content,
276 cellCoords,
277 elementIndex,
278 contentIsBound,
279 hasDynamicBindings,
280 isDynamicLoading,
281 buttonStyleCss,
282 }: {
283 content: string;
284 cellCoords: CellKey;
285 elementIndex: number;
286 contentIsBound: boolean;
287 hasDynamicBindings: boolean;
288 isDynamicLoading: boolean;
289 buttonStyleCss: Record<string, string | undefined>;
290 }) {
291 const sortPreviewMode = useTableStore(state => state.sortPreviewMode);
292 const updateCellElement = useTableStore(state => state.updateCellElement);
293
294 const searchTerm = useTableStore(state => state.searchTerm);
295 const searchConfig = useTableStore(state => state.table.search);
296 const isPro = isProAvailable();
297
298 const shouldHighlight =
299 isPro &&
300 searchConfig?.enabled &&
301 searchConfig?.highlightColor &&
302 searchTerm.trim();
303
304 const baseClassName = classNames(
305 "wp-block-button__link",
306 "wp-element-button"
307 );
308
309 const className = classNames(baseClassName, {
310 "tableberg-dynamic-content": hasDynamicBindings,
311 });
312
313 if (isDynamicLoading) {
314 return (
315 <div className={className} style={buttonStyleCss}>
316 {__("Loading...", "tableberg")}
317 </div>
318 );
319 }
320
321 if (shouldHighlight) {
322 return (
323 <div className={className} style={buttonStyleCss}>
324 <HighlightedText
325 content={content}
326 searchTerm={searchTerm}
327 highlightColor={searchConfig!.highlightColor}
328 />
329 </div>
330 );
331 }
332
333 if (sortPreviewMode || contentIsBound) {
334 return (
335 <div className={className} style={buttonStyleCss}>
336 {content}
337 </div>
338 );
339 }
340
341 return (
342 <RichText
343 className={baseClassName}
344 tagName="div"
345 style={buttonStyleCss}
346 placeholder={__("Button text", "tableberg")}
347 value={content}
348 onChange={content =>
349 updateCellElement(cellCoords, elementIndex, {
350 attributes: { content },
351 })
352 }
353 />
354 );
355 }
356
357 export const buttonBindableAttributes: BindableAttribute[] = [
358 {
359 path: "content",
360 label: __("Button Text", "tableberg"),
361 },
362 {
363 path: "link.url",
364 label: __("Link URL", "tableberg"),
365 },
366 {
367 path: "id",
368 label: __("HTML ID", "tableberg"),
369 },
370 {
371 path: "styles.backgroundColor",
372 label: __("Background Color", "tableberg"),
373 },
374 {
375 path: "styles.textColor",
376 label: __("Text Color", "tableberg"),
377 },
378 ];
379