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

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

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