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 / components / ResponsiveControl / index.tsx

index.tsx in Tableberg – Simple Gutenberg Table Block 1.1.5, at src/components/ResponsiveControl/index.tsx

385 lines 14.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { InspectorControls } from "@wordpress/block-editor";
2 import {
3 BaseControl,
4 Button,
5 ButtonGroup,
6 __experimentalNumberControl as NumberControl,
7 PanelBody,
8 SelectControl,
9 ToggleControl,
10 } from "@wordpress/components";
11 import { useDispatch, useSelect } from "@wordpress/data";
12 import { __ } from "@wordpress/i18n";
13 import { desktop, mobile, tablet } from "@wordpress/icons";
14 import LockedControl from "../LockedControl";
15 import {
16 attrDefaults,
17 ResponsiveBreakpoint,
18 ResponsiveConfig,
19 ResponsiveMode,
20 } from "../../attributes";
21 import { isProAvailable } from "../../pro-status";
22 import { useTableStore } from "../../store";
23
24 import "./style.scss";
25
26 type ResponsiveDevice = "desktop" | "tablet" | "mobile";
27
28 type ResponsivePreviewStore = {
29 getDeviceType?: () => string;
30 __experimentalGetPreviewDeviceType?: () => string;
31 };
32
33 type CoreEditorActions = {
34 setDeviceType?: (device: string) => void;
35 };
36
37 type LegacyPreviewActions = {
38 __experimentalSetPreviewDeviceType?: (device: string) => void;
39 };
40
41 const DEVICE_OPTIONS = [
42 {
43 value: "desktop",
44 deviceType: "Desktop",
45 label: __("Desktop", "tableberg"),
46 icon: desktop,
47 },
48 {
49 value: "tablet",
50 deviceType: "Tablet",
51 label: __("Tablet", "tableberg"),
52 icon: tablet,
53 },
54 {
55 value: "mobile",
56 deviceType: "Mobile",
57 label: __("Mobile", "tableberg"),
58 icon: mobile,
59 },
60 ];
61
62 const tableConfigDefaults = attrDefaults.table;
63
64 const DEFAULT_RESPONSIVE_CONFIG =
65 tableConfigDefaults.responsive as ResponsiveConfig;
66
67 const MODE_OPTIONS = [
68 { value: "scroll", label: __("Scroll", "tableberg") },
69 { value: "stack", label: __("Stack", "tableberg") },
70 ];
71
72 function getDefaultBreakpoint(
73 device: "tablet" | "mobile"
74 ): ResponsiveBreakpoint {
75 return { ...DEFAULT_RESPONSIVE_CONFIG[device] };
76 }
77
78 function getPreviewDeviceType(
79 rawDeviceType: string | undefined
80 ): ResponsiveDevice {
81 const normalized = (rawDeviceType || "desktop").toLowerCase();
82
83 if (normalized === "tablet") {
84 return "tablet";
85 }
86
87 if (normalized === "mobile") {
88 return "mobile";
89 }
90
91 return "desktop";
92 }
93
94 function normalizeBreakpoint(
95 responsiveConfig: ResponsiveConfig | undefined,
96 device: "tablet" | "mobile"
97 ): ResponsiveBreakpoint {
98 const defaults = getDefaultBreakpoint(device);
99 const config = responsiveConfig?.[device] as
100 | (Partial<ResponsiveBreakpoint> & {
101 direction?: "row" | "col";
102 headerAsCol?: boolean;
103 })
104 | undefined;
105
106 const normalizedTranspose =
107 typeof config?.transpose === "boolean"
108 ? config.transpose
109 : config?.direction === "row";
110
111 const normalizedRepeatFirstCol =
112 typeof config?.repeatFirstCol === "boolean"
113 ? config.repeatFirstCol
114 : !!config?.headerAsCol;
115
116 return {
117 ...defaults,
118 ...(config || {}),
119 transpose: normalizedTranspose,
120 repeatFirstCol: normalizedRepeatFirstCol,
121 };
122 }
123
124 export function ResponsiveControl() {
125 const isPro = isProAvailable();
126 const tableConfig = useTableStore(state => state.table);
127 const updateTableConfig = useTableStore(state => state.updateTable);
128
129 const previewDevice = useSelect(select => {
130 const editorStore = select("core/editor") as ResponsivePreviewStore;
131 const siteEditorStore = select(
132 "core/edit-site"
133 ) as ResponsivePreviewStore;
134 const postEditorStore = select(
135 "core/edit-post"
136 ) as ResponsivePreviewStore;
137
138 const rawDeviceType =
139 editorStore?.getDeviceType?.() ||
140 siteEditorStore?.__experimentalGetPreviewDeviceType?.() ||
141 postEditorStore?.__experimentalGetPreviewDeviceType?.();
142
143 return getPreviewDeviceType(rawDeviceType);
144 }, []);
145
146 const editorActions = useDispatch("core/editor") as CoreEditorActions;
147 const siteEditorActions = useDispatch(
148 "core/edit-site"
149 ) as LegacyPreviewActions;
150 const postEditorActions = useDispatch(
151 "core/edit-post"
152 ) as LegacyPreviewActions;
153
154 const responsiveConfig = tableConfig.responsive;
155
156 const applyBreakpointUpdates = (
157 device: "tablet" | "mobile",
158 updates: Partial<ResponsiveBreakpoint>
159 ) => {
160 const currentResponsive: ResponsiveConfig = {
161 tablet: normalizeBreakpoint(responsiveConfig, "tablet"),
162 mobile: normalizeBreakpoint(responsiveConfig, "mobile"),
163 };
164
165 updateTableConfig({
166 responsive: {
167 ...currentResponsive,
168 [device]: {
169 ...currentResponsive[device],
170 ...updates,
171 },
172 },
173 });
174 };
175
176 const setPreviewDevice = (deviceLabel: string) => {
177 if (editorActions?.setDeviceType) {
178 editorActions.setDeviceType(deviceLabel);
179 return;
180 }
181
182 if (siteEditorActions?.__experimentalSetPreviewDeviceType) {
183 siteEditorActions.__experimentalSetPreviewDeviceType(deviceLabel);
184 return;
185 }
186
187 if (postEditorActions?.__experimentalSetPreviewDeviceType) {
188 postEditorActions.__experimentalSetPreviewDeviceType(deviceLabel);
189 }
190 };
191
192 const activeDevice = previewDevice === "desktop" ? "tablet" : previewDevice;
193 const activeBreakpoint = normalizeBreakpoint(
194 responsiveConfig,
195 activeDevice
196 );
197 const controlsDisabled = !activeBreakpoint.enabled;
198
199 return (
200 <InspectorControls>
201 <PanelBody title={__("Responsive", "tableberg")}>
202 <BaseControl __nextHasNoMarginBottom>
203 <ButtonGroup className="tableberg-responsiveness-device-switcher-container">
204 {DEVICE_OPTIONS.map(
205 ({ value, deviceType, label, icon }) => (
206 <Button
207 key={value}
208 isPressed={previewDevice === value}
209 icon={icon}
210 className="tableberg-responsiveness-device-switcher"
211 onClick={() => {
212 setPreviewDevice(deviceType);
213 }}
214 >
215 {label}
216 </Button>
217 )
218 )}
219 </ButtonGroup>
220
221 {previewDevice === "desktop" ? (
222 <p style={{ margin: 0, color: "#757575" }}>
223 {__(
224 "Switch to Tablet or Mobile preview to configure responsive behavior.",
225 "tableberg"
226 )}
227 </p>
228 ) : (
229 <>
230 <ToggleControl
231 label={__(
232 "Enable Responsive Rule",
233 "tableberg"
234 )}
235 checked={activeBreakpoint.enabled}
236 onChange={enabled => {
237 applyBreakpointUpdates(activeDevice, {
238 enabled,
239 });
240 }}
241 />
242
243 <NumberControl
244 label={__("Max Width", "tableberg")}
245 value={activeBreakpoint.maxWidth}
246 min={1}
247 suffix="px"
248 spinControls="none"
249 onChange={value => {
250 const parsed = parseInt(value || "", 10);
251 if (isNaN(parsed) || parsed < 1) {
252 return;
253 }
254
255 applyBreakpointUpdates(activeDevice, {
256 maxWidth: parsed,
257 });
258 }}
259 disabled={controlsDisabled}
260 help={__(
261 "The columns will be stacked when browser window width is less than this width",
262 "tableberg"
263 )}
264 />
265
266 <SelectControl
267 label={__("Responsive Mode", "tableberg")}
268 value={activeBreakpoint.mode}
269 options={MODE_OPTIONS}
270 onChange={(mode: string) => {
271 applyBreakpointUpdates(activeDevice, {
272 mode: mode as ResponsiveMode,
273 });
274 }}
275 disabled={controlsDisabled}
276 help={
277 activeBreakpoint.mode === "scroll"
278 ? __(
279 "Makes the table horizontally scrollable",
280 "tableberg"
281 )
282 : activeBreakpoint.mode === "stack"
283 ? __(
284 "Breaks the table by columns and stacks the columns",
285 "tableberg"
286 )
287 : undefined
288 }
289 />
290
291 {activeBreakpoint.mode === "stack" && (
292 <>
293 <ToggleControl
294 label={__(
295 "Transform Rows to Columns",
296 "tableberg"
297 )}
298 checked={activeBreakpoint.transpose}
299 onChange={transpose => {
300 applyBreakpointUpdates(
301 activeDevice,
302 {
303 transpose,
304 }
305 );
306 }}
307 disabled={controlsDisabled}
308 />
309
310 {isPro ? (
311 <ToggleControl
312 label={__(
313 "Show First Column in Every Stack Row",
314 "tableberg"
315 )}
316 checked={
317 activeBreakpoint.repeatFirstCol
318 }
319 onChange={repeatFirstCol => {
320 applyBreakpointUpdates(
321 activeDevice,
322 {
323 repeatFirstCol,
324 }
325 );
326 }}
327 disabled={controlsDisabled}
328 />
329 ) : (
330 <LockedControl
331 isEnhanced
332 selected="sticky-first-col"
333 >
334 <ToggleControl
335 label={__(
336 "Show First Column in Every Stack Row",
337 "tableberg"
338 )}
339 checked={false}
340 onChange={() => null}
341 disabled={controlsDisabled}
342 />
343 </LockedControl>
344 )}
345
346 <NumberControl
347 label={__(
348 "Items Per Stack Row",
349 "tableberg"
350 )}
351 value={activeBreakpoint.stackCount}
352 min={1}
353 spinControls="none"
354 help={__(
355 "Maximum number of cells allowed in each stacked sub-row.",
356 "tableberg"
357 )}
358 onChange={value => {
359 const parsed = parseInt(
360 value || "",
361 10
362 );
363 if (isNaN(parsed) || parsed < 1) {
364 return;
365 }
366
367 applyBreakpointUpdates(
368 activeDevice,
369 {
370 stackCount: parsed,
371 }
372 );
373 }}
374 disabled={controlsDisabled}
375 />
376 </>
377 )}
378 </>
379 )}
380 </BaseControl>
381 </PanelBody>
382 </InspectorControls>
383 );
384 }
385