Repository: CNFeffery/feffery-antd-components (language: python)

Issue:
[Feature] 新增分隔面板组件`AntdSplitter`



Discussion / hints:

Fix patch:
diff --git a/NAMESPACE b/NAMESPACE
index 98a6509b..3f964c3f 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -91,6 +91,7 @@ export('feffery'AntdHeader)
 export('feffery'AntdLayout)
 export('feffery'AntdSider)
 export('feffery'AntdSpace)
+export('feffery'AntdSplitter)
 export('feffery'AntdCol)
 export('feffery'AntdRow)
 export('feffery'AntdAnchor)
diff --git a/feffery_antd_components/AntdSplitter.py b/feffery_antd_components/AntdSplitter.py
new file mode 100644
index 00000000..cb437f3a
--- /dev/null
+++ b/feffery_antd_components/AntdSplitter.py
@@ -0,0 +1,119 @@
+# AUTO GENERATED FILE - DO NOT EDIT
+
+from dash.development.base_component import Component, _explicitize_args
+
+
+class AntdSplitter(Component):
+    """An AntdSplitter component.
+分隔面板组件AntdSplitter
+
+Keyword arguments:
+
+- id (string; optional):
+    组件唯一id.
+
+- key (string; optional):
+    对当前组件的`key`值进行更新，可实现强制重绘当前组件的效果.
+
+- style (dict; optional):
+    当前组件css样式.
+
+- className (string | dict; optional):
+    当前组件css类名，支持[动态css](/advanced-classname).
+
+- layout (a value equal to: 'horizontal', 'vertical'; default 'horizontal'):
+    布局方向，可选项有`'horizontal'`、`'vertical'`  默认值：`'horizontal'`.
+
+- items (list of dicts; required):
+    配置分隔面板子项，优先级高于`children`.
+
+    `items` is a list of dicts with keys:
+
+    - key (string; optional):
+        面板key.
+
+    - children (a list of or a singular dash component, string or number; optional):
+        组件型，内嵌元素.
+
+    - style (dict; optional):
+        当前组件css样式.
+
+    - className (string
+
+      Or dict; optional):
+        当前组件css类名，支持[动态css](/advanced-classname).
+
+    - defaultSize (number | string; optional):
+        初始面板大小，支持数字`px`或者文字`'百分比%'`类型.
+
+    - min (number | string; optional):
+        最小阈值，支持数字`px`或者文字`'百分比%'`类型.
+
+    - max (number | string; optional):
+        最大阈值，支持数字`px`或者文字`'百分比%'`类型.
+
+    - size (number | string; optional):
+        面板大小，支持数字`px`或者文字`'百分比%'`类型.
+
+    - collapsible (dict; optional):
+        是否可折叠  默认值：`False`.
+
+        `collapsible` is a boolean | dict with keys:
+
+        - start (boolean; optional)
+
+        - end (boolean; optional)
+
+    - resizable (boolean; optional):
+        是否开启拖拽伸缩  默认值：`True`.
+
+- currentSizes (list of dicts; optional):
+    监听当前面板大小变化信息.
+
+    `currentSizes` is a list of dicts with keys:
+
+    - key (string; optional):
+        面板key.
+
+    - size (number | string; optional):
+        面板大小.
+
+- data-* (string; optional):
+    `data-*`格式属性通配.
+
+- aria-* (string; optional):
+    `aria-*`格式属性通配.
+
+- loading_state (dict; optional)
+
+    `loading_state` is a dict with keys:
+
+    - is_loading (boolean; optional):
+        Determines if the component is loading or not.
+
+    - prop_name (string; optional):
+        Holds which property is loading.
+
+    - component_name (string; optional):
+        Holds the name of the component that is loading."""
+    _children_props = ['items[].children']
+    _base_nodes = ['children']
+    _namespace = 'feffery_antd_components'
+    _type = 'AntdSplitter'
+    @_explicitize_args
+    def __init__(self, id=Component.UNDEFINED, key=Component.UNDEFINED, style=Component.UNDEFINED, className=Component.UNDEFINED, layout=Component.UNDEFINED, items=Component.REQUIRED, currentSizes=Component.UNDEFINED, loading_state=Component.UNDEFINED, **kwargs):
+        self._prop_names = ['id', 'key', 'style', 'className', 'layout', 'items', 'currentSizes', 'data-*', 'aria-*', 'loading_state']
+        self._valid_wildcard_attributes =            ['data-', 'aria-']
+        self.available_properties = ['id', 'key', 'style', 'className', 'layout', 'items', 'currentSizes', 'data-*', 'aria-*', 'loading_state']
+        self.available_wildcard_properties =            ['data-', 'aria-']
+        _explicit_args = kwargs.pop('_explicit_args')
+        _locals = locals()
+        _locals.update(kwargs)  # For wildcard attrs and excess named props
+        args = {k: _locals[k] for k in _explicit_args}
+
+        for k in ['items']:
+            if k not in args:
+                raise TypeError(
+                    'Required argument `' + k + '` was not specified.')
+
+        super(AntdSplitter, self).__init__(**args)
diff --git a/feffery_antd_components/_imports_.py b/feffery_antd_components/_imports_.py
index 47c3e9e4..6c024c79 100644
--- a/feffery_antd_components/_imports_.py
+++ b/feffery_antd_components/_imports_.py
@@ -89,6 +89,7 @@
 from .AntdLayout import AntdLayout
 from .AntdSider import AntdSider
 from .AntdSpace import AntdSpace
+from .AntdSplitter import AntdSplitter
 from .AntdCol import AntdCol
 from .AntdRow import AntdRow
 from .AntdAnchor import AntdAnchor
@@ -200,6 +201,7 @@
     "AntdLayout",
     "AntdSider",
     "AntdSpace",
+    "AntdSplitter",
     "AntdCol",
     "AntdRow",
     "AntdAnchor",
diff --git a/src/lib/components/layout/AntdSplitter.react.js b/src/lib/components/layout/AntdSplitter.react.js
new file mode 100644
index 00000000..4ffe94a4
--- /dev/null
+++ b/src/lib/components/layout/AntdSplitter.react.js
@@ -0,0 +1,231 @@
+// react核心
+import React, { useEffect, useMemo, useState } from 'react';
+import PropTypes from 'prop-types';
+// antd核心
+import { Splitter } from 'antd';
+// 辅助库
+import { isString } from 'lodash';
+import { pickBy } from 'ramda';
+// 自定义hooks
+import useCss from '../../hooks/useCss';
+
+/**
+ * 分隔面板组件AntdSplitter
+ */
+const AntdSplitter = (props) => {
+    let {
+        id,
+        className,
+        style,
+        key,
+        layout,
+        items,
+        currentSizes,
+        setProps,
+        loading_state
+    } = props;
+
+    const [sizes, setSizes] = useState(items.map(item => item.size));
+
+    const _items = useMemo(() => {
+        return items.map((item, index) => sizes ? <Splitter.Panel {...item} size={sizes[index]} /> : <Splitter.Panel {...item} />)
+    }, [sizes])
+
+    useEffect(() => {
+        let currentSizes = _items.map(
+            (item, index) => { return { key: item.key, size: sizes[index] } }
+        )
+        setProps({ currentSizes: currentSizes })
+    }, [_items])
+
+    const onResize = (_sizes) => {
+        setSizes(_sizes);
+    }
+
+    return (
+        <Splitter
+            // 提取具有data-*或aria-*通配格式的属性
+            {...pickBy((_, k) => k.startsWith('data-') || k.startsWith('aria-'), props)}
+            id={id}
+            className={
+                isString(className) ?
+                    className :
+                    (className ? useCss(className) : undefined)
+            }
+            style={style}
+            key={key}
+            layout={layout}
+            onResize={onResize}
+            data-dash-is-loading={
+                (loading_state && loading_state.is_loading) || undefined
+            }>
+            {_items}
+        </Splitter>
+    );
+}
+
+AntdSplitter.propTypes = {
+    /**
+     * 组件唯一id
+     */
+    id: PropTypes.string,
+
+    /**
+     * 对当前组件的`key`值进行更新，可实现强制重绘当前组件的效果
+     */
+    key: PropTypes.string,
+
+    /**
+     * 当前组件css样式
+     */
+    style: PropTypes.object,
+
+    /**
+     * 当前组件css类名，支持[动态css](/advanced-classname)
+     */
+    className: PropTypes.oneOfType([
+        PropTypes.string,
+        PropTypes.object
+    ]),
+
+    /**
+     * 布局方向，可选项有`'horizontal'`、`'vertical'`
+     * 默认值：`'horizontal'`
+     */
+    layout: PropTypes.oneOf(['horizontal', 'vertical']),
+
+    /**
+     * 配置分隔面板子项，优先级高于`children`
+     */
+    items: PropTypes.arrayOf(
+        PropTypes.shape({
+            /**
+             * 面板key
+             */
+            key: PropTypes.string,
+            /**
+             * 组件型，内嵌元素
+             */
+            children: PropTypes.node,
+
+            /**
+             * 当前组件css样式
+             */
+            style: PropTypes.object,
+
+            /**
+             * 当前组件css类名，支持[动态css](/advanced-classname)
+             */
+            className: PropTypes.oneOfType([
+                PropTypes.string,
+                PropTypes.object
+            ]),
+
+            /**
+             * 初始面板大小，支持数字`px`或者文字`'百分比%'`类型
+             */
+            defaultSize: PropTypes.oneOfType([
+                PropTypes.number,
+                PropTypes.string
+            ]),
+
+            /**
+             * 最小阈值，支持数字`px`或者文字`'百分比%'`类型
+             */
+            min: PropTypes.oneOfType([
+                PropTypes.number,
+                PropTypes.string
+            ]),
+
+            /**
+             * 最大阈值，支持数字`px`或者文字`'百分比%'`类型
+             */
+            max: PropTypes.oneOfType([
+                PropTypes.number,
+                PropTypes.string
+            ]),
+
+            /**
+             * 面板大小，支持数字`px`或者文字`'百分比%'`类型
+             */
+            size: PropTypes.oneOfType([
+                PropTypes.number,
+                PropTypes.string
+            ]),
+
+            /**
+             * 是否可折叠
+             * 默认值：`false`
+             */
+            collapsible: PropTypes.oneOfType([
+                PropTypes.bool,
+                PropTypes.shape({
+                    start: PropTypes.bool,
+                    end: PropTypes.bool
+                })
+            ]),
+
+            /**
+             * 是否开启拖拽伸缩
+             * 默认值：`true`
+             */
+            resizable: PropTypes.bool,
+        })
+    ).isRequired,
+
+    /**
+     * 监听当前面板大小变化信息
+     */
+    currentSizes: PropTypes.arrayOf(PropTypes.exact({
+        /**
+         * 面板key
+         */
+        key: PropTypes.string,
+        /**
+         * 面板大小
+         */
+        size: PropTypes.oneOfType([
+            PropTypes.number,
+            PropTypes.string
+        ])
+    })),
+
+
+    /**
+     * `data-*`格式属性通配
+     */
+    'data-*': PropTypes.string,
+
+    /**
+     * `aria-*`格式属性通配
+     */
+    'aria-*': PropTypes.string,
+
+    loading_state: PropTypes.shape({
+        /**
+         * Determines if the component is loading or not
+         */
+        is_loading: PropTypes.bool,
+        /**
+         * Holds which property is loading
+         */
+        prop_name: PropTypes.string,
+        /**
+         * Holds the name of the component that is loading
+         */
+        component_name: PropTypes.string
+    }),
+
+    /**
+     * Dash-assigned callback that should be called to report property changes
+     * to Dash, to make them available for callbacks.
+     */
+    setProps: PropTypes.func
+};
+
+// 设置默认参数
+AntdSplitter.defaultProps = {
+    layout: 'horizontal',
+}
+
+export default AntdSplitter;
\ No newline at end of file
diff --git a/src/lib/index.js b/src/lib/index.js
index 10a2041d..8ec1b815 100644
--- a/src/lib/index.js
+++ b/src/lib/index.js
@@ -19,6 +19,7 @@ import AntdHeader from './components/layout/AntdHeader.react';
 import AntdContent from './components/layout/AntdContent.react';
 import AntdFooter from './components/layout/AntdFooter.react';
 import AntdSider from './components/layout/AntdSider.react';
+import AntdSplitter from './components/layout/AntdSplitter.react';
 // 导航
 import AntdAnchor from './components/navigation/AntdAnchor.react';
 import AntdBreadcrumb from './components/navigation/AntdBreadcrumb.react';
@@ -141,6 +142,7 @@ export {
     AntdContent,
     AntdFooter,
     AntdSider,
+    AntdSplitter,
     // 导航
     AntdAnchor,
     AntdBreadcrumb,


Test patch:
diff --git a/tests/layout/AntdSplitter/feat_AntdSplitter.py b/tests/layout/AntdSplitter/feat_AntdSplitter.py
new file mode 100644
index 00000000..938dced8
--- /dev/null
+++ b/tests/layout/AntdSplitter/feat_AntdSplitter.py
@@ -0,0 +1,210 @@
+if True:
+    import sys
+
+    sys.path.append('../../../')
+    import feffery_antd_components as fac
+    import json
+    from dash import Dash, html
+    from dash.dependencies import Input, Output
+    from feffery_dash_utils.style_utils import style
+
+app = Dash(__name__)
+
+
+def desc(text):
+    return fac.AntdFlex(
+        fac.AntdTitle(
+            text,
+            type='secondary',
+            level=5,
+            style=style(whiteSpace='nowrap'),
+        ),
+        justify='center',
+        align='center',
+        style=style(height='100%'),
+    )
+
+
+app.layout = html.Div(
+    [
+        fac.AntdDivider('基本用法'),
+        fac.AntdSpace(
+            [
+                fac.AntdSplitter(
+                    id='splitter-1',
+                    items=[
+                        {
+                            'children': desc('First'),
+                            'defaultSize': '40%',
+                            'min': '20%',
+                            'max': '70%',
+                        },
+                        {'children': desc('Second')},
+                    ],
+                    style=style(
+                        height=200,
+                        boxShadow='0 0 10px rgba(0, 0, 0, 0.1)',
+                    ),
+                ),
+                fac.AntdSplitter(
+                    id='splitter-2',
+                    items=[
+                        {
+                            'key': 'splitter1',
+                            'children': desc('First'),
+                            'size': '50%',
+                        },
+                        {
+                            'key': 'splitter2',
+                            'children': desc('Second'),
+                            'size': '50%',
+                        },
+                    ],
+                    style=style(
+                        height=200,
+                        boxShadow='0 0 10px rgba(0, 0, 0, 0.1)',
+                    ),
+                ),
+            ],
+            direction='vertical',
+            style=style(width='100%'),
+        ),
+        fac.AntdDivider('垂直方向'),
+        fac.AntdSplitter(
+            id='splitter-3',
+            layout='vertical',
+            items=[
+                {
+                    'key': 'splitter1',
+                    'children': desc('First'),
+                },
+                {
+                    'key': 'splitter2',
+                    'children': desc('Second'),
+                },
+            ],
+            style=style(
+                height=300,
+                boxShadow='0 0 10px rgba(0, 0, 0, 0.1)',
+            ),
+        ),
+        fac.AntdDivider('可折叠'),
+        fac.AntdSpace(
+            [
+                fac.AntdSplitter(
+                    id='splitter-4',
+                    items=[
+                        {
+                            'key': 'splitter1',
+                            'children': desc('First'),
+                            'collapsible': True,
+                            'min': '20%',
+                        },
+                        {
+                            'key': 'splitter2',
+                            'children': desc('Second'),
+                            'collapsible': True,
+                        },
+                    ],
+                    style=style(
+                        height=200,
+                        boxShadow='0 0 10px rgba(0, 0, 0, 0.1)',
+                    ),
+                ),
+                fac.AntdSplitter(
+                    id='splitter-5',
+                    layout='vertical',
+                    items=[
+                        {
+                            'key': 'splitter1',
+                            'children': desc('First'),
+                            'collapsible': True,
+                            'min': '20%',
+                        },
+                        {
+                            'key': 'splitter2',
+                            'children': desc('Second'),
+                            'collapsible': True,
+                        },
+                    ],
+                    style=style(
+                        height=300,
+                        boxShadow='0 0 10px rgba(0, 0, 0, 0.1)',
+                    ),
+                ),
+            ],
+            direction='vertical',
+            style=style(width='100%'),
+        ),
+        fac.AntdDivider('多面板'),
+        fac.AntdSplitter(
+            id='splitter-6',
+            items=[
+                {
+                    'key': 'splitter1',
+                    'children': desc('Panel 1'),
+                    'collapsible': True,
+                },
+                {
+                    'key': 'splitter2',
+                    'children': desc('Panel 2'),
+                    'collapsible': {'start': True},
+                },
+                {
+                    'key': 'splitter3',
+                    'children': desc('Panel 3'),
+                },
+            ],
+            style=style(
+                height=200,
+                boxShadow='0 0 10px rgba(0, 0, 0, 0.1)',
+            ),
+        ),
+        fac.AntdDivider('复杂组合'),
+        fac.AntdSplitter(
+            id='splitter-7',
+            items=[
+                {
+                    'key': 'splitter1',
+                    'children': desc('Left'),
+                    'collapsible': True,
+                },
+                {
+                    'key': 'splitter2',
+                    'children': fac.AntdSplitter(
+                        layout='vertical',
+                        items=[
+                            {
+                                'key': 'splitter3',
+                                'children': desc('Top'),
+                            },
+                            {
+                                'key': 'splitter4',
+                                'children': desc('Bottom'),
+                            },
+                        ],
+                    ),
+                },
+            ],
+            style=style(
+                height=200,
+                boxShadow='0 0 10px rgba(0, 0, 0, 0.1)',
+            ),
+        ),
+        html.Pre(id='splitter-2-output'),
+    ]
+)
+
+
+@app.callback(
+    Output('splitter-2-output', 'children'),
+    Input('splitter-2', 'currentSizes'),
+)
+def update_output(currentSizes):
+    return json.dumps(
+        currentSizes, indent=4, ensure_ascii=False
+    )
+
+
+if __name__ == '__main__':
+    app.run(debug=True)