diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..7e3649a
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,16 @@
+# http://editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
+
+[Makefile]
+indent_style = tab
diff --git a/.fatherrc.ts b/.fatherrc.ts
new file mode 100644
index 0000000..cc3a27b
--- /dev/null
+++ b/.fatherrc.ts
@@ -0,0 +1,4 @@
+export default {
+ esm: 'rollup',
+ cjs: 'rollup',
+};
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..82cff45
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,25 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/npm-debug.log*
+/yarn-error.log
+/yarn.lock
+/package-lock.json
+
+# production
+/dist
+/docs-dist
+
+# misc
+.DS_Store
+
+# umi
+.umi
+.umi-production
+.umi-test
+.env.local
+
+# ide
+/.vscode
+/.idea
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..ecb24d3
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,7 @@
+**/*.svg
+**/*.ejs
+**/*.html
+package.json
+.umi
+.umi-production
+.umi-test
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..94beb14
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,11 @@
+{
+ "singleQuote": true,
+ "trailingComma": "all",
+ "printWidth": 80,
+ "overrides": [
+ {
+ "files": ".prettierrc",
+ "options": { "parser": "json" }
+ }
+ ]
+}
diff --git a/.umirc.ts b/.umirc.ts
new file mode 100644
index 0000000..c2fe667
--- /dev/null
+++ b/.umirc.ts
@@ -0,0 +1,11 @@
+import { defineConfig } from 'dumi';
+
+export default defineConfig({
+ title: 'react-org-chart',
+ favicon: '/images/logo.svg',
+ logo: '/images/logo.svg',
+ outputPath: 'docs-dist',
+ base: '/react-org-chart',
+ publicPath: '/',
+ // more config: https://d.umijs.org/config
+});
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..e69de29
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..591684e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,61 @@
+# react-org-chart - 组织结构图
+
+## 使用
+
+```typescript
+import React from 'react';
+import OrgChart, { NodeDataType } from '@twp0217/react-org-chart';
+
+export default () => {
+ const data: NodeDataType = {
+ key: 0,
+ label: '科技有限公司',
+ children: [
+ {
+ key: 1,
+ label: '研发部',
+ children: [
+ { key: 11, label: '开发-前端' },
+ { key: 12, label: '开发-后端' },
+ { key: 13, label: 'UI设计' },
+ { key: 14, label: '产品经理' },
+ ],
+ },
+ {
+ key: 2,
+ label: '销售部',
+ children: [
+ { key: 21, label: '销售一部' },
+ { key: 22, label: '销售二部' },
+ ],
+ },
+ { key: 3, label: '财务部' },
+ { key: 4, label: '人事部' },
+ ],
+ };
+
+ return ;
+};
+```
+
+## API
+
+### NodeDataType
+
+| 名称 | 类型 | 默认值 | 说明 |
+| --------- | ------------------- | ------ | ---------- |
+| key | string \| number | - | key |
+| label | number | - | label |
+| children | NodeDataType[] | - | 子节点集合 |
+| className | string | - | 类名 |
+| style | React.CSSProperties | - | 样式 |
+
+### OrgChartProps
+
+| 名称 | 类型 | 默认值 | 说明 |
+| ---------- | --------------------------------------------------------------------- | ------ | -------------- |
+| data | NodeDataType | - | 数据 |
+| className | string | - | 类名 |
+| style | React.CSSProperties | - | 样式 |
+| renderNode | (node: NodeDataType, originNode: React.ReactNode) => React.ReactNode; | - | 自定义渲染节点 |
+| onClick | (node: NodeDataType) => void | - | 点击事件 |
diff --git a/docs/changelog.md b/docs/changelog.md
new file mode 100644
index 0000000..beb2edd
--- /dev/null
+++ b/docs/changelog.md
@@ -0,0 +1 @@
+
diff --git a/docs/demo/basic.md b/docs/demo/basic.md
new file mode 100644
index 0000000..94cc536
--- /dev/null
+++ b/docs/demo/basic.md
@@ -0,0 +1,3 @@
+## basic
+
+
diff --git a/docs/demo/event.md b/docs/demo/event.md
new file mode 100644
index 0000000..9715115
--- /dev/null
+++ b/docs/demo/event.md
@@ -0,0 +1,3 @@
+## event
+
+
diff --git a/docs/examples/basic.tsx b/docs/examples/basic.tsx
new file mode 100644
index 0000000..80f2e2d
--- /dev/null
+++ b/docs/examples/basic.tsx
@@ -0,0 +1,33 @@
+import React from 'react';
+import OrgChart, { NodeDataType } from '@twp0217/react-org-chart';
+
+export default () => {
+ const data: NodeDataType = {
+ key: 0,
+ label: '科技有限公司',
+ children: [
+ {
+ key: 1,
+ label: '研发部',
+ children: [
+ { key: 11, label: '开发-前端' },
+ { key: 12, label: '开发-后端' },
+ { key: 13, label: 'UI设计' },
+ { key: 14, label: '产品经理' },
+ ],
+ },
+ {
+ key: 2,
+ label: '销售部',
+ children: [
+ { key: 21, label: '销售一部' },
+ { key: 22, label: '销售二部' },
+ ],
+ },
+ { key: 3, label: '财务部' },
+ { key: 4, label: '人事部' },
+ ],
+ };
+
+ return ;
+};
diff --git a/docs/examples/data.json b/docs/examples/data.json
new file mode 100644
index 0000000..8ec8b3f
--- /dev/null
+++ b/docs/examples/data.json
@@ -0,0 +1,26 @@
+{
+ "key": 0,
+ "label": "科技有限公司",
+ "children": [
+ {
+ "key": 1,
+ "label": "研发部",
+ "children": [
+ { "key": 11, "label": "开发-前端" },
+ { "key": 12, "label": "开发-后端" },
+ { "key": 13, "label": "UI设计" },
+ { "key": 14, "label": "产品经理" }
+ ]
+ },
+ {
+ "key": 2,
+ "label": "销售部",
+ "children": [
+ { "key": 21, "label": "销售一部" },
+ { "key": 22, "label": "销售二部" }
+ ]
+ },
+ { "key": 3, "label": "财务部" },
+ { "key": 4, "label": "人事部" }
+ ]
+}
diff --git a/docs/examples/event.tsx b/docs/examples/event.tsx
new file mode 100644
index 0000000..5201035
--- /dev/null
+++ b/docs/examples/event.tsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import OrgChart, { NodeDataType } from '@twp0217/react-org-chart';
+
+export default () => {
+ const data = require('./data.json');
+
+ const onClick = (node: NodeDataType) => {
+ console.log('onClick', node);
+ };
+
+ return (
+
+
+
+ );
+};
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..f3eef61
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,5 @@
+---
+title: react-org-chart
+---
+
+
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..be882fb
--- /dev/null
+++ b/package.json
@@ -0,0 +1,55 @@
+{
+ "name": "@twp0217/react-org-chart",
+ "version": "1.0.0",
+ "scripts": {
+ "start": "dumi dev",
+ "docs:build": "dumi build",
+ "docs:deploy": "gh-pages -d docs-dist",
+ "build": "father-build",
+ "deploy": "npm run docs:build && npm run docs:deploy",
+ "release": "npm run build && npm publish --access public",
+ "prettier": "prettier --write \"**/*.{js,jsx,tsx,ts,less,md,json}\"",
+ "test": "umi-test",
+ "test:coverage": "umi-test --coverage",
+ "commit": "git-cz",
+ "version": "conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md"
+ },
+ "main": "dist/index.js",
+ "module": "dist/index.esm.js",
+ "typings": "dist/index.d.ts",
+ "gitHooks": {
+ "pre-commit": "lint-staged"
+ },
+ "lint-staged": {
+ "*.{js,jsx,less,md,json}": [
+ "prettier --write"
+ ],
+ "*.ts?(x)": [
+ "prettier --parser=typescript --write"
+ ]
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ },
+ "dependencies": {
+ "classnames": "^2.3.1"
+ },
+ "devDependencies": {
+ "@umijs/test": "^3.0.5",
+ "commitizen": "^4.2.4",
+ "conventional-changelog-cli": "^2.1.1",
+ "cz-conventional-changelog": "^3.3.0",
+ "dumi": "^1.0.16",
+ "father-build": "^1.17.2",
+ "gh-pages": "^3.0.0",
+ "lint-staged": "^10.0.7",
+ "prettier": "^2.2.1",
+ "yorkie": "^2.0.0"
+ },
+ "config": {
+ "commitizen": {
+ "path": "./node_modules/cz-conventional-changelog"
+ }
+ }
+}
diff --git a/public/images/logo.svg b/public/images/logo.svg
new file mode 100644
index 0000000..67c6eb3
--- /dev/null
+++ b/public/images/logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/OrgChart.module.less b/src/OrgChart.module.less
new file mode 100644
index 0000000..a7d5750
--- /dev/null
+++ b/src/OrgChart.module.less
@@ -0,0 +1,54 @@
+@node-color: #1890ff;
+@line-width: 1px;
+@line-color: @node-color;
+
+.orgChartContainer {
+ display: inline-block;
+
+ :global {
+ table {
+ border-collapse: separate;
+ border-spacing: 0;
+ line-height: 1.5715;
+
+ tr {
+ &.lines td {
+ height: 20px;
+
+ .vertical {
+ width: @line-width;
+ height: 100%;
+ background-color: @line-color;
+ display: inline-block;
+ }
+
+ &.left {
+ border-right: @line-width solid @line-color;
+ }
+
+ &.right {
+ border-left: @line-width solid transparent;
+ }
+
+ &.top {
+ border-top: @line-width solid @line-color;
+ }
+ }
+ }
+
+ td {
+ text-align: center;
+ padding: 0;
+ vertical-align: top;
+
+ .node {
+ display: inline-block;
+ border: 1px solid @node-color;
+ padding: 0 0.5rem;
+ margin: 0 5px;
+ cursor: pointer;
+ }
+ }
+ }
+ }
+}
diff --git a/src/OrgChart.tsx b/src/OrgChart.tsx
new file mode 100644
index 0000000..6ddac7f
--- /dev/null
+++ b/src/OrgChart.tsx
@@ -0,0 +1,20 @@
+import classNames from 'classnames';
+import React from 'react';
+import DefaultOrgChart from './components/DefaultOrgChart';
+import { OrgChartProps } from './interface';
+import styles from './OrgChart.module.less';
+
+const OrgChart = (props: OrgChartProps) => {
+ const { data, className, style, renderNode, onClick } = props;
+
+ return !!data ? (
+
+
+
+ ) : null;
+};
+
+export default OrgChart;
diff --git a/src/components/DefaultOrgChart.tsx b/src/components/DefaultOrgChart.tsx
new file mode 100644
index 0000000..ac6c042
--- /dev/null
+++ b/src/components/DefaultOrgChart.tsx
@@ -0,0 +1,114 @@
+import { NodeDataType, OrgChartComponentProps, RenderNode } from '../interface';
+import classNames from 'classnames';
+import React from 'react';
+
+const DefaultOrgChart = (props: OrgChartComponentProps) => {
+ const { data, renderNode: customRenderNode, onClick } = props;
+
+ const childrenLength = data.children?.length || 0;
+ const colSpan: number = childrenLength * 2;
+
+ /**
+ * 渲染节点
+ * @param data
+ * @returns
+ */
+ const renderNode = (data: NodeDataType): React.ReactNode => {
+ const contentNode: React.ReactNode = (
+
+ {data.label}
+
+ );
+ return (
+
+
+ onClick && onClick(data)}
+ >
+ {!!customRenderNode
+ ? customRenderNode(data, contentNode)
+ : contentNode}
+
+ |
+
+ );
+ };
+
+ /**
+ * 渲染垂直线
+ * @returns
+ */
+ const renderVerticalLine = (): React.ReactNode => {
+ return (
+
+
+ |
+ );
+ };
+
+ /**
+ * 渲染连接线
+ * @returns
+ */
+ const renderConnectLines = (): React.ReactNode[] => {
+ const lines: React.ReactNode[] = [];
+ for (let index = 0; index < colSpan; index++) {
+ lines.push(
+
+
+ | ,
+ );
+ }
+ return lines;
+ };
+
+ /**
+ * 渲染子节点
+ * @param datas
+ * @returns
+ */
+ const renderChildren = (datas: NodeDataType[] = []): React.ReactNode => {
+ if (datas.length > 0) {
+ return (
+ <>
+ {renderVerticalLine()}
+ {renderConnectLines()}
+
+ {datas.map((data) => {
+ return (
+
+
+ |
+ );
+ })}
+
+ >
+ );
+ }
+ return;
+ };
+
+ return (
+
+
+ {renderNode(data)}
+ {renderChildren(data.children)}
+
+
+ );
+};
+
+export default DefaultOrgChart;
diff --git a/src/index.ts b/src/index.ts
new file mode 100644
index 0000000..5c58e7b
--- /dev/null
+++ b/src/index.ts
@@ -0,0 +1,5 @@
+import { NodeDataType, OrgChartProps } from './interface';
+import OrgChart from './OrgChart';
+
+export { NodeDataType, OrgChartProps };
+export default OrgChart;
diff --git a/src/interface.ts b/src/interface.ts
new file mode 100644
index 0000000..6681111
--- /dev/null
+++ b/src/interface.ts
@@ -0,0 +1,23 @@
+export interface NodeDataType {
+ key: string | number;
+ label: string;
+ children?: NodeDataType[];
+ className?: string;
+ style?: React.CSSProperties;
+}
+
+export type RenderNode = (
+ node: NodeDataType,
+ originNode: React.ReactNode,
+) => React.ReactNode;
+
+export interface OrgChartComponentProps {
+ data: NodeDataType;
+ renderNode?: RenderNode;
+ onClick?: (node: NodeDataType) => void;
+}
+
+export interface OrgChartProps extends Partial {
+ className?: string;
+ style?: React.CSSProperties;
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..de15e0a
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,29 @@
+{
+ "compilerOptions": {
+ "target": "esnext",
+ "module": "esnext",
+ "moduleResolution": "node",
+ "importHelpers": true,
+ "jsx": "react",
+ "esModuleInterop": true,
+ "sourceMap": true,
+ "baseUrl": "./",
+ "strict": true,
+ "paths": {
+ "@/*": ["src/*"],
+ "@@/*": ["src/.umi/*"]
+ },
+ "allowSyntheticDefaultImports": true
+ },
+ "exclude": [
+ "node_modules",
+ "lib",
+ "es",
+ "dist",
+ "typings",
+ "**/__test__",
+ "test",
+ "docs",
+ "tests"
+ ]
+}
diff --git a/typings.d.ts b/typings.d.ts
new file mode 100644
index 0000000..71e0e9f
--- /dev/null
+++ b/typings.d.ts
@@ -0,0 +1,2 @@
+declare module '*.css';
+declare module '*.less';