react-simple-dock 0.1.4 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/index.css +1 -1
- package/index.d.ts +5 -3
- package/index.js +61 -11
- package/package.json +1 -1
- package/types.d.ts +17 -0
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# React-Simple-Dock
|
|
2
2
|
|
|
3
|
+
[](https://pypi.org/project/pret-simple-dock/)
|
|
4
|
+
[](https://www.npmjs.com/package/react-simple-dock)
|
|
5
|
+
[](https://github.com/percevalw/react-simple-dock/actions/workflows/playwright.yml)
|
|
6
|
+
|
|
3
7
|
A set of React components to create a dockable interface, allowing to arrange and resize tabs.
|
|
4
8
|
|
|
5
9
|
## Installation of the javascript package
|
package/index.css
CHANGED
package/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import "./index.css";
|
|
3
|
-
import {
|
|
3
|
+
import { PanelProps, DefaultLayoutConfig } from "./types";
|
|
4
4
|
/**
|
|
5
5
|
* A Panel component.
|
|
6
6
|
*
|
|
@@ -21,10 +21,12 @@ export declare const Panel: (props: PanelProps) => any;
|
|
|
21
21
|
* @param children The children `Panel` components to render within the layout.
|
|
22
22
|
* @param defaultConfig The default layout configuration to use.
|
|
23
23
|
* @param wrapDnd A boolean flag to enable or disable drag and drop support (default: true).
|
|
24
|
+
* @param collapseTabsOnMobile If true, auto-detect mobile devices and collapse the whole layout into a single tabbed panel.
|
|
24
25
|
* @returns A React element representing the complete panel layout.
|
|
25
26
|
*/
|
|
26
|
-
export declare function Layout({ children, defaultConfig, wrapDnd, }: {
|
|
27
|
+
export declare function Layout({ children, defaultConfig, wrapDnd, collapseTabsOnMobile, }: {
|
|
27
28
|
children: React.ReactElement<PanelProps>[] | React.ReactElement<PanelProps>;
|
|
28
|
-
defaultConfig?:
|
|
29
|
+
defaultConfig?: DefaultLayoutConfig;
|
|
29
30
|
wrapDnd?: boolean;
|
|
31
|
+
collapseTabsOnMobile?: boolean | string[];
|
|
30
32
|
}): import("react/jsx-runtime").JSX.Element;
|
package/index.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
3
|
+
import { DndProvider, useDrag, useDrop, useDragDropManager } from "react-dnd";
|
|
3
4
|
import "./index.css";
|
|
4
|
-
import { DndProvider, useDrag, useDragDropManager, useDrop } from "react-dnd";
|
|
5
5
|
import { filterPanels, movePanel } from "./utils";
|
|
6
6
|
import { HTML5Backend } from "react-dnd-html5-backend";
|
|
7
7
|
function useForceUpdate() {
|
|
8
8
|
const [value, setValue] = useState(0); // integer state
|
|
9
9
|
return () => setValue((value) => value + 1); // update state to force render
|
|
10
10
|
}
|
|
11
|
+
const isMobileDevice = () => {
|
|
12
|
+
if (typeof window === "undefined" || typeof navigator === "undefined")
|
|
13
|
+
return false;
|
|
14
|
+
return /Mobi|Android|iPhone|iPad|iPod|Windows Phone|IEMobile|Opera Mini/i.test(navigator.userAgent || "");
|
|
15
|
+
};
|
|
11
16
|
const getPanelElementMaxHeaderHeight = (config, panelElements) => {
|
|
12
17
|
// If we have a leaf, then the header height is the max of each tabs header height
|
|
13
18
|
if (config.kind === "leaf") {
|
|
@@ -22,6 +27,26 @@ const getPanelElementMaxHeaderHeight = (config, panelElements) => {
|
|
|
22
27
|
return config.children.reduce((a, b) => a + getPanelElementMaxHeaderHeight(b, panelElements), 0);
|
|
23
28
|
}
|
|
24
29
|
};
|
|
30
|
+
function normalizeConfig(config, siblingsCount, depth = 0, default_kind = depth % 2 === 0 ? "row" : "column") {
|
|
31
|
+
if (typeof config === "string") {
|
|
32
|
+
config = { tabs: [config] };
|
|
33
|
+
}
|
|
34
|
+
if (Array.isArray(config)) {
|
|
35
|
+
config = { children: config };
|
|
36
|
+
}
|
|
37
|
+
// Leaf panel if tabs provided
|
|
38
|
+
if (config.tabs) {
|
|
39
|
+
const leaf = config;
|
|
40
|
+
const tabIndex = typeof leaf.tabIndex === "number" ? leaf.tabIndex : 0;
|
|
41
|
+
const size = typeof leaf.size === "number" ? leaf.size : 100 / siblingsCount;
|
|
42
|
+
return { kind: "leaf", tabs: leaf.tabs, tabIndex, size, nesting: leaf.nesting };
|
|
43
|
+
}
|
|
44
|
+
const container = config;
|
|
45
|
+
const kind = container.kind || default_kind;
|
|
46
|
+
const size = typeof container.size === "number" ? container.size : 100 / siblingsCount;
|
|
47
|
+
const children = container.children.map((child) => normalizeConfig(child, container.children.length, depth + 1, kind === "row" ? "column" : "row"));
|
|
48
|
+
return { kind, children, size, nesting: container.nesting };
|
|
49
|
+
}
|
|
25
50
|
const TabHandle = ({ name, index, visible, onClick, children, }) => {
|
|
26
51
|
const getItem = () => ({
|
|
27
52
|
name,
|
|
@@ -162,6 +187,7 @@ const NestedPanel = React.memo(({ leaves, config, index, onResize, saveSizes, is
|
|
|
162
187
|
return (_jsxs("div", { className: `${config.kind} panel`, style: style, ref: panelRef, children: [config.kind === "leaf" ? (_jsx(TabHeader, { config: config, leaves: leaves, onClick: handleHeaderClick })) : null, _jsx("div", { className: "panel-content", ref: panelContentRef, style: makeStyle(), children: config.kind === "leaf" ? (config.tabIndex < config.tabs.length ? (_jsx("div", { children: leaves[config.tabs[config.tabIndex]].element }, config.tabs[config.tabIndex])) : (_jsx("div", { style: { width: "100%", height: "100%" } }))) : (config.children.map((c, i) => (_jsx(NestedPanel, { config: c, leaves: leaves, saveSizes: handleSaveSizes, index: i, onResize: handleResize, isLast: i === config.children.length - 1, direction: config.kind, panelElements: panelElements }, i)))) }), !isLast && direction === "column" && (_jsx("div", { className: "resize-border bottom", onMouseDown: (e) => handleMouseDown(e, "bottom") })), !isLast && direction === "row" && (_jsx("div", { className: "resize-border right", onMouseDown: (e) => handleMouseDown(e, "right") }))] }));
|
|
163
188
|
});
|
|
164
189
|
const Overlay = ({ panelElements, onDrop, rootConfig, }) => {
|
|
190
|
+
const [, set] = useState();
|
|
165
191
|
const closestRef = useRef(null);
|
|
166
192
|
const lastItem = useRef(null);
|
|
167
193
|
let lastPlaceholder = useRef(null);
|
|
@@ -378,9 +404,10 @@ export const Panel = (props) => {
|
|
|
378
404
|
* @param children The children `Panel` components to render within the layout.
|
|
379
405
|
* @param defaultConfig The default layout configuration to use.
|
|
380
406
|
* @param wrapDnd A boolean flag to enable or disable drag and drop support (default: true).
|
|
407
|
+
* @param collapseTabsOnMobile If true, auto-detect mobile devices and collapse the whole layout into a single tabbed panel.
|
|
381
408
|
* @returns A React element representing the complete panel layout.
|
|
382
409
|
*/
|
|
383
|
-
export function Layout({ children, defaultConfig, wrapDnd = true, }) {
|
|
410
|
+
export function Layout({ children, defaultConfig, wrapDnd = true, collapseTabsOnMobile = true, }) {
|
|
384
411
|
const children_array = React.Children.toArray(children);
|
|
385
412
|
const namedChildren = Object.fromEntries(children_array.map((c, i) => [
|
|
386
413
|
c.props.name || (c.key !== null ? c.key.toString().slice(2) : `unnamed-${i}`),
|
|
@@ -390,15 +417,38 @@ export function Layout({ children, defaultConfig, wrapDnd = true, }) {
|
|
|
390
417
|
},
|
|
391
418
|
]));
|
|
392
419
|
const panelElements = useRef(new Map());
|
|
393
|
-
const [rootConfig, setRootConfig] = useState(
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
420
|
+
const [rootConfig, setRootConfig] = useState(() => {
|
|
421
|
+
const base = defaultConfig
|
|
422
|
+
? normalizeConfig(defaultConfig, children_array.length)
|
|
423
|
+
: {
|
|
424
|
+
kind: "row",
|
|
425
|
+
size: 1,
|
|
426
|
+
children: children_array.map((c, i) => ({
|
|
427
|
+
kind: "leaf",
|
|
428
|
+
tabs: [c.props.name || (c.key !== null ? c.key.toString().slice(2) : `unnamed-${i}`)],
|
|
429
|
+
tabIndex: 0,
|
|
430
|
+
size: 100 / children_array.length,
|
|
431
|
+
})),
|
|
432
|
+
};
|
|
433
|
+
if (collapseTabsOnMobile && isMobileDevice()) {
|
|
434
|
+
// If collapseTabsOnMobile is a list, use the names in the list as the first tabs
|
|
435
|
+
// then complete with the rest of the named children
|
|
436
|
+
const actualTabs = Object.keys(namedChildren);
|
|
437
|
+
const tabs = [
|
|
438
|
+
...(Array.isArray(collapseTabsOnMobile)
|
|
439
|
+
? collapseTabsOnMobile.filter((name) => actualTabs.includes(name))
|
|
440
|
+
: []),
|
|
441
|
+
...actualTabs.filter((name) => !Array.isArray(collapseTabsOnMobile) ||
|
|
442
|
+
!collapseTabsOnMobile.includes(name)),
|
|
443
|
+
];
|
|
444
|
+
return {
|
|
445
|
+
kind: "leaf",
|
|
446
|
+
tabs: tabs,
|
|
447
|
+
tabIndex: 0,
|
|
448
|
+
size: 100,
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
return base;
|
|
402
452
|
});
|
|
403
453
|
let config = rootConfig;
|
|
404
454
|
if (rootConfig.kind !== "leaf" || rootConfig.tabs.length > 0) {
|
package/package.json
CHANGED
package/types.d.ts
CHANGED
|
@@ -28,6 +28,23 @@ export type ContainerLayoutConfig = {
|
|
|
28
28
|
* A union type that represents either a leaf panel (with tabs) or a container panel (row/column with children).
|
|
29
29
|
*/
|
|
30
30
|
export type LayoutConfig = LeafLayoutConfig | ContainerLayoutConfig;
|
|
31
|
+
/**
|
|
32
|
+
* User-provided config can omit kind, size, and tabIndex for inference
|
|
33
|
+
*/
|
|
34
|
+
export type DefaultLeafConfig = {
|
|
35
|
+
kind?: "leaf";
|
|
36
|
+
tabs: string[];
|
|
37
|
+
tabIndex?: number;
|
|
38
|
+
size?: number;
|
|
39
|
+
nesting?: number;
|
|
40
|
+
};
|
|
41
|
+
export type DefaultContainerConfig = {
|
|
42
|
+
kind?: "row" | "column";
|
|
43
|
+
children: DefaultLayoutConfig[];
|
|
44
|
+
size?: number;
|
|
45
|
+
nesting?: number;
|
|
46
|
+
};
|
|
47
|
+
export type DefaultLayoutConfig = string | DefaultLeafConfig | DefaultContainerConfig | DefaultLayoutConfig[];
|
|
31
48
|
/**
|
|
32
49
|
* Properties for a panel component.
|
|
33
50
|
*
|