complete text and group,position duplcate feature in topbar
This commit is contained in:
parent
79ca662a08
commit
8e6637f7fb
14 changed files with 1205 additions and 499 deletions
491
package-lock.json
generated
491
package-lock.json
generated
|
|
@ -34,6 +34,7 @@
|
||||||
"react-icons": "^5.4.0",
|
"react-icons": "^5.4.0",
|
||||||
"react-image-file-resizer": "^0.4.8",
|
"react-image-file-resizer": "^0.4.8",
|
||||||
"react-rnd": "^10.4.13",
|
"react-rnd": "^10.4.13",
|
||||||
|
"react-tooltip": "^5.28.0",
|
||||||
"react-window": "^1.8.10",
|
"react-window": "^1.8.10",
|
||||||
"tailwind-merge": "^2.5.4",
|
"tailwind-merge": "^2.5.4",
|
||||||
"tailwind-scrollbar": "^3.1.0",
|
"tailwind-scrollbar": "^3.1.0",
|
||||||
|
|
@ -54,6 +55,7 @@
|
||||||
"eslint-plugin-react-refresh": "^0.4.14",
|
"eslint-plugin-react-refresh": "^0.4.14",
|
||||||
"globals": "^15.11.0",
|
"globals": "^15.11.0",
|
||||||
"postcss": "^8.4.48",
|
"postcss": "^8.4.48",
|
||||||
|
"sass-embedded": "^1.83.4",
|
||||||
"tailwindcss": "^3.4.14",
|
"tailwindcss": "^3.4.14",
|
||||||
"vite": "^5.4.10"
|
"vite": "^5.4.10"
|
||||||
}
|
}
|
||||||
|
|
@ -378,6 +380,13 @@
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@bufbuild/protobuf": {
|
||||||
|
"version": "2.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.3.tgz",
|
||||||
|
"integrity": "sha512-tFQoXHJdkEOSwj5tRIZSPNUuXK3RaR7T1nUrPgbYX1pUbvqqaaZAsfo+NXBPsz5rZMSKVFrgK1WL8Q/MSLvprg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "(Apache-2.0 AND BSD-3-Clause)"
|
||||||
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.21.5",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
||||||
|
|
@ -3814,6 +3823,13 @@
|
||||||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/buffer-builder": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer-builder/-/buffer-builder-0.2.0.tgz",
|
||||||
|
"integrity": "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT/X11"
|
||||||
|
},
|
||||||
"node_modules/call-bind": {
|
"node_modules/call-bind": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
|
||||||
|
|
@ -3965,6 +3981,12 @@
|
||||||
"url": "https://polar.sh/cva"
|
"url": "https://polar.sh/cva"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/classnames": {
|
||||||
|
"version": "2.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
|
||||||
|
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/clsx": {
|
"node_modules/clsx": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||||
|
|
@ -4002,6 +4024,13 @@
|
||||||
"color-support": "bin.js"
|
"color-support": "bin.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/colorjs.io": {
|
||||||
|
"version": "0.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz",
|
||||||
|
"integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/combined-stream": {
|
"node_modules/combined-stream": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
|
@ -5458,6 +5487,13 @@
|
||||||
"node": ">= 4"
|
"node": ">= 4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/immutable": {
|
||||||
|
"version": "5.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.0.3.tgz",
|
||||||
|
"integrity": "sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/import-fresh": {
|
"node_modules/import-fresh": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
||||||
|
|
@ -7251,6 +7287,20 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-tooltip": {
|
||||||
|
"version": "5.28.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.28.0.tgz",
|
||||||
|
"integrity": "sha512-R5cO3JPPXk6FRbBHMO0rI9nkUG/JKfalBSQfZedZYzmqaZQgq7GLzF8vcCWx6IhUCKg0yPqJhXIzmIO5ff15xg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/dom": "^1.6.1",
|
||||||
|
"classnames": "^2.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.14.0",
|
||||||
|
"react-dom": ">=16.14.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-window": {
|
"node_modules/react-window": {
|
||||||
"version": "1.8.10",
|
"version": "1.8.10",
|
||||||
"resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.10.tgz",
|
"resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.10.tgz",
|
||||||
|
|
@ -7474,6 +7524,16 @@
|
||||||
"queue-microtask": "^1.2.2"
|
"queue-microtask": "^1.2.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rxjs": {
|
||||||
|
"version": "7.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
|
||||||
|
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/safe-array-concat": {
|
"node_modules/safe-array-concat": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz",
|
||||||
|
|
@ -7539,6 +7599,407 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"node_modules/sass-embedded": {
|
||||||
|
"version": "1.83.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.83.4.tgz",
|
||||||
|
"integrity": "sha512-Hf2burRA/y5PGxsg6jB9UpoK/xZ6g/pgrkOcdl6j+rRg1Zj8XhGKZ1MTysZGtTPUUmiiErqzkP5+Kzp95yv9GQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@bufbuild/protobuf": "^2.0.0",
|
||||||
|
"buffer-builder": "^0.2.0",
|
||||||
|
"colorjs.io": "^0.5.0",
|
||||||
|
"immutable": "^5.0.2",
|
||||||
|
"rxjs": "^7.4.0",
|
||||||
|
"supports-color": "^8.1.1",
|
||||||
|
"sync-child-process": "^1.0.2",
|
||||||
|
"varint": "^6.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"sass": "dist/bin/sass.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.0.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"sass-embedded-android-arm": "1.83.4",
|
||||||
|
"sass-embedded-android-arm64": "1.83.4",
|
||||||
|
"sass-embedded-android-ia32": "1.83.4",
|
||||||
|
"sass-embedded-android-riscv64": "1.83.4",
|
||||||
|
"sass-embedded-android-x64": "1.83.4",
|
||||||
|
"sass-embedded-darwin-arm64": "1.83.4",
|
||||||
|
"sass-embedded-darwin-x64": "1.83.4",
|
||||||
|
"sass-embedded-linux-arm": "1.83.4",
|
||||||
|
"sass-embedded-linux-arm64": "1.83.4",
|
||||||
|
"sass-embedded-linux-ia32": "1.83.4",
|
||||||
|
"sass-embedded-linux-musl-arm": "1.83.4",
|
||||||
|
"sass-embedded-linux-musl-arm64": "1.83.4",
|
||||||
|
"sass-embedded-linux-musl-ia32": "1.83.4",
|
||||||
|
"sass-embedded-linux-musl-riscv64": "1.83.4",
|
||||||
|
"sass-embedded-linux-musl-x64": "1.83.4",
|
||||||
|
"sass-embedded-linux-riscv64": "1.83.4",
|
||||||
|
"sass-embedded-linux-x64": "1.83.4",
|
||||||
|
"sass-embedded-win32-arm64": "1.83.4",
|
||||||
|
"sass-embedded-win32-ia32": "1.83.4",
|
||||||
|
"sass-embedded-win32-x64": "1.83.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-android-arm": {
|
||||||
|
"version": "1.83.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.83.4.tgz",
|
||||||
|
"integrity": "sha512-9Z4pJAOgEkXa3VDY/o+U6l5XvV0mZTJcSl0l/mSPHihjAHSpLYnOW6+KOWeM8dxqrsqTYcd6COzhanI/a++5Gw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-android-arm64": {
|
||||||
|
"version": "1.83.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.83.4.tgz",
|
||||||
|
"integrity": "sha512-tgX4FzmbVqnQmD67ZxQDvI+qFNABrboOQgwsG05E5bA/US42zGajW9AxpECJYiMXVOHmg+d81ICbjb0fsVHskw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-android-ia32": {
|
||||||
|
"version": "1.83.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-android-ia32/-/sass-embedded-android-ia32-1.83.4.tgz",
|
||||||
|
"integrity": "sha512-RsFOziFqPcfZXdFRULC4Ayzy9aK6R6FwQ411broCjlOBX+b0gurjRadkue3cfUEUR5mmy0KeCbp7zVKPLTK+5Q==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-android-riscv64": {
|
||||||
|
"version": "1.83.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.83.4.tgz",
|
||||||
|
"integrity": "sha512-EHwh0nmQarBBrMRU928eTZkFGx19k/XW2YwbPR4gBVdWLkbTgCA5aGe8hTE6/1zStyx++3nDGvTZ78+b/VvvLg==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-android-x64": {
|
||||||
|
"version": "1.83.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.83.4.tgz",
|
||||||
|
"integrity": "sha512-0PgQNuPWYy1jEOEPDVsV89KfqOsMLIp9CSbjBY7jRcwRhyVAcigqrUG6bDeNtojHUYKA1kU+Eh/85WxOHUOgBw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-darwin-arm64": {
|
||||||
|
"version": "1.83.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.83.4.tgz",
|
||||||
|
"integrity": "sha512-rp2ywymWc3nymnSnAFG5R/8hvxWCsuhK3wOnD10IDlmNB7o4rzKby1c+2ZfpQGowlYGWsWWTgz8FW2qzmZsQRw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-darwin-x64": {
|
||||||
|
"version": "1.83.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.83.4.tgz",
|
||||||
|
"integrity": "sha512-kLkN2lXz9PCgGfDS8Ev5YVcl/V2173L6379en/CaFuJJi7WiyPgBymW7hOmfCt4uO4R1y7CP2Uc08DRtZsBlAA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-linux-arm": {
|
||||||
|
"version": "1.83.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.83.4.tgz",
|
||||||
|
"integrity": "sha512-nL90ryxX2lNmFucr9jYUyHHx21AoAgdCL1O5Ltx2rKg2xTdytAGHYo2MT5S0LIeKLa/yKP/hjuSvrbICYNDvtA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-linux-arm64": {
|
||||||
|
"version": "1.83.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.83.4.tgz",
|
||||||
|
"integrity": "sha512-E0zjsZX2HgESwyqw31EHtI39DKa7RgK7nvIhIRco1d0QEw227WnoR9pjH3M/ZQy4gQj3GKilOFHM5Krs/omeIA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-linux-ia32": {
|
||||||
|
"version": "1.83.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-linux-ia32/-/sass-embedded-linux-ia32-1.83.4.tgz",
|
||||||
|
"integrity": "sha512-ew5HpchSzgAYbQoriRh8QhlWn5Kw2nQ2jHoV9YLwGKe3fwwOWA0KDedssvDv7FWnY/FCqXyymhLd6Bxae4Xquw==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-linux-musl-arm": {
|
||||||
|
"version": "1.83.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.83.4.tgz",
|
||||||
|
"integrity": "sha512-0RrJRwMrmm+gG0VOB5b5Cjs7Sd+lhqpQJa6EJNEaZHljJokEfpE5GejZsGMRMIQLxEvVphZnnxl6sonCGFE/QQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-linux-musl-arm64": {
|
||||||
|
"version": "1.83.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.83.4.tgz",
|
||||||
|
"integrity": "sha512-IzMgalf6MZOxgp4AVCgsaWAFDP/IVWOrgVXxkyhw29fyAEoSWBJH4k87wyPhEtxSuzVHLxKNbc8k3UzdWmlBFg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-linux-musl-ia32": {
|
||||||
|
"version": "1.83.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-ia32/-/sass-embedded-linux-musl-ia32-1.83.4.tgz",
|
||||||
|
"integrity": "sha512-LLb4lYbcxPzX4UaJymYXC+WwokxUlfTJEFUv5VF0OTuSsHAGNRs/rslPtzVBTvMeG9TtlOQDhku1F7G6iaDotA==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-linux-musl-riscv64": {
|
||||||
|
"version": "1.83.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.83.4.tgz",
|
||||||
|
"integrity": "sha512-zoKlPzD5Z13HKin1UGR74QkEy+kZEk2AkGX5RelRG494mi+IWwRuWCppXIovor9+BQb9eDWPYPoMVahwN5F7VA==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-linux-musl-x64": {
|
||||||
|
"version": "1.83.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.83.4.tgz",
|
||||||
|
"integrity": "sha512-hB8+/PYhfEf2zTIcidO5Bpof9trK6WJjZ4T8g2MrxQh8REVtdPcgIkoxczRynqybf9+fbqbUwzXtiUao2GV+vQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-linux-riscv64": {
|
||||||
|
"version": "1.83.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.83.4.tgz",
|
||||||
|
"integrity": "sha512-83fL4n+oeDJ0Y4KjASmZ9jHS1Vl9ESVQYHMhJE0i4xDi/P3BNarm2rsKljq/QtrwGpbqwn8ujzOu7DsNCMDSHA==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-linux-x64": {
|
||||||
|
"version": "1.83.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.83.4.tgz",
|
||||||
|
"integrity": "sha512-NlnGdvCmTD5PK+LKXlK3sAuxOgbRIEoZfnHvxd157imCm/s2SYF/R28D0DAAjEViyI8DovIWghgbcqwuertXsA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-win32-arm64": {
|
||||||
|
"version": "1.83.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.83.4.tgz",
|
||||||
|
"integrity": "sha512-J2BFKrEaeSrVazU2qTjyQdAk+MvbzJeTuCET0uAJEXSKtvQ3AzxvzndS7LqkDPbF32eXAHLw8GVpwcBwKbB3Uw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-win32-ia32": {
|
||||||
|
"version": "1.83.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-win32-ia32/-/sass-embedded-win32-ia32-1.83.4.tgz",
|
||||||
|
"integrity": "sha512-uPAe9T/5sANFhJS5dcfAOhOJy8/l2TRYG4r+UO3Wp4yhqbN7bggPvY9c7zMYS0OC8tU/bCvfYUDFHYMCl91FgA==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-win32-x64": {
|
||||||
|
"version": "1.83.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.83.4.tgz",
|
||||||
|
"integrity": "sha512-C9fkDY0jKITdJFij4UbfPFswxoXN9O/Dr79v17fJnstVwtUojzVJWKHUXvF0Zg2LIR7TCc4ju3adejKFxj7ueA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded/node_modules/supports-color": {
|
||||||
|
"version": "8.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
|
||||||
|
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"has-flag": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/saxes": {
|
"node_modules/saxes": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz",
|
||||||
|
|
@ -7985,6 +8446,29 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"node_modules/sync-child-process": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/sync-child-process/-/sync-child-process-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"sync-message-port": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sync-message-port": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sync-message-port/-/sync-message-port-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tailwind-merge": {
|
"node_modules/tailwind-merge": {
|
||||||
"version": "2.5.5",
|
"version": "2.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.5.tgz",
|
||||||
|
|
@ -8396,6 +8880,13 @@
|
||||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/varint": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "5.4.11",
|
"version": "5.4.11",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@
|
||||||
"react-icons": "^5.4.0",
|
"react-icons": "^5.4.0",
|
||||||
"react-image-file-resizer": "^0.4.8",
|
"react-image-file-resizer": "^0.4.8",
|
||||||
"react-rnd": "^10.4.13",
|
"react-rnd": "^10.4.13",
|
||||||
|
"react-tooltip": "^5.28.0",
|
||||||
"react-window": "^1.8.10",
|
"react-window": "^1.8.10",
|
||||||
"tailwind-merge": "^2.5.4",
|
"tailwind-merge": "^2.5.4",
|
||||||
"tailwind-scrollbar": "^3.1.0",
|
"tailwind-scrollbar": "^3.1.0",
|
||||||
|
|
@ -56,6 +57,7 @@
|
||||||
"eslint-plugin-react-refresh": "^0.4.14",
|
"eslint-plugin-react-refresh": "^0.4.14",
|
||||||
"globals": "^15.11.0",
|
"globals": "^15.11.0",
|
||||||
"postcss": "^8.4.48",
|
"postcss": "^8.4.48",
|
||||||
|
"sass-embedded": "^1.83.4",
|
||||||
"tailwindcss": "^3.4.14",
|
"tailwindcss": "^3.4.14",
|
||||||
"vite": "^5.4.10"
|
"vite": "^5.4.10"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
14
src/App.css
14
src/App.css
|
|
@ -2,3 +2,17 @@
|
||||||
box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.2);
|
box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.2);
|
||||||
border-radius: 0px;
|
border-radius: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tooltip {
|
||||||
|
--tooltip-spacing: 30px;
|
||||||
|
}
|
||||||
|
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||||
|
.example::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide scrollbar for IE, Edge and Firefox */
|
||||||
|
.example {
|
||||||
|
-ms-overflow-style: none; /* IE and Edge */
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,7 @@ export default function Canvas() {
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
className={`w-full max-w-3xl p-2 my-4 overflow-y-scroll scrollbar-thin scrollbar-thumb-secondary scrollbar-track-background rounded-none flex-1 flex flex-col ${
|
className={`w-full max-w-3xl p-2 my-4 overflow-y-scroll scrollbar-thin scrollbar-thumb-secondary scrollbar-track-background rounded-none flex-1 flex flex-col ${
|
||||||
activeObject ? "mt-5" : "mt-20"
|
activeObject ? "mt-20" : "mt-20"
|
||||||
} mx-auto bg-white pl-5 pb-5 pt-5 border-0 shadow-none`}
|
} mx-auto bg-white pl-5 pb-5 pt-5 border-0 shadow-none`}
|
||||||
>
|
>
|
||||||
<CardContent className="p-0 space-y-2">
|
<CardContent className="p-0 space-y-2">
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useContext, useState, useRef, useEffect } from "react";
|
import { useContext, useState, useRef, useEffect, useCallback } from "react";
|
||||||
import { fabric } from "fabric";
|
import { fabric } from "fabric";
|
||||||
import ActiveObjectContext from "@/components/Context/activeObject/ObjectContext";
|
import ActiveObjectContext from "@/components/Context/activeObject/ObjectContext";
|
||||||
import CanvasContext from "@/components/Context/canvasContext/CanvasContext";
|
import CanvasContext from "@/components/Context/canvasContext/CanvasContext";
|
||||||
|
|
@ -47,34 +47,37 @@ const StrokeCustomization = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (object.strokeWidth) {
|
if (object.strokeWidth) {
|
||||||
setStrokeWidth(0);
|
setStrokeWidth(object.strokeWidth || 0);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Recursively process group objects
|
// Recursively process group objects
|
||||||
const processGroupObjects = (group) => {
|
const processGroupObjects = useCallback((group, callback) => {
|
||||||
group._objects.forEach((obj) => {
|
group._objects.forEach((obj) => {
|
||||||
if (obj.type === "group") {
|
if (obj.type === "group") {
|
||||||
processGroupObjects(obj); // Handle nested groups
|
processGroupObjects(obj, callback); // Handle nested groups
|
||||||
} else {
|
} else {
|
||||||
handleObjectStyle(obj); // Apply styles to each object
|
callback(obj); // Apply callback to each object
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
// Effect to get previous values from active object
|
// Effect to get previous values from active object
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (activeObject) {
|
if (activeObject) {
|
||||||
if (activeObject.type === "group") {
|
if (activeObject.type === "group") {
|
||||||
processGroupObjects(activeObject); // Process grouped objects
|
processGroupObjects(activeObject, handleObjectStyle); // Process grouped objects
|
||||||
} else {
|
} else {
|
||||||
handleObjectStyle(activeObject); // Process single object
|
handleObjectStyle(activeObject); // Process single object
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [activeObject]);
|
}, [activeObject, processGroupObjects]);
|
||||||
|
|
||||||
const updatePreview = () => {
|
// Update preview style
|
||||||
if (!previewRef.current) return;
|
const updatePreview = useCallback(() => {
|
||||||
|
if (!previewRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const previewStyle = {
|
const previewStyle = {
|
||||||
width: "80px",
|
width: "80px",
|
||||||
|
|
@ -90,10 +93,6 @@ const StrokeCustomization = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.assign(previewRef.current.style, previewStyle);
|
Object.assign(previewRef.current.style, previewStyle);
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
updatePreview();
|
|
||||||
}, [
|
}, [
|
||||||
strokeWidth,
|
strokeWidth,
|
||||||
strokeColor,
|
strokeColor,
|
||||||
|
|
@ -102,24 +101,35 @@ const StrokeCustomization = () => {
|
||||||
gradientDirection,
|
gradientDirection,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
updatePreview();
|
||||||
|
}, [updatePreview]);
|
||||||
|
|
||||||
|
// Handle stroke width change
|
||||||
const handleStrokeWidthChange = (value) => {
|
const handleStrokeWidthChange = (value) => {
|
||||||
setStrokeWidth(value);
|
setStrokeWidth(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle color type change
|
||||||
const handleColorTypeChange = (type) => {
|
const handleColorTypeChange = (type) => {
|
||||||
setColorType(type);
|
setColorType(type);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle stroke color change
|
||||||
const handleStrokeColorChange = (e) => {
|
const handleStrokeColorChange = (e) => {
|
||||||
setStrokeColor(e.target.value);
|
setStrokeColor(e.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle gradient color change
|
||||||
const handleGradientColorChange = (key, e) => {
|
const handleGradientColorChange = (key, e) => {
|
||||||
setGradientStrokeColors((prev) => ({ ...prev, [key]: e.target.value }));
|
setGradientStrokeColors((prev) => ({ ...prev, [key]: e.target.value }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const applyStrokeStyle = () => {
|
// Apply stroke style to active object
|
||||||
if (!activeObject || activeObject.type === "line") return;
|
const applyStrokeStyle = useCallback(() => {
|
||||||
|
if (!activeObject || activeObject.type === "line" || !canvas) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const width = activeObject?.width || 0;
|
const width = activeObject?.width || 0;
|
||||||
const height = activeObject?.height || 0;
|
const height = activeObject?.height || 0;
|
||||||
|
|
@ -132,7 +142,7 @@ const StrokeCustomization = () => {
|
||||||
};
|
};
|
||||||
const directionCoords = coords[gradientDirection];
|
const directionCoords = coords[gradientDirection];
|
||||||
|
|
||||||
const applyStrokeStyle = (object) => {
|
const applyStrokeToObject = (object) => {
|
||||||
object.set("strokeWidth", strokeWidth);
|
object.set("strokeWidth", strokeWidth);
|
||||||
|
|
||||||
if (colorType === "color") {
|
if (colorType === "color") {
|
||||||
|
|
@ -151,56 +161,42 @@ const StrokeCustomization = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const processGroupObjects = (group) => {
|
|
||||||
group._objects.forEach((obj) => {
|
|
||||||
if (obj.type === "group") {
|
|
||||||
processGroupObjects(obj);
|
|
||||||
} else {
|
|
||||||
applyStrokeStyle(obj);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (activeObject.type === "group") {
|
if (activeObject.type === "group") {
|
||||||
processGroupObjects(activeObject);
|
processGroupObjects(activeObject, applyStrokeToObject);
|
||||||
} else {
|
} else {
|
||||||
applyStrokeStyle(activeObject);
|
applyStrokeToObject(activeObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas.renderAll();
|
canvas.renderAll();
|
||||||
};
|
|
||||||
|
|
||||||
// Automatically apply stroke styles on state change
|
|
||||||
useEffect(() => {
|
|
||||||
applyStrokeStyle();
|
|
||||||
}, [
|
}, [
|
||||||
|
activeObject,
|
||||||
strokeWidth,
|
strokeWidth,
|
||||||
strokeColor,
|
strokeColor,
|
||||||
gradientStrokeColors,
|
gradientStrokeColors,
|
||||||
colorType,
|
colorType,
|
||||||
gradientDirection,
|
gradientDirection,
|
||||||
|
canvas,
|
||||||
|
processGroupObjects,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Automatically apply stroke styles on state change
|
||||||
|
useEffect(() => {
|
||||||
|
applyStrokeStyle();
|
||||||
|
}, [applyStrokeStyle]);
|
||||||
|
|
||||||
|
// Revert stroke styles
|
||||||
const revertStroke = () => {
|
const revertStroke = () => {
|
||||||
if (!activeObject) return;
|
if (!activeObject || !canvas) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const revertObject = (object) => {
|
const revertObject = (object) => {
|
||||||
object.set("strokeWidth", 0);
|
object.set("strokeWidth", 0);
|
||||||
object.set("stroke", null);
|
object.set("stroke", null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const processGroupObjects = (group) => {
|
|
||||||
group._objects.forEach((obj) => {
|
|
||||||
if (obj.type === "group") {
|
|
||||||
processGroupObjects(obj);
|
|
||||||
} else {
|
|
||||||
revertObject(obj);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (activeObject.type === "group") {
|
if (activeObject.type === "group") {
|
||||||
processGroupObjects(activeObject);
|
processGroupObjects(activeObject, revertObject);
|
||||||
} else {
|
} else {
|
||||||
revertObject(activeObject);
|
revertObject(activeObject);
|
||||||
}
|
}
|
||||||
|
|
@ -208,8 +204,10 @@ const StrokeCustomization = () => {
|
||||||
canvas.renderAll();
|
canvas.renderAll();
|
||||||
};
|
};
|
||||||
|
|
||||||
const content = () => {
|
// Render the component
|
||||||
return (
|
return (
|
||||||
|
<Card className="shadow-none border-0">
|
||||||
|
<CollapsibleComponent text={"Stroke Control"}>
|
||||||
<CardContent className="p-0 mb-2">
|
<CardContent className="p-0 mb-2">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -283,7 +281,9 @@ const StrokeCustomization = () => {
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="gradient" className="space-y-4">
|
<TabsContent value="gradient" className="space-y-4">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Label htmlFor="gradient-direction">Gradient Direction</Label>
|
<Label htmlFor="gradient-direction">
|
||||||
|
Gradient Direction
|
||||||
|
</Label>
|
||||||
<Select
|
<Select
|
||||||
value={gradientDirection}
|
value={gradientDirection}
|
||||||
onValueChange={setGradientDirection}
|
onValueChange={setGradientDirection}
|
||||||
|
|
@ -292,10 +292,18 @@ const StrokeCustomization = () => {
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="to bottom">Top to Bottom</SelectItem>
|
<SelectItem value="to bottom" key="to-bottom">
|
||||||
<SelectItem value="to top">Bottom to Top</SelectItem>
|
Top to Bottom
|
||||||
<SelectItem value="to right">Left to Right</SelectItem>
|
</SelectItem>
|
||||||
<SelectItem value="to left">Right to Left</SelectItem>
|
<SelectItem value="to top" key="to-top">
|
||||||
|
Bottom to Top
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="to right" key="to-right">
|
||||||
|
Left to Right
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="to left" key="to-left">
|
||||||
|
Right to Left
|
||||||
|
</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -308,7 +316,9 @@ const StrokeCustomization = () => {
|
||||||
id="gradient-color-1"
|
id="gradient-color-1"
|
||||||
type="color"
|
type="color"
|
||||||
value={gradientStrokeColors.color1}
|
value={gradientStrokeColors.color1}
|
||||||
onChange={(e) => handleGradientColorChange("color1", e)}
|
onChange={(e) =>
|
||||||
|
handleGradientColorChange("color1", e)
|
||||||
|
}
|
||||||
className="w-10 h-10 p-1 rounded-md cursor-pointer"
|
className="w-10 h-10 p-1 rounded-md cursor-pointer"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
|
|
@ -336,7 +346,9 @@ const StrokeCustomization = () => {
|
||||||
id="gradient-color-2"
|
id="gradient-color-2"
|
||||||
type="color"
|
type="color"
|
||||||
value={gradientStrokeColors.color2}
|
value={gradientStrokeColors.color2}
|
||||||
onChange={(e) => handleGradientColorChange("color2", e)}
|
onChange={(e) =>
|
||||||
|
handleGradientColorChange("color2", e)
|
||||||
|
}
|
||||||
className="w-10 h-10 p-1 rounded-md cursor-pointer"
|
className="w-10 h-10 p-1 rounded-md cursor-pointer"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
|
|
@ -382,13 +394,6 @@ const StrokeCustomization = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card className=" shadow-none border-0">
|
|
||||||
<CollapsibleComponent text={"Stroke Control"}>
|
|
||||||
{content()}
|
|
||||||
</CollapsibleComponent>
|
</CollapsibleComponent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import {
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "@/components/ui/popover";
|
} from "@/components/ui/popover";
|
||||||
import { useCallback, useContext, useEffect, useState } from "react";
|
import { useCallback, useContext, useEffect, useRef, useState } from "react";
|
||||||
import {
|
import {
|
||||||
AlignLeft,
|
AlignLeft,
|
||||||
AlignCenter,
|
AlignCenter,
|
||||||
|
|
@ -31,6 +31,7 @@ import {
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { RiLineHeight } from "react-icons/ri";
|
import { RiLineHeight } from "react-icons/ri";
|
||||||
import { Slider } from "@/components/ui/slider";
|
import { Slider } from "@/components/ui/slider";
|
||||||
|
import { Tooltip } from "react-tooltip";
|
||||||
|
|
||||||
const fonts = [
|
const fonts = [
|
||||||
"Roboto",
|
"Roboto",
|
||||||
|
|
@ -88,6 +89,11 @@ const TextCustomization = () => {
|
||||||
const { activeObject } = useContext(ActiveObjectContext);
|
const { activeObject } = useContext(ActiveObjectContext);
|
||||||
const { canvas, setSelectedPanel, textColor } = useContext(CanvasContext);
|
const { canvas, setSelectedPanel, textColor } = useContext(CanvasContext);
|
||||||
|
|
||||||
|
const activeObjectType = activeObject?.type;
|
||||||
|
const hasClipPath = !!activeObject?.clipPath;
|
||||||
|
const customClipPath = activeObject?.isClipPath;
|
||||||
|
|
||||||
|
const prevTextRef = useRef("");
|
||||||
const [text, setText] = useState("");
|
const [text, setText] = useState("");
|
||||||
const [fontFamily, setFontFamily] = useState("Arial");
|
const [fontFamily, setFontFamily] = useState("Arial");
|
||||||
const [fontSize, setFontSize] = useState(20);
|
const [fontSize, setFontSize] = useState(20);
|
||||||
|
|
@ -102,6 +108,13 @@ const TextCustomization = () => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (activeObject?.type === "i-text") {
|
if (activeObject?.type === "i-text") {
|
||||||
|
if (
|
||||||
|
activeObject?.text !== undefined &&
|
||||||
|
activeObject.text !== prevTextRef.current
|
||||||
|
) {
|
||||||
|
setText(activeObject.text);
|
||||||
|
prevTextRef.current = activeObject.text;
|
||||||
|
}
|
||||||
setText(activeObject?.text || "");
|
setText(activeObject?.text || "");
|
||||||
setFontFamily(activeObject?.fontFamily || "Arial");
|
setFontFamily(activeObject?.fontFamily || "Arial");
|
||||||
setFontSize(activeObject?.fontSize || 20);
|
setFontSize(activeObject?.fontSize || 20);
|
||||||
|
|
@ -119,11 +132,16 @@ const TextCustomization = () => {
|
||||||
const updateActiveObject = useCallback(
|
const updateActiveObject = useCallback(
|
||||||
(properties) => {
|
(properties) => {
|
||||||
if (activeObject?.type === "i-text") {
|
if (activeObject?.type === "i-text") {
|
||||||
activeObject.set(properties);
|
// Preserve the text value when updating other properties
|
||||||
|
const updatedProperties = {
|
||||||
|
...properties,
|
||||||
|
text: text || prevTextRef.current || properties.text,
|
||||||
|
};
|
||||||
|
activeObject.set(updatedProperties);
|
||||||
canvas?.renderAll();
|
canvas?.renderAll();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[activeObject, canvas]
|
[activeObject, canvas, text]
|
||||||
); // Add dependencies
|
); // Add dependencies
|
||||||
|
|
||||||
const applyChanges = useCallback(() => {
|
const applyChanges = useCallback(() => {
|
||||||
|
|
@ -153,6 +171,12 @@ const TextCustomization = () => {
|
||||||
updateActiveObject, // Add this dependency
|
updateActiveObject, // Add this dependency
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const handleColorPanelClick = () => {
|
||||||
|
// Store current text value before switching panels
|
||||||
|
prevTextRef.current = text;
|
||||||
|
setSelectedPanel("color");
|
||||||
|
};
|
||||||
|
|
||||||
// Automatically apply changes when state updates
|
// Automatically apply changes when state updates
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (activeObject?.type === "i-text") {
|
if (activeObject?.type === "i-text") {
|
||||||
|
|
@ -209,7 +233,12 @@ const TextCustomization = () => {
|
||||||
{/* New Toolbar Design */}
|
{/* New Toolbar Design */}
|
||||||
<div className="flex w-full items-center space-x-2 rounded-lg p-1 bg-white">
|
<div className="flex w-full items-center space-x-2 rounded-lg p-1 bg-white">
|
||||||
{/* Font Family Select */}
|
{/* Font Family Select */}
|
||||||
<Select value={fontFamily} onValueChange={handleFontFamilyChange}>
|
<a data-tooltip-id="fonts">
|
||||||
|
<Select
|
||||||
|
value={fontFamily}
|
||||||
|
onValueChange={handleFontFamilyChange}
|
||||||
|
title="Font Family"
|
||||||
|
>
|
||||||
<SelectTrigger className="min-w-[140px] h-8">
|
<SelectTrigger className="min-w-[140px] h-8">
|
||||||
<SelectValue placeholder="Select a font" />
|
<SelectValue placeholder="Select a font" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
|
|
@ -227,9 +256,11 @@ const TextCustomization = () => {
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
</a>
|
||||||
|
<Tooltip id="fonts" content="Font Family" place="bottom" />
|
||||||
{/* Font Size Controls */}
|
{/* Font Size Controls */}
|
||||||
<div className="flex items-center border rounded-md">
|
<div className="flex items-center border rounded-md">
|
||||||
|
<a data-tooltip-id="font-dec">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
|
|
@ -238,6 +269,13 @@ const TextCustomization = () => {
|
||||||
>
|
>
|
||||||
<Minus className="h-4 w-4" />
|
<Minus className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</a>
|
||||||
|
<Tooltip
|
||||||
|
id="font-dec"
|
||||||
|
content="Decrease font size"
|
||||||
|
place="bottom"
|
||||||
|
/>
|
||||||
|
<a data-tooltip-id="font-size">
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
value={fontSize}
|
value={fontSize}
|
||||||
|
|
@ -247,6 +285,9 @@ const TextCustomization = () => {
|
||||||
}}
|
}}
|
||||||
className="w-12 h-8 border-0 text-center"
|
className="w-12 h-8 border-0 text-center"
|
||||||
/>
|
/>
|
||||||
|
</a>
|
||||||
|
<Tooltip id="font-size" content="Font size" place="bottom" />
|
||||||
|
<a data-tooltip-id="font-inc">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
|
|
@ -255,6 +296,12 @@ const TextCustomization = () => {
|
||||||
>
|
>
|
||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</a>
|
||||||
|
<Tooltip
|
||||||
|
id="font-inc"
|
||||||
|
content="Increase font size"
|
||||||
|
place="bottom"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Vertical Separator */}
|
{/* Vertical Separator */}
|
||||||
|
|
@ -262,11 +309,15 @@ const TextCustomization = () => {
|
||||||
|
|
||||||
{/* Text Formatting Controls */}
|
{/* Text Formatting Controls */}
|
||||||
<div className="flex items-center space-x-1">
|
<div className="flex items-center space-x-1">
|
||||||
|
{activeObjectType !== "image" &&
|
||||||
|
!hasClipPath &&
|
||||||
|
!customClipPath && (
|
||||||
|
<a data-tooltip-id="text-color">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="relative"
|
className="relative"
|
||||||
onClick={() => setSelectedPanel("color")}
|
onClick={handleColorPanelClick} // Updated onClick handler
|
||||||
>
|
>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<span className="text-lg font-semibold">A</span>
|
<span className="text-lg font-semibold">A</span>
|
||||||
|
|
@ -276,7 +327,11 @@ const TextCustomization = () => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Tooltip id="text-color" content="Text color" place="bottom" />
|
||||||
|
<a data-tooltip-id="text-left">
|
||||||
<Button
|
<Button
|
||||||
variant={textAlign === "left" ? "secondary" : "ghost"}
|
variant={textAlign === "left" ? "secondary" : "ghost"}
|
||||||
size="icon"
|
size="icon"
|
||||||
|
|
@ -284,6 +339,8 @@ const TextCustomization = () => {
|
||||||
>
|
>
|
||||||
<AlignLeft className="h-4 w-4" />
|
<AlignLeft className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</a>
|
||||||
|
<a data-tooltip-id="text-center">
|
||||||
<Button
|
<Button
|
||||||
variant={textAlign === "center" ? "secondary" : "ghost"}
|
variant={textAlign === "center" ? "secondary" : "ghost"}
|
||||||
size="icon"
|
size="icon"
|
||||||
|
|
@ -291,6 +348,8 @@ const TextCustomization = () => {
|
||||||
>
|
>
|
||||||
<AlignCenter className="h-4 w-4" />
|
<AlignCenter className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</a>
|
||||||
|
<a data-tooltip-id="text-right">
|
||||||
<Button
|
<Button
|
||||||
variant={textAlign === "right" ? "secondary" : "ghost"}
|
variant={textAlign === "right" ? "secondary" : "ghost"}
|
||||||
size="icon"
|
size="icon"
|
||||||
|
|
@ -298,6 +357,8 @@ const TextCustomization = () => {
|
||||||
>
|
>
|
||||||
<AlignRight className="h-4 w-4" />
|
<AlignRight className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</a>
|
||||||
|
<a data-tooltip-id="text-bold">
|
||||||
<Button
|
<Button
|
||||||
variant={fontWeight === "bold" ? "secondary" : "ghost"}
|
variant={fontWeight === "bold" ? "secondary" : "ghost"}
|
||||||
size="icon"
|
size="icon"
|
||||||
|
|
@ -306,6 +367,8 @@ const TextCustomization = () => {
|
||||||
>
|
>
|
||||||
<Bold className="h-4 w-4" />
|
<Bold className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</a>
|
||||||
|
<a data-tooltip-id="text-underline">
|
||||||
<Button
|
<Button
|
||||||
variant={underline ? "secondary" : "ghost"}
|
variant={underline ? "secondary" : "ghost"}
|
||||||
size="icon"
|
size="icon"
|
||||||
|
|
@ -314,6 +377,8 @@ const TextCustomization = () => {
|
||||||
>
|
>
|
||||||
<Underline className="h-4 w-4" />
|
<Underline className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</a>
|
||||||
|
<a data-tooltip-id="text-italic">
|
||||||
<Button
|
<Button
|
||||||
variant={fontStyle === "italic" ? "secondary" : "ghost"}
|
variant={fontStyle === "italic" ? "secondary" : "ghost"}
|
||||||
size="icon"
|
size="icon"
|
||||||
|
|
@ -322,6 +387,8 @@ const TextCustomization = () => {
|
||||||
>
|
>
|
||||||
<Italic className="h-4 w-4" />
|
<Italic className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</a>
|
||||||
|
<a data-tooltip-id="line-through">
|
||||||
<Button
|
<Button
|
||||||
variant={linethrough ? "secondary" : "ghost"}
|
variant={linethrough ? "secondary" : "ghost"}
|
||||||
size="icon"
|
size="icon"
|
||||||
|
|
@ -330,6 +397,14 @@ const TextCustomization = () => {
|
||||||
>
|
>
|
||||||
<Strikethrough className="h-4 w-4" />
|
<Strikethrough className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</a>
|
||||||
|
<Tooltip id="text-left" content="Left align" place="bottom" />
|
||||||
|
<Tooltip id="text-center" content="Center align" place="bottom" />
|
||||||
|
<Tooltip id="text-right" content="Right align" place="bottom" />
|
||||||
|
<Tooltip id="text-bold" content="Bold" place="bottom" />
|
||||||
|
<Tooltip id="text-underline" content="Underline" place="bottom" />
|
||||||
|
<Tooltip id="text-italic" content="Italics" place="bottom" />
|
||||||
|
<Tooltip id="line-through" content="StrikeThrough" place="bottom" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Vertical Separator */}
|
{/* Vertical Separator */}
|
||||||
|
|
@ -338,9 +413,11 @@ const TextCustomization = () => {
|
||||||
{/* Spacing Controls */}
|
{/* Spacing Controls */}
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
|
<a data-tooltip-id="spacing">
|
||||||
<Button variant="ghost" size="icon" className="h-8 w-8">
|
<Button variant="ghost" size="icon" className="h-8 w-8">
|
||||||
<RiLineHeight className="h-4 w-4" />
|
<RiLineHeight className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</a>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-44 mt-3">
|
<PopoverContent className="w-44 mt-3">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
|
@ -373,6 +450,7 @@ const TextCustomization = () => {
|
||||||
</div>
|
</div>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
<Tooltip id="spacing" content="Spacing" place="bottom" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Text Input */}
|
{/* Text Input */}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,39 @@
|
||||||
import { useCallback, useContext } from 'react'
|
import { useCallback, useContext, useMemo, useState } from "react";
|
||||||
import CanvasContext from './Context/canvasContext/CanvasContext';
|
import CanvasContext from "./Context/canvasContext/CanvasContext";
|
||||||
import ActiveObjectContext from './Context/activeObject/ObjectContext';
|
import ActiveObjectContext from "./Context/activeObject/ObjectContext";
|
||||||
import { fabric } from 'fabric';
|
import { fabric } from "fabric";
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip';
|
import {
|
||||||
import { Button } from './ui/button';
|
Tooltip,
|
||||||
import { BringToFront, CopyPlus, GroupIcon, SquareX, Trash2, UngroupIcon } from 'lucide-react';
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "./ui/tooltip";
|
||||||
|
import { Button } from "./ui/button";
|
||||||
|
import {
|
||||||
|
BringToFront,
|
||||||
|
SendToBack,
|
||||||
|
CopyPlus,
|
||||||
|
GroupIcon,
|
||||||
|
SquareX,
|
||||||
|
Trash2,
|
||||||
|
UngroupIcon,
|
||||||
|
Layers,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
||||||
|
|
||||||
export const ObjectShortcut = ({ value }) => {
|
export const ObjectShortcut = ({ value }) => {
|
||||||
const { canvas } = useContext(CanvasContext);
|
const { canvas } = useContext(CanvasContext);
|
||||||
const { setActiveObject, activeObject } = useContext(ActiveObjectContext);
|
const { setActiveObject, activeObject } = useContext(ActiveObjectContext);
|
||||||
|
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||||
|
|
||||||
|
const activeObjects = canvas.getActiveObjects();
|
||||||
|
const multipleObjects = activeObjects.length > 1;
|
||||||
|
const objectActive = canvas.getActiveObject();
|
||||||
|
const isGroupObject = objectActive && objectActive.type === "group";
|
||||||
|
|
||||||
const groupSelectedObjects = () => {
|
const groupSelectedObjects = () => {
|
||||||
const activeObjects = canvas.getActiveObjects(); // Get selected objects
|
const activeObjects = canvas.getActiveObjects(); // Get selected objects
|
||||||
if (activeObjects.length > 1) {
|
if (activeObjects.length > 1) {
|
||||||
|
|
||||||
canvas.discardActiveObject();
|
canvas.discardActiveObject();
|
||||||
|
|
||||||
const group = new fabric.Group(activeObjects, {
|
const group = new fabric.Group(activeObjects, {
|
||||||
|
|
@ -24,67 +44,95 @@ export const ObjectShortcut = ({ value }) => {
|
||||||
selectable: true, // Allow group selection
|
selectable: true, // Allow group selection
|
||||||
subTargetCheck: true, // Allow individual object selection
|
subTargetCheck: true, // Allow individual object selection
|
||||||
hasControls: true, // Enable resizing/movement of the group
|
hasControls: true, // Enable resizing/movement of the group
|
||||||
})
|
});
|
||||||
canvas.remove(...activeObjects);
|
canvas.remove(...activeObjects);
|
||||||
canvas.add(group);
|
canvas.add(group);
|
||||||
canvas.setActiveObject(group);
|
canvas.setActiveObject(group);
|
||||||
setActiveObject(group);
|
setActiveObject(group);
|
||||||
canvas.renderAll();
|
canvas.renderAll();
|
||||||
} else {
|
} else {
|
||||||
console.log("Select at least two objects")
|
console.log("Select at least two objects");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const ungroupSelectedObjects = () => {
|
const ungroupSelectedObjects = () => {
|
||||||
const activeObject = canvas.getActiveObject();
|
const activeObject = canvas.getActiveObject();
|
||||||
if (activeObject && activeObject.type === "group") {
|
if (activeObject && activeObject.type === "group") {
|
||||||
// Get the group
|
const groupObjects = activeObject._objects;
|
||||||
const group = activeObject;
|
|
||||||
canvas.discardActiveObject();
|
canvas.discardActiveObject();
|
||||||
// Remove the group from the canvas
|
canvas.remove(activeObject);
|
||||||
canvas.remove(group);
|
|
||||||
setActiveObject(null);
|
setActiveObject(null);
|
||||||
|
|
||||||
// Iterate through each object in the group
|
const ungroupedObjects = [];
|
||||||
group._objects.forEach((object) => {
|
|
||||||
// Calculate the absolute position based on the group's transformation
|
groupObjects.forEach((object) => {
|
||||||
const objLeft = object.left * group.scaleX + group.left;
|
// Calculate absolute position
|
||||||
const objTop = object.top * group.scaleY + group.top;
|
const objLeft = object.left * activeObject.scaleX + activeObject.left;
|
||||||
|
const objTop = object.top * activeObject.scaleY + activeObject.top;
|
||||||
|
|
||||||
// Reset transformations and positions
|
|
||||||
object.set({
|
object.set({
|
||||||
left: objLeft,
|
left: objLeft,
|
||||||
top: objTop,
|
top: objTop,
|
||||||
scaleX: object.scaleX * group.scaleX, // Adjust scale based on group
|
scaleX: object.scaleX * activeObject.scaleX,
|
||||||
scaleY: object.scaleY * group.scaleY, // Adjust scale based on group
|
scaleY: object.scaleY * activeObject.scaleY,
|
||||||
angle: object.angle + group.angle, // Adjust rotation
|
angle: object.angle + activeObject.angle,
|
||||||
hasControls: true, // Allow resizing
|
hasControls: true,
|
||||||
selectable: true, // Make selectable
|
selectable: true,
|
||||||
group: null, // Remove group reference
|
group: null,
|
||||||
originX: "center",
|
originX: "center",
|
||||||
originY: "center",
|
originY: "center",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update coordinates
|
|
||||||
object.setCoords();
|
object.setCoords();
|
||||||
|
|
||||||
// Add the object back to the canvas
|
|
||||||
canvas.add(object);
|
canvas.add(object);
|
||||||
|
|
||||||
|
ungroupedObjects.push(object);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clear the selection
|
const selection = new fabric.ActiveSelection(ungroupedObjects, {
|
||||||
canvas.discardActiveObject();
|
canvas: canvas,
|
||||||
|
originX: "center",
|
||||||
|
originY: "center",
|
||||||
|
});
|
||||||
|
|
||||||
|
canvas.setActiveObject(selection);
|
||||||
|
setActiveObject(selection);
|
||||||
|
|
||||||
// Render the canvas to reflect changes
|
|
||||||
canvas.renderAll();
|
canvas.renderAll();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Bring Selected Object to Front
|
// Check if object is at front or back
|
||||||
|
const objectPosition = useMemo(() => {
|
||||||
|
if (!activeObject || !canvas) {
|
||||||
|
return { isAtFront: false, isAtBack: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
const allObjects = canvas.getObjects();
|
||||||
|
const index = allObjects.indexOf(activeObject);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isAtFront: index === allObjects.length - 1,
|
||||||
|
isAtBack: index === 0,
|
||||||
|
};
|
||||||
|
}, [activeObject, canvas]);
|
||||||
|
|
||||||
|
// Layer ordering functions with checks
|
||||||
const bringToFront = () => {
|
const bringToFront = () => {
|
||||||
if (activeObject) {
|
if (activeObject && !objectPosition.isAtFront) {
|
||||||
activeObject.bringToFront();
|
activeObject.bringToFront();
|
||||||
canvas.renderAll();
|
canvas.renderAll();
|
||||||
|
setIsPopoverOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendToBack = () => {
|
||||||
|
if (activeObject && !objectPosition.isAtBack) {
|
||||||
|
activeObject.sendToBack();
|
||||||
|
canvas.renderAll();
|
||||||
|
setIsPopoverOpen(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -94,8 +142,10 @@ export const ObjectShortcut = ({ value }) => {
|
||||||
|
|
||||||
const allObjects = canvas?.getObjects();
|
const allObjects = canvas?.getObjects();
|
||||||
|
|
||||||
const textObjects = allObjects.filter((obj) => obj.type === 'textbox' || obj.type === 'text' ||
|
const textObjects = allObjects.filter(
|
||||||
obj.type === 'i-text');
|
(obj) =>
|
||||||
|
obj.type === "textbox" || obj.type === "text" || obj.type === "i-text"
|
||||||
|
);
|
||||||
|
|
||||||
if (activeObject) {
|
if (activeObject) {
|
||||||
canvas.remove(activeObject);
|
canvas.remove(activeObject);
|
||||||
|
|
@ -116,23 +166,30 @@ export const ObjectShortcut = ({ value }) => {
|
||||||
// Clone the active object to create a true deep copy
|
// Clone the active object to create a true deep copy
|
||||||
activeObject.clone((clonedObject) => {
|
activeObject.clone((clonedObject) => {
|
||||||
// Add the cloned object to the canvas
|
// Add the cloned object to the canvas
|
||||||
clonedObject.set("left", clonedObject?.left + 30)
|
clonedObject.set("left", clonedObject?.left + 30);
|
||||||
canvas.add(clonedObject);
|
canvas.add(clonedObject);
|
||||||
canvas.renderAll();
|
canvas.renderAll();
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
// for clear canvas
|
// for clear canvas
|
||||||
const clearCanvas = () => {
|
const clearCanvas = () => {
|
||||||
canvas.clear();
|
canvas.clear();
|
||||||
canvas.renderAll();
|
canvas.renderAll();
|
||||||
setActiveObject(null);
|
setActiveObject(null);
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<div className={`grid grid-cols-3 gap-2 ${value === "default" ? "xl:grid-cols-6 lg:grid-cols-6 md:grid-cols-6" : "xl:grid-cols-3 lg:grid-cols-3 md:grid-cols-3"}`}>
|
<div
|
||||||
|
className={`flex items-center gap-2 ${
|
||||||
|
value === "default"
|
||||||
|
? "space-x-4"
|
||||||
|
: "xl:grid-cols-3 lg:grid-cols-3 md:grid-cols-3"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{multipleObjects && (
|
||||||
<ActionButton
|
<ActionButton
|
||||||
icon={<GroupIcon className="h-4 w-4" />}
|
icon={<GroupIcon className="h-4 w-4" />}
|
||||||
label="Group"
|
label="Group"
|
||||||
|
|
@ -143,20 +200,28 @@ export const ObjectShortcut = ({ value }) => {
|
||||||
<p>To select multiple objects:</p>
|
<p>To select multiple objects:</p>
|
||||||
<ol className="list-decimal list-inside mt-1">
|
<ol className="list-decimal list-inside mt-1">
|
||||||
<li>Hold down the Shift key</li>
|
<li>Hold down the Shift key</li>
|
||||||
<li>Click and drag with the left mouse button to select objects</li>
|
<li>
|
||||||
|
Click and drag with the left mouse button to select
|
||||||
|
objects
|
||||||
|
</li>
|
||||||
<li>Release the Shift key and mouse button</li>
|
<li>Release the Shift key and mouse button</li>
|
||||||
</ol>
|
</ol>
|
||||||
<p className="mt-1">Then click this button to group the selected objects.</p>
|
<p className="mt-1">
|
||||||
|
Then click this button to group the selected objects.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isGroupObject && (
|
||||||
<ActionButton
|
<ActionButton
|
||||||
icon={<UngroupIcon className="h-4 w-4" />}
|
icon={<UngroupIcon className="h-4 w-4" />}
|
||||||
label="Ungroup"
|
label="Ungroup"
|
||||||
onClick={ungroupSelectedObjects}
|
onClick={ungroupSelectedObjects}
|
||||||
tooltipContent="Ungroup selected objects"
|
tooltipContent="Ungroup selected objects"
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<ActionButton
|
<ActionButton
|
||||||
icon={<CopyPlus className="h-4 w-4" />}
|
icon={<CopyPlus className="h-4 w-4" />}
|
||||||
|
|
@ -164,13 +229,45 @@ export const ObjectShortcut = ({ value }) => {
|
||||||
onClick={duplicating}
|
onClick={duplicating}
|
||||||
tooltipContent="Duplicate selected objects"
|
tooltipContent="Duplicate selected objects"
|
||||||
/>
|
/>
|
||||||
|
<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
|
||||||
<ActionButton
|
<Tooltip>
|
||||||
icon={<BringToFront className="h-4 w-4" />}
|
<TooltipTrigger asChild>
|
||||||
label="To Front"
|
<PopoverTrigger asChild>
|
||||||
|
<Button variant="outline" size="md" className="w-full">
|
||||||
|
<div className="flex items-center gap-1 p-1">
|
||||||
|
<Layers className="h-4 w-4" />
|
||||||
|
<span className="text-[10px] font-bold">Position</span>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="bottom" align="center">
|
||||||
|
<p>Change object position</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
<PopoverContent className="w-40 p-2">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
onClick={bringToFront}
|
onClick={bringToFront}
|
||||||
tooltipContent="Bring selected objects to front"
|
disabled={objectPosition.isAtFront}
|
||||||
/>
|
>
|
||||||
|
<BringToFront className="h-4 w-4 mr-2" />
|
||||||
|
<span className="text-sm">Bring to Front</span>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={sendToBack}
|
||||||
|
disabled={objectPosition.isAtBack}
|
||||||
|
>
|
||||||
|
<SendToBack className="h-4 w-4 mr-2" />
|
||||||
|
<span className="text-sm">Send to Back</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
|
||||||
<ActionButton
|
<ActionButton
|
||||||
icon={<Trash2 className="h-4 w-4" />}
|
icon={<Trash2 className="h-4 w-4" />}
|
||||||
|
|
@ -188,15 +285,19 @@ export const ObjectShortcut = ({ value }) => {
|
||||||
</div>
|
</div>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
function ActionButton({ icon, label, onClick, tooltipContent }) {
|
function ActionButton({ icon, label, onClick, tooltipContent }) {
|
||||||
return (
|
return (
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button variant="outline" size="md" className="w-full" onClick={onClick}>
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="md"
|
||||||
|
className="w-full"
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
<div className="flex items-center gap-1 p-1">
|
<div className="flex items-center gap-1 p-1">
|
||||||
{icon}
|
{icon}
|
||||||
<span className="text-[10px] font-bold">{label}</span>
|
<span className="text-[10px] font-bold">{label}</span>
|
||||||
|
|
@ -207,5 +308,5 @@ function ActionButton({ icon, label, onClick, tooltipContent }) {
|
||||||
<p>{tooltipContent}</p>
|
<p>{tooltipContent}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ const ColorPanel = () => {
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<ScrollArea className="lg:h-[calc(90vh-190px)] xl:h-[calc(100vh-190px)] px-4 py-4">
|
<ScrollArea className="lg:h-[calc(90vh-190px)] px-4 py-4">
|
||||||
<ApplyColor />
|
<ApplyColor />
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ const EditorPanel = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{selectedPanel !== "" && (
|
{selectedPanel !== "" && (
|
||||||
<div className="w-80 lg:h-[calc(90vh-100px)] xl:h-[calc(100vh-100px)] bg-background rounded-xl shadow-lg mx-4 my-auto">
|
<div className="w-80 lg:h-[calc(90vh-100px)] bg-background rounded-xl shadow-lg mx-4 my-auto">
|
||||||
{renderPanel()}
|
{renderPanel()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
29
src/components/Panel/StrokePanel.jsx
Normal file
29
src/components/Panel/StrokePanel.jsx
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { useContext } from "react";
|
||||||
|
import ApplyColor from "../EachComponent/ApplyColor";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import CanvasContext from "../Context/canvasContext/CanvasContext";
|
||||||
|
import { X } from "lucide-react";
|
||||||
|
import { ScrollArea } from "../ui/scroll-area";
|
||||||
|
|
||||||
|
const ColorPanel = () => {
|
||||||
|
const { setSelectedPanel } = useContext(CanvasContext);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="flex justify-between items-center p-4 border-b">
|
||||||
|
<h2 className="text-lg font-semibold">Color</h2>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => setSelectedPanel("")}
|
||||||
|
>
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<ScrollArea className="lg:h-[calc(90vh-190px)] xl:h-[calc(100vh-190px)] px-4 py-4">
|
||||||
|
<ApplyColor />
|
||||||
|
</ScrollArea>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ColorPanel;
|
||||||
|
|
@ -20,6 +20,7 @@ export default function TextPanel() {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}
|
}
|
||||||
}, [activeObject]);
|
}, [activeObject]);
|
||||||
|
|
||||||
const addText = () => {
|
const addText = () => {
|
||||||
if (canvas) {
|
if (canvas) {
|
||||||
const text = new fabric.IText("Editable Text", {
|
const text = new fabric.IText("Editable Text", {
|
||||||
|
|
@ -27,6 +28,8 @@ export default function TextPanel() {
|
||||||
top: 100,
|
top: 100,
|
||||||
fontFamily: "Poppins",
|
fontFamily: "Poppins",
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
|
stroke: "", // empty string for no stroke color
|
||||||
|
strokeWidth: 0, // set stroke width to 0
|
||||||
});
|
});
|
||||||
// Add the text to the canvas and re-render
|
// Add the text to the canvas and re-render
|
||||||
canvas.add(text);
|
canvas.add(text);
|
||||||
|
|
@ -50,7 +53,7 @@ export default function TextPanel() {
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ScrollArea className="lg:h-[calc(90vh-190px)] xl:h-[calc(100vh-190px)] px-4 py-4">
|
<ScrollArea className="lg:h-[calc(90vh-190px)] px-4 py-4">
|
||||||
<Button
|
<Button
|
||||||
className="w-full bg-[#FF2B85] hover:bg-[#FF2B85] text-white rounded-[10px] mb-6 h-12 font-medium text-xl"
|
className="w-full bg-[#FF2B85] hover:bg-[#FF2B85] text-white rounded-[10px] mb-6 h-12 font-medium text-xl"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
|
||||||
|
|
@ -1,48 +1,36 @@
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "../ui/Select";
|
|
||||||
import TextCustomization from "../EachComponent/Customization/TextCustomization";
|
import TextCustomization from "../EachComponent/Customization/TextCustomization";
|
||||||
import LockObject from "../EachComponent/Customization/LockObject";
|
import LockObject from "../EachComponent/Customization/LockObject";
|
||||||
import { ScrollArea, ScrollBar } from "../ui/scroll-area";
|
import { ScrollArea, ScrollBar } from "../ui/scroll-area";
|
||||||
import OpacityCustomization from "../EachComponent/Customization/OpacityCustomization";
|
import OpacityCustomization from "../EachComponent/Customization/OpacityCustomization";
|
||||||
import CanvasContext from "../Context/canvasContext/CanvasContext";
|
import CanvasContext from "../Context/canvasContext/CanvasContext";
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
|
import { ObjectShortcut } from "../ObjectShortcut";
|
||||||
|
|
||||||
export function TopBar() {
|
export function TopBar() {
|
||||||
const { selectedPanel } = useContext(CanvasContext);
|
const { selectedPanel } = useContext(CanvasContext);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<ScrollArea
|
<ScrollArea
|
||||||
className={`absolute top-2 lg:left-[20%] ${
|
className={`!absolute top-2 -translate-x-1/2 z-40 h-28 ${
|
||||||
selectedPanel !== ""
|
selectedPanel !== ""
|
||||||
? "lg:w-[600px] xl:w-[820px] xl:left-[40%]"
|
? "w-[500px] lg:w-[600px] xl:w-[900px] lg:left-[10%] xl:left-[25%] 2xl:left-[50%]"
|
||||||
: "w-[70%] xl:left-[50%]"
|
: "w-[500px] lg:w-[600px] xl:w-[900px] lg:left-[41%] xl:left-[50%]"
|
||||||
} -translate-x-1/2 z-40 scrollbar-hide`}
|
} `}
|
||||||
>
|
>
|
||||||
<div className="bg-white rounded-[16px] shadow-sm px-4 py-2 flex items-center gap-2">
|
<div className="bg-white shadow-sm mx-auto px-4 py-2 flex justify-center items-center gap-2">
|
||||||
<div>
|
<div>
|
||||||
<TextCustomization />
|
<TextCustomization />
|
||||||
</div>
|
</div>
|
||||||
<OpacityCustomization />
|
<OpacityCustomization />
|
||||||
<div className="h-4 w-px bg-border mx-2" />
|
<div className="h-4 w-px bg-border mx-2" />
|
||||||
|
|
||||||
<Select defaultValue="position">
|
<div>
|
||||||
<SelectTrigger className="w-[100px] h-9">
|
<ObjectShortcut value={"default"} />
|
||||||
<SelectValue placeholder="Position" />
|
</div>
|
||||||
</SelectTrigger>
|
<div className="ml-4">
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="position">Position</SelectItem>
|
|
||||||
<SelectItem value="front">Bring to Front</SelectItem>
|
|
||||||
<SelectItem value="back">Send to Back</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
|
|
||||||
<LockObject />
|
<LockObject />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<ScrollBar orientation="horizontal" />
|
<ScrollBar orientation="horizontal" />
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,43 @@
|
||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
|
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const ScrollArea = React.forwardRef(({ className, children, ...props }, ref) => (
|
const ScrollArea = React.forwardRef(
|
||||||
|
({ className, children, ...props }, ref) => (
|
||||||
<ScrollAreaPrimitive.Root
|
<ScrollAreaPrimitive.Root
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn("relative overflow-hidden", className)}
|
className={cn("relative overflow-hidden", className)}
|
||||||
{...props}>
|
{...props}
|
||||||
|
>
|
||||||
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit] p-1">
|
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit] p-1">
|
||||||
{children}
|
{children}
|
||||||
</ScrollAreaPrimitive.Viewport>
|
</ScrollAreaPrimitive.Viewport>
|
||||||
<ScrollBar />
|
<ScrollBar />
|
||||||
<ScrollAreaPrimitive.Corner />
|
<ScrollAreaPrimitive.Corner />
|
||||||
</ScrollAreaPrimitive.Root>
|
</ScrollAreaPrimitive.Root>
|
||||||
))
|
)
|
||||||
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
|
);
|
||||||
|
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
|
||||||
|
|
||||||
const ScrollBar = React.forwardRef(({ className, orientation = "vertical", ...props }, ref) => (
|
const ScrollBar = React.forwardRef(
|
||||||
|
({ className, orientation = "vertical", ...props }, ref) => (
|
||||||
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||||
ref={ref}
|
ref={ref}
|
||||||
orientation={orientation}
|
orientation={orientation}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex touch-none select-none transition-colors",
|
"flex touch-none select-none transition-colors",
|
||||||
orientation === "vertical" &&
|
orientation === "vertical" && " border-l border-l-transparent p-[1px]",
|
||||||
"h-full w-2.5 border-l border-l-transparent p-[1px]",
|
|
||||||
orientation === "horizontal" &&
|
orientation === "horizontal" &&
|
||||||
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
|
" flex-col border-t border-t-transparent p-[1px]",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}>
|
{...props}
|
||||||
|
>
|
||||||
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
|
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
|
||||||
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||||
))
|
)
|
||||||
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
|
);
|
||||||
|
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
|
||||||
|
|
||||||
export { ScrollArea, ScrollBar }
|
export { ScrollArea, ScrollBar };
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import scrollbar from 'tailwind-scrollbar';
|
import scrollbar from "tailwind-scrollbar";
|
||||||
import scrollbarHide from 'tailwind-scrollbar-hide';
|
import scrollbarHide from "tailwind-scrollbar-hide";
|
||||||
|
|
||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
export default {
|
export default {
|
||||||
|
|
@ -9,66 +9,56 @@ export default {
|
||||||
extend: {
|
extend: {
|
||||||
textColor: {
|
textColor: {
|
||||||
primary: {
|
primary: {
|
||||||
DEFAULT: 'hsl(var(--primary-text))',
|
DEFAULT: "hsl(var(--primary-text))",
|
||||||
foreground: 'hsl(var(--primary-foreground))',
|
foreground: "hsl(var(--primary-foreground))",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
colors: {
|
colors: {
|
||||||
border: 'hsl(var(--border))',
|
border: "hsl(var(--border))",
|
||||||
input: 'hsl(var(--input))',
|
input: "hsl(var(--input))",
|
||||||
ring: 'hsl(var(--ring))',
|
ring: "hsl(var(--ring))",
|
||||||
background: 'hsl(var(--background))',
|
background: "hsl(var(--background))",
|
||||||
foreground: 'hsl(var(--foreground))',
|
foreground: "hsl(var(--foreground))",
|
||||||
selection: {
|
selection: {
|
||||||
DEFAULT: 'hsl(var(--selection))',
|
DEFAULT: "hsl(var(--selection))",
|
||||||
foreground: 'hsl(var(--selection-foreground))',
|
foreground: "hsl(var(--selection-foreground))",
|
||||||
},
|
},
|
||||||
primary: {
|
primary: {
|
||||||
DEFAULT: 'hsl(var(--primary))',
|
DEFAULT: "hsl(var(--primary))",
|
||||||
foreground: 'hsl(var(--primary-foreground))',
|
foreground: "hsl(var(--primary-foreground))",
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
DEFAULT: 'hsl(var(--secondary))',
|
DEFAULT: "hsl(var(--secondary))",
|
||||||
foreground: 'hsl(var(--secondary-foreground))',
|
foreground: "hsl(var(--secondary-foreground))",
|
||||||
},
|
},
|
||||||
destructive: {
|
destructive: {
|
||||||
DEFAULT: 'hsl(var(--destructive))',
|
DEFAULT: "hsl(var(--destructive))",
|
||||||
foreground: 'hsl(var(--destructive-foreground))',
|
foreground: "hsl(var(--destructive-foreground))",
|
||||||
},
|
},
|
||||||
muted: {
|
muted: {
|
||||||
DEFAULT: 'hsl(var(--muted))',
|
DEFAULT: "hsl(var(--muted))",
|
||||||
foreground: 'hsl(var(--muted-foreground))',
|
foreground: "hsl(var(--muted-foreground))",
|
||||||
},
|
},
|
||||||
accent: {
|
accent: {
|
||||||
DEFAULT: 'hsl(var(--accent))',
|
DEFAULT: "hsl(var(--accent))",
|
||||||
foreground: 'hsl(var(--accent-foreground))',
|
foreground: "hsl(var(--accent-foreground))",
|
||||||
},
|
},
|
||||||
popover: {
|
popover: {
|
||||||
DEFAULT: 'hsl(var(--popover))',
|
DEFAULT: "hsl(var(--popover))",
|
||||||
foreground: 'hsl(var(--popover-foreground))',
|
foreground: "hsl(var(--popover-foreground))",
|
||||||
},
|
},
|
||||||
card: {
|
card: {
|
||||||
DEFAULT: 'hsl(var(--card))',
|
DEFAULT: "hsl(var(--card))",
|
||||||
foreground: 'hsl(var(--card-foreground))',
|
foreground: "hsl(var(--card-foreground))",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
borderRadius: {
|
borderRadius: {
|
||||||
lg: 'var(--radius)',
|
lg: "var(--radius)",
|
||||||
md: 'calc(var(--radius) - 2px)',
|
md: "calc(var(--radius) - 2px)",
|
||||||
sm: 'calc(var(--radius) - 4px)',
|
sm: "calc(var(--radius) - 4px)",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [scrollbar, scrollbarHide],
|
||||||
scrollbar,
|
|
||||||
scrollbarHide,
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue