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 CHANGED
@@ -1,5 +1,9 @@
1
1
  # React-Simple-Dock
2
2
 
3
+ [![PyPI - pret-simple-dock](https://img.shields.io/pypi/v/pret-simple-dock?style=flat-square&color=blue)](https://pypi.org/project/pret-simple-dock/)
4
+ [![npm - react-simple-dock](https://img.shields.io/npm/v/react-simple-dock?style=flat-square&color=blue)](https://www.npmjs.com/package/react-simple-dock)
5
+ [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/percevalw/react-simple-dock/playwright.yml?style=flat-square)](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
@@ -122,7 +122,7 @@ body[data-jp-theme-light] {
122
122
  left: 0;
123
123
  width: 100%;
124
124
  height: 100%;
125
- overflow: scroll;
125
+ overflow: auto;
126
126
  }
127
127
 
128
128
  /* TAB HEADER */
package/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
2
  import "./index.css";
3
- import { LayoutConfig, PanelProps } from "./types";
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?: LayoutConfig;
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(defaultConfig || {
394
- kind: "row",
395
- size: 1,
396
- children: children_array.map((c, i) => ({
397
- kind: "leaf",
398
- tabs: [c.props.name || (c.key !== null ? c.key.toString().slice(2) : `unnamed-${i}`)],
399
- tabIndex: 0,
400
- size: 100 / children_array.length,
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-simple-dock",
3
- "version": "0.1.4",
3
+ "version": "0.2.2",
4
4
  "main": "index.js",
5
5
  "description": "Simple dock component for React",
6
6
  "repository": "https://github.com/percevalw/react-simple-dock",
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
  *