Начал переводить на consta UI

This commit is contained in:
NikitaKalitov
2026-07-20 16:42:34 +03:00
parent d823b3df47
commit 52e28e8dc6
10 changed files with 687 additions and 257 deletions
+391 -4
View File
@@ -8,6 +8,8 @@
"name": "portal-chart-builder", "name": "portal-chart-builder",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@consta/icons": "^1.7.1",
"@consta/uikit": "^5.33.2",
"@xyflow/react": "^12.11.2", "@xyflow/react": "^12.11.2",
"react": "^19.2.7", "react": "^19.2.7",
"react-dom": "^19.2.7" "react-dom": "^19.2.7"
@@ -22,6 +24,71 @@
"vite": "^8.1.1" "vite": "^8.1.1"
} }
}, },
"node_modules/@babel/runtime": {
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz",
"integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/runtime-corejs3": {
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.29.7.tgz",
"integrity": "sha512-ppj9ouYku+RX0ljtgZd+KMO5mkM2bCqg8H2PYAFWnLsHEIKIdRojqbJ2i3eVHrisuxy7nOFCmngTDdWtUCdXUQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"core-js-pure": "^3.48.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@bem-react/classname": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@bem-react/classname/-/classname-1.8.0.tgz",
"integrity": "sha512-uazGRFKTMNbGR8KLvEh5qT+aP0yuym+sB+YFFaAyCkFodtjI0SskV6BfkiZ9+E3FloIFYPkwqJ1owxp48WCS9g==",
"license": "MPL-2.0",
"peer": true
},
"node_modules/@bem-react/classnames": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@bem-react/classnames/-/classnames-1.5.0.tgz",
"integrity": "sha512-kHa04vgeU0pJbqE/ZDzOn0bRkkVdbo0ZuV2h71h8PtopfDDGzfrgPIn+L6ohW3uF4cbdWUE49sQ0nyVT+O555g==",
"license": "MPL-2.0",
"peer": true
},
"node_modules/@consta/icons": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/@consta/icons/-/icons-1.7.1.tgz",
"integrity": "sha512-ZFngpjo9MlVyNnRU0SJqg2kbzHmh41ydqCYLOWokvhPjlgqUS7W+R38+dHSpIMyMe33wKBvF8LciSqhGwmuP9w==",
"peerDependencies": {
"@consta/uikit": "^5.0.0"
}
},
"node_modules/@consta/uikit": {
"version": "5.33.2",
"resolved": "https://registry.npmjs.org/@consta/uikit/-/uikit-5.33.2.tgz",
"integrity": "sha512-akF8UkXkJuIg3RKEzorGOZ9/aIPC9v8t3ZDSC+jFbHobP4h0AhGF8NgltB4BW1aXBLkiVN3ee8uGid1edwGcMA==",
"peerDependencies": {
"@bem-react/classname": "^1.6.0",
"@bem-react/classnames": "^1.3.10",
"@consta/icons": "^1.7.1",
"@reatom/core": "^3.10.1",
"@reatom/npm-react": "^3.10.6",
"compute-scroll-into-view": "^3.1.1",
"date-fns": "^4.1.0",
"react": ">= 16.8.0",
"react-dom": ">= 16.8.0",
"react-dropzone": "^14.2.3",
"react-imask": "^7.2.1",
"react-textarea-autosize": "^8.5.3",
"react-transition-group": "^4.4.5"
}
},
"node_modules/@emnapi/core": { "node_modules/@emnapi/core": {
"version": "1.11.1", "version": "1.11.1",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz",
@@ -408,6 +475,85 @@
"node": "^20.19.0 || >=22.12.0" "node": "^20.19.0 || >=22.12.0"
} }
}, },
"node_modules/@reatom/core": {
"version": "3.10.3",
"resolved": "https://registry.npmjs.org/@reatom/core/-/core-3.10.3.tgz",
"integrity": "sha512-uctBfmL/ZNvHWRX92Y+VQZON37fXNja4FM1fzevX7RYOWy6R7evx42VM6K4kuOaIci7bVWPhPZzHWeAu0J2lHQ==",
"license": "MIT",
"peer": true
},
"node_modules/@reatom/effects": {
"version": "3.11.3",
"resolved": "https://registry.npmjs.org/@reatom/effects/-/effects-3.11.3.tgz",
"integrity": "sha512-0qxr7m6e+GrOvt0pESONl4aRZxGjsU1HWXIsDR2Ghw0mNGjuStnEDUZnO+MVbKOArMIAvZ8ZoMrQWqXEBfOrVg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@reatom/core": "^3.2.0",
"@reatom/utils": "^3.5.0"
}
},
"node_modules/@reatom/hooks": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/@reatom/hooks/-/hooks-3.6.1.tgz",
"integrity": "sha512-1q8qXAOkQlDKc/Y94alPHWqMnXvJhCG4Rr9hQxPMPG1Qf3WpeKm7Zdxs4v3DC2Kcw6oG6djVk3i5duIjPygGWA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@reatom/core": "^3.2.0",
"@reatom/effects": "^3.7.0",
"@reatom/utils": "^3.3.0"
}
},
"node_modules/@reatom/lens": {
"version": "3.12.0",
"resolved": "https://registry.npmjs.org/@reatom/lens/-/lens-3.12.0.tgz",
"integrity": "sha512-YsEnYYHi58ePDscXomnbDPC9NSggfJQHvhcpqEAmvZuHH4nbsUpoQj2W69nSQyUDH/+X+TxURvsGUOHaXMl9pQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@reatom/core": "^3.4.0",
"@reatom/effects": "^3.2.0",
"@reatom/hooks": "^3.3.1",
"@reatom/primitives": "^3.6.0",
"@reatom/utils": "^3.1.0"
}
},
"node_modules/@reatom/npm-react": {
"version": "3.10.6",
"resolved": "https://registry.npmjs.org/@reatom/npm-react/-/npm-react-3.10.6.tgz",
"integrity": "sha512-lyoJD+pF2/P6B5bzSYjUQCzUSa0zkUQNAm1Lj8VXmVbILuiWid+n/9o/fJa8eEyH+w6KaFRunz1WZcOg/NW91g==",
"license": "MIT",
"peer": true,
"dependencies": {
"@reatom/core": "^3.5.0",
"@reatom/effects": "^3.7.3",
"@reatom/lens": "^3.1.0",
"@reatom/utils": "^3.9.0",
"use-sync-external-store": "^1.2.0"
},
"peerDependencies": {
"react": ">=16.8.0"
}
},
"node_modules/@reatom/primitives": {
"version": "3.11.0",
"resolved": "https://registry.npmjs.org/@reatom/primitives/-/primitives-3.11.0.tgz",
"integrity": "sha512-b+jtK7qpQxSP83mYQXpPRMlFyg+C9WPv4sZDWSmm00mwmwetW0KbltftkWiwQjQM1TLwpDLZ6R7DX7fcTraIgg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@reatom/core": "^3.1.1",
"@reatom/utils": "^3.1.1"
}
},
"node_modules/@reatom/utils": {
"version": "3.11.3",
"resolved": "https://registry.npmjs.org/@reatom/utils/-/utils-3.11.3.tgz",
"integrity": "sha512-H2FQf9xra7Twf0PxS6L0DtuRRC79NfHRB3V/YhnhPHyUE/UFscXrin/I2eGj3FEKcgOfC2BWnzCOrkXjKWgECQ==",
"license": "MIT",
"peer": true
},
"node_modules/@rolldown/binding-android-arm64": { "node_modules/@rolldown/binding-android-arm64": {
"version": "1.1.5", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz",
@@ -830,17 +976,45 @@
"d3-zoom": "^3.0.0" "d3-zoom": "^3.0.0"
} }
}, },
"node_modules/attr-accept": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz",
"integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=4"
}
},
"node_modules/classcat": { "node_modules/classcat": {
"version": "5.0.5", "version": "5.0.5",
"resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz",
"integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/compute-scroll-into-view": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz",
"integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==",
"license": "MIT",
"peer": true
},
"node_modules/core-js-pure": {
"version": "3.49.0",
"resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.49.0.tgz",
"integrity": "sha512-XM4RFka59xATyJv/cS3O3Kml72hQXUeGRuuTmMYFxwzc9/7C8OYTaIR/Ji+Yt8DXzsFLNhat15cE/JP15HrCgw==",
"hasInstallScript": true,
"license": "MIT",
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
}
},
"node_modules/csstype": { "node_modules/csstype": {
"version": "3.2.3", "version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/d3-color": { "node_modules/d3-color": {
@@ -948,6 +1122,17 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/date-fns": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.4.0.tgz",
"integrity": "sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==",
"license": "MIT",
"peer": true,
"funding": {
"type": "github",
"url": "https://github.com/sponsors/kossnocorp"
}
},
"node_modules/detect-libc": { "node_modules/detect-libc": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
@@ -958,6 +1143,17 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/dom-helpers": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.8.7",
"csstype": "^3.0.2"
}
},
"node_modules/fdir": { "node_modules/fdir": {
"version": "6.5.0", "version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
@@ -976,6 +1172,19 @@
} }
} }
}, },
"node_modules/file-selector": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-2.1.2.tgz",
"integrity": "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==",
"license": "MIT",
"peer": true,
"dependencies": {
"tslib": "^2.7.0"
},
"engines": {
"node": ">= 12"
}
},
"node_modules/fsevents": { "node_modules/fsevents": {
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -991,6 +1200,26 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0" "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
} }
}, },
"node_modules/imask": {
"version": "7.6.1",
"resolved": "https://registry.npmjs.org/imask/-/imask-7.6.1.tgz",
"integrity": "sha512-sJlIFM7eathUEMChTh9Mrfw/IgiWgJqBKq2VNbyXvBZ7ev/IlO6/KQTKlV/Fm+viQMLrFLG/zCuudrLIwgK2dg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime-corejs3": "^7.24.4"
},
"engines": {
"npm": ">=4.0.0"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"license": "MIT",
"peer": true
},
"node_modules/lightningcss": { "node_modules/lightningcss": {
"version": "1.32.0", "version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
@@ -1252,6 +1481,19 @@
"url": "https://opencollective.com/parcel" "url": "https://opencollective.com/parcel"
} }
}, },
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.16", "version": "3.3.16",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
@@ -1271,6 +1513,16 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
} }
}, },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/oxlint": { "node_modules/oxlint": {
"version": "1.73.0", "version": "1.73.0",
"resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.73.0.tgz", "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.73.0.tgz",
@@ -1369,6 +1621,18 @@
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
} }
}, },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.13.1"
}
},
"node_modules/react": { "node_modules/react": {
"version": "19.2.7", "version": "19.2.7",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz",
@@ -1390,6 +1654,83 @@
"react": "^19.2.7" "react": "^19.2.7"
} }
}, },
"node_modules/react-dropzone": {
"version": "14.4.1",
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.4.1.tgz",
"integrity": "sha512-QDuV76v3uKbHiH34SpwifZ+gOLi1+RdsCO1kl5vxMT4wW8R82+sthjvBw4th3NHF/XX6FBsqDYZVNN+pnhaw0g==",
"license": "MIT",
"peer": true,
"dependencies": {
"attr-accept": "^2.2.4",
"file-selector": "^2.1.0",
"prop-types": "^15.8.1"
},
"engines": {
"node": ">= 10.13"
},
"peerDependencies": {
"react": ">= 16.8 || 18.0.0"
}
},
"node_modules/react-imask": {
"version": "7.6.1",
"resolved": "https://registry.npmjs.org/react-imask/-/react-imask-7.6.1.tgz",
"integrity": "sha512-vLNfzcCz62Yzx/GRGh5tiCph9Gbh2cZu+Tz8OiO5it2eNuuhpA0DWhhSlOtVtSJ80+Bx+vFK5De8eQ9AmbkXzA==",
"license": "MIT",
"peer": true,
"dependencies": {
"imask": "^7.6.1",
"prop-types": "^15.8.1"
},
"engines": {
"npm": ">=4.0.0"
},
"peerDependencies": {
"react": ">=0.14.0"
}
},
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"license": "MIT",
"peer": true
},
"node_modules/react-textarea-autosize": {
"version": "8.5.9",
"resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.9.tgz",
"integrity": "sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.20.13",
"use-composed-ref": "^1.3.0",
"use-latest": "^1.2.1"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
"license": "BSD-3-Clause",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2"
},
"peerDependencies": {
"react": ">=16.6.0",
"react-dom": ">=16.6.0"
}
},
"node_modules/rolldown": { "node_modules/rolldown": {
"version": "1.1.5", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz",
@@ -1461,9 +1802,7 @@
"version": "2.8.1", "version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true, "license": "0BSD"
"license": "0BSD",
"optional": true
}, },
"node_modules/typescript": { "node_modules/typescript": {
"version": "6.0.3", "version": "6.0.3",
@@ -1486,6 +1825,54 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/use-composed-ref": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.4.0.tgz",
"integrity": "sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==",
"license": "MIT",
"peer": true,
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/use-isomorphic-layout-effect": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz",
"integrity": "sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==",
"license": "MIT",
"peer": true,
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/use-latest": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.3.0.tgz",
"integrity": "sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"use-isomorphic-layout-effect": "^1.1.1"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/use-sync-external-store": { "node_modules/use-sync-external-store": {
"version": "1.6.0", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+2
View File
@@ -10,6 +10,8 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@consta/icons": "^1.7.1",
"@consta/uikit": "^5.33.2",
"@xyflow/react": "^12.11.2", "@xyflow/react": "^12.11.2",
"react": "^19.2.7", "react": "^19.2.7",
"react-dom": "^19.2.7" "react-dom": "^19.2.7"
+109
View File
@@ -0,0 +1,109 @@
/*
Извлечено со скриншотов брендбука/стайлгайда.
Угол градиента в источнике указан как «60%» — трактован как 60deg
(стандартная запись угла в CSS linear-gradient). Проверьте по оригиналу,
если это принципиально.
*/
:root {
/* ============================================================
ОСНОВНЫЕ ГРАДИЕНТЫ (синие) — для инфоблоков, диаграмм, блок-схем
============================================================ */
--gradient-navy-start: rgb(0, 75, 158);
--gradient-navy-end: rgb(0, 164, 230);
--gradient-navy: linear-gradient(60deg, var(--gradient-navy-start), var(--gradient-navy-end));
--gradient-azure-start: rgb(36, 139, 224);
--gradient-azure-end: rgb(65, 205, 255);
--gradient-azure: linear-gradient(60deg, var(--gradient-azure-start), var(--gradient-azure-end));
--gradient-sky-start: rgb(94, 179, 236);
--gradient-sky-end: rgb(173, 214, 247);
--gradient-sky: linear-gradient(60deg, var(--gradient-sky-start), var(--gradient-sky-end));
--gradient-teal-dark-start: rgb(60, 129, 160);
--gradient-teal-dark-end: rgb(151, 195, 219);
--gradient-teal-dark: linear-gradient(60deg, var(--gradient-teal-dark-start), var(--gradient-teal-dark-end));
--gradient-teal-light-start: rgb(102, 190, 218);
--gradient-teal-light-end: rgb(161, 226, 233);
--gradient-teal-light: linear-gradient(60deg, var(--gradient-teal-light-start), var(--gradient-teal-light-end));
--gradient-navy-dark-start: rgb(0, 33, 92);
--gradient-navy-dark-end: rgb(0, 101, 170);
--gradient-navy-dark: linear-gradient(60deg, var(--gradient-navy-dark-start), var(--gradient-navy-dark-end));
--gradient-ice-start: rgb(206, 234, 255);
--gradient-ice-end: rgb(241, 249, 255);
--gradient-ice: linear-gradient(60deg, var(--gradient-ice-start), var(--gradient-ice-end));
--gradient-frost-start: rgb(215, 229, 237);
--gradient-frost-end: rgb(243, 245, 247);
--gradient-frost: linear-gradient(60deg, var(--gradient-frost-start), var(--gradient-frost-end));
/* ============================================================
ОСНОВНАЯ ПАЛИТРА — для диаграмм, блок-схем, смысловых выделений
============================================================ */
--gradient-purple-start: rgb(161, 44, 126);
--gradient-purple-end: rgb(191, 51, 168);
--gradient-purple: linear-gradient(60deg, var(--gradient-purple-start), var(--gradient-purple-end));
--gradient-red-start: rgb(225, 48, 66);
--gradient-red-end: rgb(241, 103, 117);
--gradient-red: linear-gradient(60deg, var(--gradient-red-start), var(--gradient-red-end));
--gradient-orange-start: rgb(255, 142, 88);
--gradient-orange-end: rgb(254, 180, 106);
--gradient-orange: linear-gradient(60deg, var(--gradient-orange-start), var(--gradient-orange-end));
--gradient-olive-start: rgb(187, 189, 57);
--gradient-olive-end: rgb(197, 231, 103);
--gradient-olive: linear-gradient(60deg, var(--gradient-olive-start), var(--gradient-olive-end));
--gradient-green-start: rgb(44, 181, 135);
--gradient-green-end: rgb(57, 224, 137);
--gradient-green: linear-gradient(60deg, var(--gradient-green-start), var(--gradient-green-end));
--gradient-green-dark-start: rgb(25, 111, 82);
--gradient-green-dark-end: rgb(38, 160, 119);
--gradient-green-dark: linear-gradient(60deg, var(--gradient-green-dark-start), var(--gradient-green-dark-end));
--gradient-blue-start: rgb(7, 144, 203);
--gradient-blue-end: rgb(17, 203, 217);
--gradient-blue: linear-gradient(60deg, var(--gradient-blue-start), var(--gradient-blue-end));
--gradient-cyan-start: rgb(27, 211, 247);
--gradient-cyan-end: rgb(101, 240, 238);
--gradient-cyan: linear-gradient(60deg, var(--gradient-cyan-start), var(--gradient-cyan-end));
/* ============================================================
ДОПОЛНИТЕЛЬНЫЕ ГРАДИЕНТЫ — для диаграмм, блок-схем, смысловых выделений
============================================================ */
--gradient-violet-start: rgb(138, 92, 231);
--gradient-violet-end: rgb(148, 57, 227);
--gradient-violet: linear-gradient(60deg, var(--gradient-violet-start), var(--gradient-violet-end));
--gradient-lavender-start: rgb(168, 143, 248);
--gradient-lavender-end: rgb(132, 159, 246);
--gradient-lavender: linear-gradient(60deg, var(--gradient-lavender-start), var(--gradient-lavender-end));
--gradient-pink-start: rgb(241, 141, 206);
--gradient-pink-end: rgb(222, 146, 232);
--gradient-pink: linear-gradient(60deg, var(--gradient-pink-start), var(--gradient-pink-end));
/* ============================================================
ДОПОЛНИТЕЛЬНЫЕ ЦВЕТА — для мелкой графики, линий (одиночные акценты)
Соответствуют по цветовому тону основной палитре и доп. градиентам
============================================================ */
--accent-purple: rgb(181, 48, 154);
--accent-red: rgb(236, 86, 101);
--accent-orange: rgb(255, 171, 102);
--accent-olive: rgb(194, 220, 91);
--accent-green: rgb(50, 202, 136);
--accent-green-dark: rgb(34, 146, 109);
--accent-blue: rgb(14, 186, 213);
--accent-cyan: rgb(78, 231, 244);
--accent-violet: rgb(145, 82, 230);
--accent-lavender: rgb(157, 154, 247);
--accent-pink: rgb(232, 143, 219);
}
+6 -1
View File
@@ -1,4 +1,5 @@
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { Theme, presetGpnDefault } from '@consta/uikit/Theme';
import { DiagramEditor } from './DiagramEditor'; import { DiagramEditor } from './DiagramEditor';
import { createDevAdapter } from './host/devAdapter'; import { createDevAdapter } from './host/devAdapter';
import { createBitrixAdapter } from './host/bitrixAdapter'; import { createBitrixAdapter } from './host/bitrixAdapter';
@@ -83,7 +84,11 @@ function App() {
return <p style={{ padding: 16, fontFamily: 'system-ui, sans-serif' }}>Загрузка диаграммы…</p>; return <p style={{ padding: 16, fontFamily: 'system-ui, sans-serif' }}>Загрузка диаграммы…</p>;
} }
return <DiagramEditor initialValue={doc} onChange={handleChange} />; return (
<Theme preset={presetGpnDefault} style={{ height: '100%' }}>
<DiagramEditor initialValue={doc} onChange={handleChange} />
</Theme>
);
} }
export default App; export default App;
+29 -24
View File
@@ -25,6 +25,9 @@ import {
type EdgeMouseHandler, type EdgeMouseHandler,
} from '@xyflow/react'; } from '@xyflow/react';
import '@xyflow/react/dist/style.css'; import '@xyflow/react/dist/style.css';
import { Button } from '@consta/uikit/Button';
import { ChoiceGroup } from '@consta/uikit/ChoiceGroup';
import { Breadcrumbs } from '@consta/uikit/Breadcrumbs';
import { nodeTypes, NodesWithChildrenContext, ModeContext } from './nodes'; import { nodeTypes, NodesWithChildrenContext, ModeContext } from './nodes';
import { NodeInspector } from './NodeInspector'; import { NodeInspector } from './NodeInspector';
import { EdgeInspector } from './EdgeInspector'; import { EdgeInspector } from './EdgeInspector';
@@ -52,6 +55,13 @@ const NODE_KINDS: { kind: NodeKind; label: string }[] = [
{ kind: 'inclusiveGateway', label: '+ Вкл. шлюз' }, { kind: 'inclusiveGateway', label: '+ Вкл. шлюз' },
]; ];
// Stable references — ChoiceGroup's controlled `value` is compared against
// entries in `items` by identity, so this can't be recreated on every render.
const MODE_ITEMS: { label: string; mode: 'edit' | 'view' }[] = [
{ label: 'Просмотр', mode: 'view' },
{ label: 'Редактирование', mode: 'edit' },
];
const DEFAULT_NODE_SIZE: Record<NodeKind, { width: number; height: number }> = { const DEFAULT_NODE_SIZE: Record<NodeKind, { width: number; height: number }> = {
task: { width: 140, height: 50 }, task: { width: 140, height: 50 },
startEvent: { width: 48, height: 48 }, startEvent: { width: 48, height: 48 },
@@ -634,38 +644,33 @@ function DiagramCanvas({ initialValue, onChange }: DiagramEditorProps) {
</Controls> </Controls>
{mode === 'edit' && <MiniMap />} {mode === 'edit' && <MiniMap />}
<Panel position="top-right" className="diagram-mode-toggle"> <Panel position="top-right" className="diagram-mode-toggle">
<button type="button" disabled={mode === 'view'} onClick={() => setMode('view')}> <ChoiceGroup
Просмотр size="xs"
</button> name="diagram-mode"
<button type="button" disabled={mode === 'edit'} onClick={() => setMode('edit')}> items={MODE_ITEMS}
Редактирование value={MODE_ITEMS.find((item) => item.mode === mode) ?? null}
</button> onChange={(item) => setMode(item.mode)}
/>
</Panel> </Panel>
{mode === 'edit' && ( {mode === 'edit' && (
<Panel position="top-left" className="diagram-toolbar"> <Panel position="top-left" className="diagram-toolbar">
{NODE_KINDS.map(({ kind, label }) => ( {NODE_KINDS.map(({ kind, label }) => (
<button key={kind} type="button" onClick={() => addNode(kind)}> <Button key={kind} size="xs" view="secondary" label={label} onClick={() => addNode(kind)} />
{label}
</button>
))} ))}
</Panel> </Panel>
)} )}
<Panel position="top-center" className="diagram-breadcrumbs"> <Panel position="top-center" className="diagram-breadcrumbs">
<button type="button" disabled={!focusNodeId} onClick={() => goToLevel(null)}> <Breadcrumbs
Корень size="xs"
</button> items={[
{breadcrumbPath.map((node, index) => ( { label: 'Корень', onClick: focusNodeId ? () => goToLevel(null) : undefined },
<span key={node.id}> ...breadcrumbPath.map((node, index) => ({
{' / '} label: (node.data as DiagramNodeData).label,
<button onClick: index === breadcrumbPath.length - 1 ? undefined : () => goToLevel(node.id),
type="button" })),
disabled={index === breadcrumbPath.length - 1} ]}
onClick={() => goToLevel(node.id)} getItemOnClick={(item) => item.onClick}
> />
{(node.data as DiagramNodeData).label}
</button>
</span>
))}
</Panel> </Panel>
</ReactFlow> </ReactFlow>
</ModeContext.Provider> </ModeContext.Provider>
+14 -15
View File
@@ -1,3 +1,7 @@
import { Button } from '@consta/uikit/Button';
import { TextField } from '@consta/uikit/TextField';
import { Text } from '@consta/uikit/Text';
interface EdgeInspectorProps { interface EdgeInspectorProps {
edgeId: string; edgeId: string;
label: string; label: string;
@@ -18,29 +22,24 @@ export function EdgeInspector({
return ( return (
<div className="node-inspector"> <div className="node-inspector">
<div className="node-inspector__header"> <div className="node-inspector__header">
<span className="node-inspector__type">Связь</span> <Text size="s" weight="semibold">
<code className="node-inspector__id">{edgeId}</code> Связь
</Text>
<Text size="xs" view="secondary" font="mono">
{edgeId}
</Text>
</div> </div>
<div className="node-inspector__toolbar"> <div className="node-inspector__toolbar">
<button type="button" onClick={onDelete}> <Button size="s" view="secondary" label="Удалить" onClick={onDelete} />
Удалить
</button>
</div> </div>
<div className="node-inspector__toolbar"> <div className="node-inspector__toolbar">
<button type="button" onClick={onBringToFront}> <Button size="s" view="secondary" label="На передний план" onClick={onBringToFront} />
На передний план <Button size="s" view="secondary" label="На задний план" onClick={onSendToBack} />
</button>
<button type="button" onClick={onSendToBack}>
На задний план
</button>
</div> </div>
<label className="node-inspector__field"> <TextField label="Подпись" size="s" value={label} onChange={(value) => onChange(value ?? '')} />
Подпись
<input type="text" value={label} onChange={(e) => onChange(e.target.value)} />
</label>
</div> </div>
); );
} }
+96 -52
View File
@@ -1,6 +1,28 @@
import { useRef, useState, type RefObject } from 'react';
import { Button } from '@consta/uikit/Button';
import { TextField } from '@consta/uikit/TextField';
import { ColorControl, ColorPicker, hexModel } from '@consta/uikit/ColorPicker';
import { Text } from '@consta/uikit/Text';
import { DEFAULT_NODE_COLOR } from './nodes'; import { DEFAULT_NODE_COLOR } from './nodes';
import type { DiagramNodeData, NodeKind } from './types'; import type { DiagramNodeData, NodeKind } from './types';
// "Дополнительные цвета" from palette.css — the section the palette itself
// marks as meant for single-color accents (as opposed to the gradients used
// for shape defaults, which a flat-hex picker can't represent).
const NODE_COLOR_PRESETS = [
'#B5309A',
'#EC5665',
'#FFAB66',
'#C2DC5B',
'#32CA88',
'#22926D',
'#0EBAD5',
'#4EE7F4',
'#9152E6',
'#9D9AF7',
'#E88FDB',
];
const TYPE_LABELS: Record<NodeKind, string> = { const TYPE_LABELS: Record<NodeKind, string> = {
task: 'Задача', task: 'Задача',
startEvent: 'Начальное событие', startEvent: 'Начальное событие',
@@ -38,17 +60,29 @@ export function NodeInspector({
onBringToFront, onBringToFront,
onSendToBack, onSendToBack,
}: NodeInspectorProps) { }: NodeInspectorProps) {
const [colorPickerOpen, setColorPickerOpen] = useState(false);
// Consta's ref props still use the pre-React-19 non-nullable RefObject shape.
const colorMarkerRef = useRef<HTMLButtonElement>(null) as RefObject<HTMLButtonElement>;
if (mode === 'view') { if (mode === 'view') {
const hasMeta = Boolean(data.responsible || data.duration); const hasMeta = Boolean(data.responsible || data.duration);
return ( return (
<div className="node-inspector"> <div className="node-inspector">
<div className="node-inspector__header"> <div className="node-inspector__header">
<span className="node-inspector__type">{TYPE_LABELS[nodeType]}</span> <Text size="s" weight="semibold">
{TYPE_LABELS[nodeType]}
</Text>
</div> </div>
<h3 className="node-inspector__label">{data.label}</h3> <Text size="l" className="node-inspector__label">
{data.label}
</Text>
{data.description && <p className="node-inspector__description">{data.description}</p>} {data.description && (
<Text size="s" view="secondary" className="node-inspector__description">
{data.description}
</Text>
)}
{hasMeta && ( {hasMeta && (
<dl className="node-inspector__meta"> <dl className="node-inspector__meta">
@@ -68,9 +102,12 @@ export function NodeInspector({
)} )}
{childCount > 0 && ( {childCount > 0 && (
<button type="button" className="node-inspector__action" onClick={() => onEnterSubProcess(nodeId)}> <Button
Открыть вложенный процесс → size="s"
</button> view="ghost"
label="Открыть вложенный процесс →"
onClick={() => onEnterSubProcess(nodeId)}
/>
)} )}
</div> </div>
); );
@@ -79,81 +116,88 @@ export function NodeInspector({
return ( return (
<div className="node-inspector"> <div className="node-inspector">
<div className="node-inspector__header"> <div className="node-inspector__header">
<span className="node-inspector__type">{TYPE_LABELS[nodeType]}</span> <Text size="s" weight="semibold">
<code className="node-inspector__id">{nodeId}</code> {TYPE_LABELS[nodeType]}
</Text>
<Text size="xs" view="secondary" font="mono">
{nodeId}
</Text>
</div> </div>
<div className="node-inspector__toolbar"> <div className="node-inspector__toolbar">
<button type="button" onClick={onDuplicate}> <Button size="s" view="secondary" label="Дублировать" onClick={onDuplicate} />
Дублировать <Button size="s" view="secondary" label="Удалить" onClick={onDelete} />
</button>
<button type="button" onClick={onDelete}>
Удалить
</button>
</div> </div>
<div className="node-inspector__toolbar"> <div className="node-inspector__toolbar">
<button type="button" onClick={onBringToFront}> <Button size="s" view="secondary" label="На передний план" onClick={onBringToFront} />
На передний план <Button size="s" view="secondary" label="На задний план" onClick={onSendToBack} />
</button>
<button type="button" onClick={onSendToBack}>
На задний план
</button>
</div> </div>
<label className="node-inspector__field node-inspector__field--color"> <div className="node-inspector__field">
<Text size="xs" view="secondary">
Цвет фона Цвет фона
<input </Text>
type="color" <ColorControl
markerRef={colorMarkerRef}
model={hexModel}
value={data.color ?? DEFAULT_NODE_COLOR[nodeType]} value={data.color ?? DEFAULT_NODE_COLOR[nodeType]}
onChange={(e) => onChange({ color: e.target.value })} onChange={(value) => onChange({ color: value })}
size="s"
onClick={() => setColorPickerOpen(true)}
/> />
</label> {colorPickerOpen && (
<ColorPicker
model={hexModel}
value={data.color ?? DEFAULT_NODE_COLOR[nodeType]}
onChange={(value) => onChange({ color: value })}
anchorRef={colorMarkerRef}
open={colorPickerOpen}
onOpen={setColorPickerOpen}
palette={NODE_COLOR_PRESETS}
paletteTitle="Пресеты"
/>
)}
</div>
<label className="node-inspector__field"> <TextField
Название label="Название"
<textarea type="textarea"
rows={2} rows={2}
size="s"
value={data.label} value={data.label}
onChange={(e) => onChange({ label: e.target.value })} onChange={(value) => onChange({ label: value ?? '' })}
/> />
</label>
<label className="node-inspector__field"> <TextField
Описание label="Описание"
<textarea type="textarea"
rows={3} rows={3}
size="s"
value={data.description ?? ''} value={data.description ?? ''}
onChange={(e) => onChange({ description: e.target.value })} onChange={(value) => onChange({ description: value ?? '' })}
/> />
</label>
<label className="node-inspector__field"> <TextField
Ответственный label="Ответственный"
<input size="s"
type="text"
value={data.responsible ?? ''} value={data.responsible ?? ''}
onChange={(e) => onChange({ responsible: e.target.value })} onChange={(value) => onChange({ responsible: value ?? '' })}
/> />
</label>
<label className="node-inspector__field"> <TextField
Длительность label="Длительность"
<input size="s"
type="text"
value={data.duration ?? ''} value={data.duration ?? ''}
onChange={(e) => onChange({ duration: e.target.value })} onChange={(value) => onChange({ duration: value ?? '' })}
/> />
</label>
<button type="button" className="node-inspector__action" onClick={() => onEnterSubProcess(nodeId)}> <Button size="s" view="ghost" label="Войти в под-процесс →" onClick={() => onEnterSubProcess(nodeId)} />
Войти в под-процесс → <Text size="xs" view="secondary" className="node-inspector__hint">
</button>
<p className="node-inspector__hint">
{childCount > 0 {childCount > 0
? `Внутри уже ${childCount} узл(ов).` ? `Внутри уже ${childCount} узл(ов).`
: 'Пока пусто — зайдя внутрь, можно добавить узлы детального разбора этого шага.'} : 'Пока пусто — зайдя внутрь, можно добавить узлы детального разбора этого шага.'}
</p> </Text>
</div> </div>
); );
} }
+3 -140
View File
@@ -16,20 +16,6 @@
max-width: 280px; max-width: 280px;
} }
.diagram-toolbar button {
font: inherit;
padding: 6px 10px;
border: 1px solid #999;
border-radius: 4px;
background: canvas;
color: canvastext;
cursor: pointer;
}
.diagram-toolbar button:hover {
background: color-mix(in srgb, canvastext 8%, canvas);
}
/* /*
* opacity/pointer-events, not display:none — hiding via display collapses the * opacity/pointer-events, not display:none — hiding via display collapses the
* handle to 0x0, and xyflow computes edge paths from the handle's actual * handle to 0x0, and xyflow computes edge paths from the handle's actual
@@ -57,56 +43,6 @@
z-index: 1000; z-index: 1000;
} }
.diagram-mode-toggle {
display: flex;
border: 1px solid #999;
border-radius: 4px;
overflow: hidden;
}
.diagram-mode-toggle button {
font: inherit;
padding: 6px 10px;
border: none;
background: canvas;
color: canvastext;
cursor: pointer;
}
.diagram-mode-toggle button:disabled {
background: color-mix(in srgb, canvastext 12%, canvas);
font-weight: 600;
cursor: default;
}
.diagram-mode-toggle button:first-child {
border-right: 1px solid #999;
}
.diagram-breadcrumbs {
display: flex;
align-items: center;
gap: 4px;
font-size: 12px;
}
.diagram-breadcrumbs button {
font: inherit;
padding: 2px 6px;
border: none;
background: none;
color: inherit;
cursor: pointer;
text-decoration: underline;
}
.diagram-breadcrumbs button:disabled {
text-decoration: none;
cursor: default;
opacity: 0.9;
font-weight: 600;
}
.diagram-node { .diagram-node {
position: relative; position: relative;
width: 100%; width: 100%;
@@ -125,6 +61,7 @@
} }
.diagram-node__label { .diagram-node__label {
color: white;
white-space: pre-wrap; white-space: pre-wrap;
overflow-wrap: break-word; overflow-wrap: break-word;
} }
@@ -205,6 +142,7 @@
.diagram-node__icon { .diagram-node__icon {
position: relative; position: relative;
display: inline-block; display: inline-block;
color: white;
font-size: 18px; font-size: 18px;
font-weight: 700; font-weight: 700;
line-height: 1; line-height: 1;
@@ -257,89 +195,14 @@
gap: 8px; gap: 8px;
} }
.node-inspector__toolbar button {
font: inherit;
padding: 6px 10px;
border: 1px solid #999;
border-radius: 4px;
background: canvas;
color: canvastext;
cursor: pointer;
}
.node-inspector__toolbar button:hover {
background: color-mix(in srgb, canvastext 8%, canvas);
}
.node-inspector__type {
font-weight: 600;
}
.node-inspector__id {
font-size: 11px;
opacity: 0.6;
}
.node-inspector__field { .node-inspector__field {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 4px; gap: 4px;
font-size: 12px;
}
.node-inspector__field input,
.node-inspector__field textarea,
.node-inspector__field select {
font: inherit;
padding: 6px 8px;
border: 1px solid #999;
border-radius: 4px;
background: canvas;
color: canvastext;
}
.node-inspector__field--color {
flex-direction: row;
align-items: center;
gap: 8px;
}
.node-inspector__field--color input[type='color'] {
width: 36px;
height: 28px;
padding: 2px;
cursor: pointer;
}
.node-inspector__action {
font: inherit;
padding: 8px 10px;
border: 1px solid #999;
border-radius: 4px;
background: canvas;
color: canvastext;
cursor: pointer;
align-self: flex-start;
}
.node-inspector__action:hover {
background: color-mix(in srgb, canvastext 8%, canvas);
}
.node-inspector__hint {
font-size: 11px;
opacity: 0.7;
}
.node-inspector__label {
margin: 0;
font-size: 15px;
white-space: pre-wrap;
} }
.node-inspector__label,
.node-inspector__description { .node-inspector__description {
margin: 0;
font-size: 13px;
white-space: pre-wrap; white-space: pre-wrap;
} }
+1
View File
@@ -1,5 +1,6 @@
import { StrictMode } from 'react' import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client' import { createRoot } from 'react-dom/client'
import '../palette.css'
import './index.css' import './index.css'
import App from './App.tsx' import App from './App.tsx'
+25 -10
View File
@@ -10,14 +10,29 @@ export const NodesWithChildrenContext = createContext<Set<string>>(new Set());
/** Current editor mode — resize handles and connection points only show in 'edit'. */ /** Current editor mode — resize handles and connection points only show in 'edit'. */
export const ModeContext = createContext<'edit' | 'view'>('edit'); export const ModeContext = createContext<'edit' | 'view'>('edit');
// Gradients from palette.css's "ОСНОВНАЯ ПАЛИТРА" section — the one the
// palette itself labels as meant for diagrams/flowcharts/semantic highlights.
// This is what nodes actually render with until a user picks a custom color.
export const DEFAULT_NODE_BACKGROUND: Record<NodeKind, string> = {
task: 'var(--gradient-blue)',
startEvent: 'var(--gradient-green)',
intermediateEvent: 'var(--gradient-orange)',
endEvent: 'var(--gradient-red)',
exclusiveGateway: 'var(--gradient-purple)',
parallelGateway: 'var(--gradient-cyan)',
inclusiveGateway: 'var(--gradient-olive)',
};
// The color picker only understands flat hex, so it needs a solid fallback
// distinct from the gradient rendered on canvas — the gradient's start color.
export const DEFAULT_NODE_COLOR: Record<NodeKind, string> = { export const DEFAULT_NODE_COLOR: Record<NodeKind, string> = {
task: '#eef2ff', task: '#0790CB',
startEvent: '#f0fdf4', startEvent: '#2CB587',
intermediateEvent: '#fefce8', intermediateEvent: '#FF8E58',
endEvent: '#fef2f2', endEvent: '#E13042',
exclusiveGateway: '#fff7ed', exclusiveGateway: '#A12C7E',
parallelGateway: '#faf5ff', parallelGateway: '#1BD3F7',
inclusiveGateway: '#f0fdfa', inclusiveGateway: '#BBBD39',
}; };
const HANDLE_POSITIONS: { id: string; position: Position }[] = [ const HANDLE_POSITIONS: { id: string; position: Position }[] = [
@@ -76,7 +91,7 @@ function ShapeResizer({
function TaskNode({ id, data, selected }: NodeProps<ShapeNode>) { function TaskNode({ id, data, selected }: NodeProps<ShapeNode>) {
return ( return (
<div className="diagram-node diagram-node--task" style={{ background: data.color ?? DEFAULT_NODE_COLOR.task }}> <div className="diagram-node diagram-node--task" style={{ background: data.color ?? DEFAULT_NODE_BACKGROUND.task }}>
<ShapeResizer selected={selected} minWidth={70} minHeight={32} keepAspectRatio={false} /> <ShapeResizer selected={selected} minWidth={70} minHeight={32} keepAspectRatio={false} />
<ShapeHandles /> <ShapeHandles />
<SubProcessBadge id={id} /> <SubProcessBadge id={id} />
@@ -96,7 +111,7 @@ function EventNode({ id, data, type, selected }: NodeProps<ShapeNode>) {
return ( return (
<div <div
className={`diagram-node diagram-node--circle ${EVENT_BORDER_CLASS[kind]}`} className={`diagram-node diagram-node--circle ${EVENT_BORDER_CLASS[kind]}`}
style={{ background: data.color ?? DEFAULT_NODE_COLOR[kind] }} style={{ background: data.color ?? DEFAULT_NODE_BACKGROUND[kind] }}
> >
<ShapeResizer selected={selected} minWidth={24} minHeight={24} /> <ShapeResizer selected={selected} minWidth={24} minHeight={24} />
<ShapeHandles /> <ShapeHandles />
@@ -130,7 +145,7 @@ function GatewayNode({ id, data, type, selected }: NodeProps<ShapeNode>) {
of it, since clip-path would otherwise cut off anything of theirs of it, since clip-path would otherwise cut off anything of theirs
that renders outside the diamond outline. that renders outside the diamond outline.
*/} */}
<div className="diagram-node--diamond-bg" style={{ background: data.color ?? DEFAULT_NODE_COLOR[kind] }} /> <div className="diagram-node--diamond-bg" style={{ background: data.color ?? DEFAULT_NODE_BACKGROUND[kind] }} />
<ShapeHandles /> <ShapeHandles />
<SubProcessBadge id={id} /> <SubProcessBadge id={id} />
<span className="diagram-node__icon">{GATEWAY_ICON[kind]}</span> <span className="diagram-node__icon">{GATEWAY_ICON[kind]}</span>