diff --git a/package-lock.json b/package-lock.json
index d64691a56..629cf368d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10422,420 +10422,6 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
- "node_modules/@uiw/color-convert": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/@uiw/color-convert/-/color-convert-2.9.2.tgz",
- "integrity": "sha512-ibw9OS29S7GlL+vDwU3p5XG3vhR7XdzUecydpZbakUeg2Td6nfsnrCAX9sbLwQ73p0abO42v+V4qRaWq+7/BjQ==",
- "license": "MIT",
- "funding": {
- "url": "https://jaywcjlove.github.io/#/sponsor"
- },
- "peerDependencies": {
- "@babel/runtime": ">=7.19.0"
- }
- },
- "node_modules/@uiw/react-color": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/@uiw/react-color/-/react-color-2.9.2.tgz",
- "integrity": "sha512-nIrw4Ol6boAkr1CUcCAkrUFVrKbT9T7/0qaSDpXmiDgKYf77gbXTWTsqVuXVDCSoCn28LvurpASS4AW8oiSBtg==",
- "license": "MIT",
- "dependencies": {
- "@uiw/color-convert": "2.9.2",
- "@uiw/react-color-alpha": "2.9.2",
- "@uiw/react-color-block": "2.9.2",
- "@uiw/react-color-chrome": "2.9.2",
- "@uiw/react-color-circle": "2.9.2",
- "@uiw/react-color-colorful": "2.9.2",
- "@uiw/react-color-compact": "2.9.2",
- "@uiw/react-color-editable-input": "2.9.2",
- "@uiw/react-color-editable-input-hsla": "2.9.2",
- "@uiw/react-color-editable-input-rgba": "2.9.2",
- "@uiw/react-color-github": "2.9.2",
- "@uiw/react-color-hue": "2.9.2",
- "@uiw/react-color-material": "2.9.2",
- "@uiw/react-color-name": "2.9.2",
- "@uiw/react-color-saturation": "2.9.2",
- "@uiw/react-color-shade-slider": "2.9.2",
- "@uiw/react-color-sketch": "2.9.2",
- "@uiw/react-color-slider": "2.9.2",
- "@uiw/react-color-swatch": "2.9.2",
- "@uiw/react-color-wheel": "2.9.2"
- },
- "funding": {
- "url": "https://jaywcjlove.github.io/#/sponsor"
- },
- "peerDependencies": {
- "@babel/runtime": ">=7.19.0",
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/@uiw/react-color-alpha": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/@uiw/react-color-alpha/-/react-color-alpha-2.9.2.tgz",
- "integrity": "sha512-a2ACkE2vZIS4xnN9DaRfkQtAX/t8oK5NRSbX2QeOL23WIMHP1VNs7Yq5gXB68RHYenFgvs2JHuMOxZ2mK1W5Mw==",
- "license": "MIT",
- "dependencies": {
- "@uiw/color-convert": "2.9.2",
- "@uiw/react-drag-event-interactive": "2.9.2"
- },
- "funding": {
- "url": "https://jaywcjlove.github.io/#/sponsor"
- },
- "peerDependencies": {
- "@babel/runtime": ">=7.19.0",
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/@uiw/react-color-block": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/@uiw/react-color-block/-/react-color-block-2.9.2.tgz",
- "integrity": "sha512-0EIZTELA5pnxyMlBOFo3hrpy73db+Qeq6E+QptNfD/8izor8OvY1Uquj2VqD6gDz+iVHMELIoKxpaQ8sZR7NOg==",
- "license": "MIT",
- "dependencies": {
- "@uiw/color-convert": "2.9.2",
- "@uiw/react-color-editable-input": "2.9.2",
- "@uiw/react-color-swatch": "2.9.2"
- },
- "funding": {
- "url": "https://jaywcjlove.github.io/#/sponsor"
- },
- "peerDependencies": {
- "@babel/runtime": ">=7.19.0",
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/@uiw/react-color-chrome": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/@uiw/react-color-chrome/-/react-color-chrome-2.9.2.tgz",
- "integrity": "sha512-p7OZB7VWrkVbHxcTHsAq5U2vt3hAP3VvKEiDi592LKxS11IMnSd15ta8ngbJaXZWatqEpJSNgj12581yHtx+Bg==",
- "license": "MIT",
- "dependencies": {
- "@uiw/color-convert": "2.9.2",
- "@uiw/react-color-alpha": "2.9.2",
- "@uiw/react-color-editable-input": "2.9.2",
- "@uiw/react-color-editable-input-hsla": "2.9.2",
- "@uiw/react-color-editable-input-rgba": "2.9.2",
- "@uiw/react-color-github": "2.9.2",
- "@uiw/react-color-hue": "2.9.2",
- "@uiw/react-color-saturation": "2.9.2"
- },
- "funding": {
- "url": "https://jaywcjlove.github.io/#/sponsor"
- },
- "peerDependencies": {
- "@babel/runtime": ">=7.19.0",
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/@uiw/react-color-circle": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/@uiw/react-color-circle/-/react-color-circle-2.9.2.tgz",
- "integrity": "sha512-7XaeX3LfCRkZKffHL/KtYps7I9hNpmx9sJOuwi0ML+3urToFD8s7Iuq3upYZt8REYt1Y84SBjuUqx2YYmjUEjA==",
- "license": "MIT",
- "dependencies": {
- "@uiw/color-convert": "2.9.2",
- "@uiw/react-color-swatch": "2.9.2"
- },
- "funding": {
- "url": "https://jaywcjlove.github.io/#/sponsor"
- },
- "peerDependencies": {
- "@babel/runtime": ">=7.19.0",
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/@uiw/react-color-colorful": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/@uiw/react-color-colorful/-/react-color-colorful-2.9.2.tgz",
- "integrity": "sha512-tz/xeHayna2wpLipkZmcMgL1rmLMxfAmlOyBhUeWrpvqb9Fx59C/wL+5IYJA4rdsQvr9WyWjWmU/GhVKsEkW9w==",
- "license": "MIT",
- "dependencies": {
- "@uiw/color-convert": "2.9.2",
- "@uiw/react-color-alpha": "2.9.2",
- "@uiw/react-color-hue": "2.9.2",
- "@uiw/react-color-saturation": "2.9.2"
- },
- "funding": {
- "url": "https://jaywcjlove.github.io/#/sponsor"
- },
- "peerDependencies": {
- "@babel/runtime": ">=7.19.0",
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/@uiw/react-color-compact": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/@uiw/react-color-compact/-/react-color-compact-2.9.2.tgz",
- "integrity": "sha512-fSSgRRBjkYuGRebZ7XM5XGVFvFwEyEGaV6mOhUpr3JFKhIES0/9oPbd80GDbRdj57Zxxrj76MCtd/aCFfwQSWA==",
- "license": "MIT",
- "dependencies": {
- "@uiw/color-convert": "2.9.2",
- "@uiw/react-color-editable-input": "2.9.2",
- "@uiw/react-color-editable-input-rgba": "2.9.2",
- "@uiw/react-color-swatch": "2.9.2"
- },
- "funding": {
- "url": "https://jaywcjlove.github.io/#/sponsor"
- },
- "peerDependencies": {
- "@babel/runtime": ">=7.19.0",
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/@uiw/react-color-editable-input": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/@uiw/react-color-editable-input/-/react-color-editable-input-2.9.2.tgz",
- "integrity": "sha512-DY7pu12+LDRn6cxDMvsy1/quaPTxicAPz/kfODV7KBf8+Hq4rFWeJ4KS6m22IKIbQxrBQgmQG0WFJLaPeY7cPw==",
- "license": "MIT",
- "funding": {
- "url": "https://jaywcjlove.github.io/#/sponsor"
- },
- "peerDependencies": {
- "@babel/runtime": ">=7.19.0",
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/@uiw/react-color-editable-input-hsla": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/@uiw/react-color-editable-input-hsla/-/react-color-editable-input-hsla-2.9.2.tgz",
- "integrity": "sha512-kZfj3W20msLeP8/HY498rG30eBHRPAyxduhu94HLa9XggT/0ogwA9xZJZgWd6B7FYPeRlhRDY7dnF7caND63GQ==",
- "license": "MIT",
- "dependencies": {
- "@uiw/color-convert": "2.9.2",
- "@uiw/react-color-editable-input-rgba": "2.9.2"
- },
- "funding": {
- "url": "https://jaywcjlove.github.io/#/sponsor"
- },
- "peerDependencies": {
- "@babel/runtime": ">=7.19.0",
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/@uiw/react-color-editable-input-rgba": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/@uiw/react-color-editable-input-rgba/-/react-color-editable-input-rgba-2.9.2.tgz",
- "integrity": "sha512-sQW3tSao754954aQuVK4qvn1i+KC2piE4UftaBubD3QxC02gg5VfgZRoI6AV+nLr73Ifv3mCXewjN1BcP/+x4A==",
- "license": "MIT",
- "dependencies": {
- "@uiw/color-convert": "2.9.2",
- "@uiw/react-color-editable-input": "2.9.2"
- },
- "funding": {
- "url": "https://jaywcjlove.github.io/#/sponsor"
- },
- "peerDependencies": {
- "@babel/runtime": ">=7.19.0",
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/@uiw/react-color-github": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/@uiw/react-color-github/-/react-color-github-2.9.2.tgz",
- "integrity": "sha512-pKR94swzWxqRFUEZJBQ8WHcw6CklxLWhDyjvGpxiieAlwUAL0mlmtCcctRgsmJRuAKQlZx4WNslRgNX5aVcoZw==",
- "license": "MIT",
- "dependencies": {
- "@uiw/color-convert": "2.9.2",
- "@uiw/react-color-swatch": "2.9.2"
- },
- "funding": {
- "url": "https://jaywcjlove.github.io/#/sponsor"
- },
- "peerDependencies": {
- "@babel/runtime": ">=7.19.0",
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/@uiw/react-color-hue": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/@uiw/react-color-hue/-/react-color-hue-2.9.2.tgz",
- "integrity": "sha512-vDGN5YCzw09BfxkQeDvAeQ7zAy141uJ3HkFk1lsXL7ha8xZ35AItE1s/C6d60vFjGdoloKShh0yA7df3pnjmxA==",
- "license": "MIT",
- "dependencies": {
- "@uiw/color-convert": "2.9.2",
- "@uiw/react-color-alpha": "2.9.2"
- },
- "funding": {
- "url": "https://jaywcjlove.github.io/#/sponsor"
- },
- "peerDependencies": {
- "@babel/runtime": ">=7.19.0",
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/@uiw/react-color-material": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/@uiw/react-color-material/-/react-color-material-2.9.2.tgz",
- "integrity": "sha512-R9MI9IlTof/L1rdxUFQAWgAgUSNJGXQsPujo8UGpwR7o5d+A3wwybUnPBsGKRnZwDy5zW7x+lPxY46GXE9aU9Q==",
- "license": "MIT",
- "dependencies": {
- "@uiw/color-convert": "2.9.2",
- "@uiw/react-color-editable-input": "2.9.2",
- "@uiw/react-color-editable-input-rgba": "2.9.2"
- },
- "funding": {
- "url": "https://jaywcjlove.github.io/#/sponsor"
- },
- "peerDependencies": {
- "@babel/runtime": ">=7.19.0",
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/@uiw/react-color-name": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/@uiw/react-color-name/-/react-color-name-2.9.2.tgz",
- "integrity": "sha512-knqRUkAe3pv6rB+tGzaURtwQkBqjRG62YNlzUx8Ty7g+pskWpLSPiMikW+9H5sLPq7wU3ichZiygqIp4BRgQzA==",
- "license": "MIT",
- "dependencies": {
- "colors-named": "^1.0.1",
- "colors-named-hex": "^1.0.1"
- },
- "funding": {
- "url": "https://jaywcjlove.github.io/#/sponsor"
- },
- "peerDependencies": {
- "@babel/runtime": ">=7.19.0"
- }
- },
- "node_modules/@uiw/react-color-saturation": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/@uiw/react-color-saturation/-/react-color-saturation-2.9.2.tgz",
- "integrity": "sha512-w1aUU+g6Axwbr1nLvF8k/zg5v7UW8z80eH6C7w+tdiOFOQKkKQlXqeOG0IIUUIj3v/ji+yM90IuOH+Ku7zsJrg==",
- "license": "MIT",
- "dependencies": {
- "@uiw/color-convert": "2.9.2",
- "@uiw/react-drag-event-interactive": "2.9.2"
- },
- "funding": {
- "url": "https://jaywcjlove.github.io/#/sponsor"
- },
- "peerDependencies": {
- "@babel/runtime": ">=7.19.0",
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/@uiw/react-color-shade-slider": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/@uiw/react-color-shade-slider/-/react-color-shade-slider-2.9.2.tgz",
- "integrity": "sha512-VE59rWv5ixqCN2CTpoe33j4SOCGU62bKguizx4HxgKczE/X0ySeEas8iP5XLg/4fYWl3EZN4uI+M8mNRnB0DPw==",
- "license": "MIT",
- "dependencies": {
- "@uiw/color-convert": "2.9.2",
- "@uiw/react-color-alpha": "2.9.2"
- },
- "funding": {
- "url": "https://jaywcjlove.github.io/#/sponsor"
- },
- "peerDependencies": {
- "@babel/runtime": ">=7.19.0",
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/@uiw/react-color-sketch": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/@uiw/react-color-sketch/-/react-color-sketch-2.9.2.tgz",
- "integrity": "sha512-PEvwaqDVDdBI/+fWASBWQOvx3ows7dIcv6Z06VHgEXk2chi95Fkrbd0YUUXMcp7ESsmUK1j5ozGMLAf9Nvx6Nw==",
- "license": "MIT",
- "dependencies": {
- "@uiw/color-convert": "2.9.2",
- "@uiw/react-color-alpha": "2.9.2",
- "@uiw/react-color-editable-input": "2.9.2",
- "@uiw/react-color-editable-input-rgba": "2.9.2",
- "@uiw/react-color-hue": "2.9.2",
- "@uiw/react-color-saturation": "2.9.2",
- "@uiw/react-color-swatch": "2.9.2"
- },
- "funding": {
- "url": "https://jaywcjlove.github.io/#/sponsor"
- },
- "peerDependencies": {
- "@babel/runtime": ">=7.19.0",
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/@uiw/react-color-slider": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/@uiw/react-color-slider/-/react-color-slider-2.9.2.tgz",
- "integrity": "sha512-vsi0AwmFJpb+flF8XCkacbX+MwLGOzrDKMqR29XE5sO8ERaezoT5mmYXzXXFcjyZYIuXse4C3JT38nsmOBp1vQ==",
- "license": "MIT",
- "dependencies": {
- "@uiw/color-convert": "2.9.2",
- "@uiw/react-color-alpha": "2.9.2"
- },
- "funding": {
- "url": "https://jaywcjlove.github.io/#/sponsor"
- },
- "peerDependencies": {
- "@babel/runtime": ">=7.19.0",
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/@uiw/react-color-swatch": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/@uiw/react-color-swatch/-/react-color-swatch-2.9.2.tgz",
- "integrity": "sha512-6zBy+E9NzZR672M2wPsbbNRqKy9Wi9jOuuxxyzov1CEZp+pPX7UwMlCX6RUhKdO0PzTSPCVQmbz5bplu5vsW0w==",
- "license": "MIT",
- "dependencies": {
- "@uiw/color-convert": "2.9.2"
- },
- "funding": {
- "url": "https://jaywcjlove.github.io/#/sponsor"
- },
- "peerDependencies": {
- "@babel/runtime": ">=7.19.0",
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/@uiw/react-color-wheel": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/@uiw/react-color-wheel/-/react-color-wheel-2.9.2.tgz",
- "integrity": "sha512-ayGzQyMZM3Cp+sX7LNElQ/QQWMO7YG4k/RQwVJAhxNQ+4lJ/p4LLSnI85D7NxILkk+jiXnjxRroxxZ2eJhWo+g==",
- "license": "MIT",
- "dependencies": {
- "@uiw/color-convert": "2.9.2",
- "@uiw/react-drag-event-interactive": "2.9.2"
- },
- "funding": {
- "url": "https://jaywcjlove.github.io/#/sponsor"
- },
- "peerDependencies": {
- "@babel/runtime": ">=7.19.0",
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/@uiw/react-drag-event-interactive": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/@uiw/react-drag-event-interactive/-/react-drag-event-interactive-2.9.2.tgz",
- "integrity": "sha512-6gxQz+Ij7JkXlEOpfZhOu+Gdp/sI9VnMeDl8AJeYl3+0YXP31lXGmyb0NkNYnoUmJO+RrAf68c1raMpaDWs+Ow==",
- "license": "MIT",
- "funding": {
- "url": "https://jaywcjlove.github.io/#/sponsor"
- },
- "peerDependencies": {
- "@babel/runtime": ">=7.19.0",
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
"node_modules/@usebruno/app": {
"resolved": "packages/bruno-app",
"link": true
@@ -13760,30 +13346,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/colors-named": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/colors-named/-/colors-named-1.0.4.tgz",
- "integrity": "sha512-R254qrKSxFJNa7QmM7vrRaz5Hygr7MIaNbXcIx7WfmlYJ9OjZQ+aczGlnKS8lLtNT0GM9aGZ4EcmNXrh5ttv6g==",
- "license": "MIT",
- "engines": {
- "node": ">=14.16"
- },
- "funding": {
- "url": "https://jaywcjlove.github.io/#/sponsor"
- }
- },
- "node_modules/colors-named-hex": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/colors-named-hex/-/colors-named-hex-1.0.3.tgz",
- "integrity": "sha512-vhUoMdCdOKgD9Ni3p6uV3ET1JJCHzlcK6lN3/yl+6TUHinDE6HUFlmnvkh/NDZ2M9049Ipn3mX85qu6akRiC1g==",
- "license": "MIT",
- "engines": {
- "node": ">=14.16"
- },
- "funding": {
- "url": "https://jaywcjlove.github.io/#/sponsor"
- }
- },
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -30456,7 +30018,6 @@
"@tabler/icons": "^1.46.0",
"@testing-library/user-event": "^14.6.1",
"@tippyjs/react": "^4.2.6",
- "@uiw/react-color": "^2.9.2",
"@usebruno/common": "0.1.0",
"@usebruno/graphql-docs": "0.1.0",
"@usebruno/schema": "0.7.0",
diff --git a/packages/bruno-app/package.json b/packages/bruno-app/package.json
index e6d45ac33..b9e12715f 100644
--- a/packages/bruno-app/package.json
+++ b/packages/bruno-app/package.json
@@ -18,7 +18,6 @@
"@tabler/icons": "^1.46.0",
"@testing-library/user-event": "^14.6.1",
"@tippyjs/react": "^4.2.6",
- "@uiw/react-color": "^2.9.2",
"@usebruno/common": "0.1.0",
"@usebruno/graphql-docs": "0.1.0",
"@usebruno/schema": "0.7.0",
diff --git a/packages/bruno-app/src/components/ColorBadge/index.js b/packages/bruno-app/src/components/ColorBadge/index.js
new file mode 100644
index 000000000..dc372bbee
--- /dev/null
+++ b/packages/bruno-app/src/components/ColorBadge/index.js
@@ -0,0 +1,24 @@
+import React from 'react';
+import { useTheme } from 'providers/Theme';
+
+const ColorBadge = ({ color, size = 10, showEmptyBorder = true }) => {
+ const sizeValue = typeof size === 'string' ? size : `${size}px`;
+ const { theme } = useTheme();
+
+ const showBorder = !color && showEmptyBorder;
+
+ return (
+
+ );
+};
+
+export default ColorBadge;
diff --git a/packages/bruno-app/src/components/ColorPicker/StyledWrapper.js b/packages/bruno-app/src/components/ColorPicker/StyledWrapper.js
new file mode 100644
index 000000000..3f1fc4bc0
--- /dev/null
+++ b/packages/bruno-app/src/components/ColorPicker/StyledWrapper.js
@@ -0,0 +1,7 @@
+import styled from 'styled-components';
+
+const StyledWrapper = styled.div`
+
+`;
+
+export default StyledWrapper;
diff --git a/packages/bruno-app/src/components/ColorPicker/index.js b/packages/bruno-app/src/components/ColorPicker/index.js
new file mode 100644
index 000000000..b2cd69198
--- /dev/null
+++ b/packages/bruno-app/src/components/ColorPicker/index.js
@@ -0,0 +1,164 @@
+import React, { useState, useEffect, useRef } from 'react';
+import { IconBan, IconBrush } from '@tabler/icons';
+import Dropdown from 'components/Dropdown';
+import ColorBadge from 'components/ColorBadge';
+import StyledWrapper from './StyledWrapper';
+import { parseToRgb, toColorString } from 'polished';
+import ColorRangePicker from 'components/ColorRange/index';
+
+const PRESET_COLORS = [
+ '#CE4F3B',
+ '#2E8A54',
+ '#346AB2',
+ '#C77A0F',
+ '#B83D7F',
+ '#8D44B2'
+];
+
+const COLOR_RANGE_SEQUENCE = ['#D85D43', '#F4BB74', '#61DCB1', '#7EBDF2', '#D48ADE', '#B491E5'];
+
+/**
+ * @param {string} hex
+ * @returns {red:string,green:string,blue:string}
+ */
+const hexToRgb = (hex) => {
+ try {
+ return parseToRgb(hex);
+ } catch (err) {
+ return { red: 0, green: 0, blue: 0 };
+ }
+};
+
+const rgbToHex = (r, g, b) => {
+ return toColorString({ red: Math.round(r), green: Math.round(g), blue: Math.round(b) });
+};
+
+const interpolateColor = (position) => {
+ const numColors = COLOR_RANGE_SEQUENCE.length;
+ const scaledPos = (position / 100) * (numColors - 1);
+ const index = Math.floor(scaledPos);
+ const fraction = scaledPos - index;
+
+ if (index >= numColors - 1) {
+ return COLOR_RANGE_SEQUENCE[numColors - 1];
+ }
+
+ const color1 = hexToRgb(COLOR_RANGE_SEQUENCE[index]);
+ const color2 = hexToRgb(COLOR_RANGE_SEQUENCE[index + 1]);
+
+ const r = color1.red + (color2.red - color1.red) * fraction;
+ const g = color1.green + (color2.green - color1.green) * fraction;
+ const b = color1.blue + (color2.blue - color1.blue) * fraction;
+
+ return rgbToHex(r, g, b);
+};
+
+const findClosestPosition = (hex) => {
+ if (!hex) return 0;
+ const target = hexToRgb(hex);
+ let closestPos = 0;
+ let minDistance = Infinity;
+
+ for (let pos = 0; pos <= 100; pos++) {
+ const color = hexToRgb(interpolateColor(pos));
+ const distance = Math.sqrt(
+ Math.pow(target.red - color.red, 2) + Math.pow(target.green - color.green, 2) + Math.pow(target.blue - color.blue, 2)
+ );
+ if (distance < minDistance) {
+ minDistance = distance;
+ closestPos = pos;
+ }
+ }
+ return closestPos;
+};
+
+const ColorPickerIcon = ({ color }) => {
+ if (color) {
+ return ;
+ }
+ return ;
+};
+
+const ColorPicker = ({ color, onChange, icon }) => {
+ const [sliderPosition, setSliderPosition] = useState(() =>
+ color && !PRESET_COLORS.includes(color) ? findClosestPosition(color) : 0
+ );
+ const [customColor, setCustomColor] = useState(() =>
+ color && !PRESET_COLORS.includes(color) ? color : COLOR_RANGE_SEQUENCE[0]
+ );
+ const pendingColorRef = useRef(customColor);
+
+ const handleColorSelect = (selectedColor) => {
+ onChange(selectedColor);
+ };
+
+ const handleSliderChange = (e) => {
+ const newPosition = parseInt(e.target.value, 10);
+ setSliderPosition(newPosition);
+ const newColor = interpolateColor(newPosition);
+ setCustomColor(newColor);
+ pendingColorRef.current = newColor;
+ };
+
+ const handleSliderEnd = () => {
+ onChange(pendingColorRef.current);
+ };
+
+ const defaultIcon = (
+
+
+
+ );
+
+ const colorPickerContent = (
+
+
+
+
handleColorSelect(null)}
+ title="No color"
+ >
+
+
+ {PRESET_COLORS.map((presetColor, index) => (
+
handleColorSelect(presetColor)}
+ title={presetColor}
+ />
+ ))}
+
+
+
+
handleColorSelect(customColor)}
+ title="Custom color"
+ />
+
+
+
+
+ );
+
+ return (
+
+ {colorPickerContent}
+
+ );
+};
+
+export default ColorPicker;
diff --git a/packages/bruno-app/src/components/ColorRange/StyledWrapper.js b/packages/bruno-app/src/components/ColorRange/StyledWrapper.js
new file mode 100644
index 000000000..7d5addb9a
--- /dev/null
+++ b/packages/bruno-app/src/components/ColorRange/StyledWrapper.js
@@ -0,0 +1,45 @@
+import styled from 'styled-components';
+
+const StyledWrapper = styled.div`
+ .hue-slider {
+ -webkit-appearance: none;
+ appearance: none;
+ height: 4px;
+ border-radius: 2px;
+ outline: none;
+ }
+
+ .hue-slider::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 14px;
+ height: 14px;
+ border-radius: 50%;
+ background: ${(props) => props.color ?? props.theme.bg};
+ border: none;
+ cursor: pointer;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
+ transition: transform 0.1s ease;
+ }
+
+ .hue-slider::-webkit-slider-thumb:hover {
+ transform: scale(1.1);
+ }
+
+ .hue-slider::-moz-range-thumb {
+ width: 14px;
+ height: 14px;
+ border-radius: 50%;
+ background: ${(props) => props.color ?? props.theme.bg};
+ border: none;
+ cursor: pointer;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
+ transition: transform 0.1s ease;
+ }
+
+ .hue-slider::-moz-range-thumb:hover {
+ transform: scale(1.1);
+ }
+`;
+
+export default StyledWrapper;
diff --git a/packages/bruno-app/src/components/ColorRange/index.js b/packages/bruno-app/src/components/ColorRange/index.js
new file mode 100644
index 000000000..09d14f50c
--- /dev/null
+++ b/packages/bruno-app/src/components/ColorRange/index.js
@@ -0,0 +1,23 @@
+import StyledWrapper from './StyledWrapper';
+
+const ColorRangePicker = ({ selectedColor, className, value, onChange, colorRange, ...props }) => {
+ return (
+
+
+
+ );
+};
+
+export default ColorRangePicker;
diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSelector/EnvironmentListContent/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSelector/EnvironmentListContent/index.js
index 217670a58..5685ddee8 100644
--- a/packages/bruno-app/src/components/Environments/EnvironmentSelector/EnvironmentListContent/index.js
+++ b/packages/bruno-app/src/components/Environments/EnvironmentSelector/EnvironmentListContent/index.js
@@ -1,6 +1,7 @@
import React from 'react';
-import { IconPlus, IconDownload, IconSettings, IconDatabase } from '@tabler/icons';
+import { IconPlus, IconDownload, IconSettings } from '@tabler/icons';
import ToolHint from 'components/ToolHint';
+import ColorBadge from 'components/ColorBadge';
const EnvironmentListContent = ({
environments,
@@ -38,7 +39,7 @@ const EnvironmentListContent = ({
data-tooltip-content={env.name}
data-tooltip-hidden={env.name?.length < 90}
>
-
+
{env.name}
))}
diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSelector/StyledWrapper.js b/packages/bruno-app/src/components/Environments/EnvironmentSelector/StyledWrapper.js
index 0bfb7befe..082c7a50f 100644
--- a/packages/bruno-app/src/components/Environments/EnvironmentSelector/StyledWrapper.js
+++ b/packages/bruno-app/src/components/Environments/EnvironmentSelector/StyledWrapper.js
@@ -5,8 +5,8 @@ const Wrapper = styled.div`
border-radius: ${(props) => props.theme.border.radius.base};
padding: 0.25rem 0.3rem 0.25rem 0.5rem;
user-select: none;
- background-color: ${(props) => props.color ? undefined : 'transparent'};
- border: 2px solid ${(props) => props.color ?? props.theme.dropdown.selectedColor};
+ background-color: ${(props) => props.theme.app.collection.toolbar.environmentSelector.bg};
+ border: 1px solid ${(props) => props.theme.app.collection.toolbar.environmentSelector.border};
line-height: 1rem;
transition: all 0.15s ease;
@@ -15,11 +15,6 @@ const Wrapper = styled.div`
background-color: ${(props) => props.theme.app.collection.toolbar.environmentSelector.hoverBg};
}
- .active-env-toolhint {
- display: flex;
- flex-direction: row;
- }
-
.caret {
margin-left: 0.25rem;
color: ${(props) => props.theme.app.collection.toolbar.environmentSelector.caret};
@@ -29,18 +24,16 @@ const Wrapper = styled.div`
.env-icon {
margin-right: 0.25rem;
- color: ${(props) => props.color ?? props.theme.dropdown.selectedColor};
+ color: ${(props) => props.theme.app.collection.toolbar.environmentSelector.icon};
}
.env-text {
- color: ${(props) => props.color ?? props.theme.dropdown.selectedColor};
- font-size: ${(props) => props.theme.font.size.base};
+ color: ${(props) => props.theme.app.collection.toolbar.environmentSelector.text};
display: block;
}
.env-separator {
- color: ${(props) => props.theme.app.collection.toolbar.environmentSelector.separator};
- margin: 0 0.35rem;
+ background-color: ${(props) => props.theme.app.collection.toolbar.environmentSelector.separator};
}
.env-text-inactive {
@@ -71,37 +64,6 @@ const Wrapper = styled.div`
overflow: hidden;
}
- .tippy-box .tippy-content {
- padding: 0;
- display: flex;
- flex-direction: column;
- height: 100%;
-
- .dropdown-item {
- display: flex;
- flex-direction: row;
- align-items: center;
- column-gap: 0.35em;
- padding: 0.35rem 0.6rem;
- cursor: pointer;
- font-size: ${(props) => props.theme.font.size.base};
- color: ${(props) => props.theme.dropdown.primaryText};
-
- &:hover:not(:disabled) {
- background-color: ${(props) => props.theme.dropdown.hoverBg};
- }
-
- &.active {
- background-color: ${(props) => props.theme.dropdown.selectedBg};
- color: ${(props) => props.color ?? props.theme.dropdown.selectedColor} !important;
- }
-
- &.no-environment {
- color: ${(props) => props.theme.dropdown.mutedText};
- }
- }
- }
-
.configure-button {
position: absolute;
bottom: 0;
diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSelector/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSelector/index.js
index 0ede0ae96..3fc299f72 100644
--- a/packages/bruno-app/src/components/Environments/EnvironmentSelector/index.js
+++ b/packages/bruno-app/src/components/Environments/EnvironmentSelector/index.js
@@ -13,6 +13,166 @@ import ImportEnvironmentModal from 'components/Environments/Common/ImportEnviron
import CreateGlobalEnvironment from 'components/WorkspaceHome/WorkspaceEnvironments/CreateEnvironment';
import ToolHint from 'components/ToolHint';
import StyledWrapper from './StyledWrapper';
+import { transparentize, toColorString, parseToRgb } from 'polished';
+
+const TABS = [
+ { id: 'collection', label: 'Collection', icon:
},
+ { id: 'global', label: 'Global', icon:
}
+];
+
+const EMPTY_STATE_DESCRIPTIONS = {
+ collection: 'Create your first environment to begin working with your collection.',
+ global: 'Create your first global environment to begin working across collections.'
+};
+
+/**
+ * Generates background color with transparency for environment badges
+ */
+const getEnvBackgroundColor = (color) => (color ? transparentize(1 - 0.12, color) : 'transparent');
+
+/**
+ * Calculates the style for an environment badge section
+ */
+const getEnvBadgeStyle = (environment, position, hasOtherEnv) => {
+ const color = environment?.color;
+ const isLeft = position === 'left';
+
+ // Determine border radius based on position and whether other env exists
+ let borderRadius = '0.3rem';
+ if (hasOtherEnv) {
+ borderRadius = isLeft ? '0.3rem 0 0 0.3rem' : '0 0.3rem 0.3rem 0';
+ }
+
+ // Determine padding based on position
+ const padding = isLeft
+ ? hasOtherEnv
+ ? '0.25rem 0.5rem 0.25rem 0.5rem'
+ : '0.25rem 0.3rem 0.25rem 0.5rem'
+ : '0.25rem 0.3rem 0.25rem 0.5rem';
+
+ return {
+ backgroundColor: getEnvBackgroundColor(color),
+ padding,
+ borderRadius
+ };
+};
+
+/**
+ * Calculates dropdown width based on longest environment name
+ */
+const calculateDropdownWidth = (environments, globalEnvironments) => {
+ const allEnvironments = [...environments, ...globalEnvironments];
+ if (allEnvironments.length === 0) return 0;
+
+ const maxCharLength = Math.max(...allEnvironments.map((env) => env.name?.length || 0));
+ // 8 pixels per character (rough estimate for average character width)
+ return maxCharLength * 8;
+};
+
+/**
+ * Displays a single environment with icon, name, and optional color styling
+ */
+const EnvironmentBadge = ({ environment, icon: Icon }) => {
+ if (!environment) return null;
+
+ const colorStyle = environment.color ? { color: environment.color } : {};
+
+ return (
+ <>
+
+
+
+ {environment.name}
+
+
+ >
+ );
+};
+
+/**
+ * Dropdown trigger component showing active environments
+ */
+const DropdownTrigger = forwardRef(({ collectionEnv, globalEnv }, ref) => {
+ const hasAnyEnv = collectionEnv || globalEnv;
+
+ // Empty state - no environments selected
+ if (!hasAnyEnv) {
+ return (
+
+ No Environment
+
+
+ );
+ }
+
+ // Only collection env selected - caret goes with collection env
+ if (collectionEnv && !globalEnv) {
+ return (
+
+ );
+ }
+
+ // Only global env selected - caret goes with global env
+ if (!collectionEnv && globalEnv) {
+ return (
+
+ );
+ }
+
+ // Both environments selected
+ return (
+
+ {/* Collection Environment Section */}
+
+
+
+
+ {/* Separator */}
+
+
+ {/* Global Environment Section + Caret */}
+
+
+
+
+
+ );
+});
const EnvironmentSelector = ({ collection }) => {
const dispatch = useDispatch();
@@ -35,160 +195,82 @@ const EnvironmentSelector = ({ collection }) => {
? find(environments, (e) => e.uid === activeEnvironmentUid)
: null;
- const tabs = [
- { id: 'collection', label: 'Collection', icon:
},
- { id: 'global', label: 'Global', icon:
}
- ];
+ const dropdownWidth = useMemo(
+ () => calculateDropdownWidth(environments, globalEnvironments),
+ [environments, globalEnvironments]
+ );
- const onDropdownCreate = (ref) => {
- dropdownTippyRef.current = ref;
- };
+ const description = EMPTY_STATE_DESCRIPTIONS[activeTab];
- // Get description based on active tab
- const description
- = activeTab === 'collection'
- ? 'Create your first environment to begin working with your collection.'
- : 'Create your first global environment to begin working across collections.';
+ const hideDropdown = () => dropdownTippyRef.current?.hide();
- // Environment selection handler
const handleEnvironmentSelect = (environment) => {
const action
= activeTab === 'collection'
- ? selectEnvironment(environment ? environment.uid : null, collection.uid)
- : selectGlobalEnvironment({ environmentUid: environment ? environment.uid : null });
+ ? selectEnvironment(environment?.uid || null, collection.uid)
+ : selectGlobalEnvironment({ environmentUid: environment?.uid || null });
dispatch(action)
.then(() => {
- if (environment) {
- toast.success(`Environment changed to ${environment.name}`);
- } else {
- toast.success('No Environments are active now');
- }
- dropdownTippyRef.current.hide();
+ toast.success(environment ? `Environment changed to ${environment.name}` : 'No Environments are active now');
+ hideDropdown();
})
- .catch((err) => {
+ .catch(() => {
toast.error('An error occurred while selecting the environment');
});
};
- // Settings handler - opens environment settings tab
const handleSettingsClick = () => {
- if (activeTab === 'collection') {
- dispatch(
- addTab({
- uid: `${collection.uid}-environment-settings`,
- collectionUid: collection.uid,
- type: 'environment-settings'
- })
- );
- } else {
- dispatch(
- addTab({
- uid: `${collection.uid}-global-environment-settings`,
- collectionUid: collection.uid,
- type: 'global-environment-settings'
- })
- );
- }
- dropdownTippyRef.current.hide();
+ const isCollection = activeTab === 'collection';
+ dispatch(
+ addTab({
+ uid: `${collection.uid}-${isCollection ? 'environment' : 'global-environment'}-settings`,
+ collectionUid: collection.uid,
+ type: isCollection ? 'environment-settings' : 'global-environment-settings'
+ })
+ );
+ hideDropdown();
};
- // Create handler
const handleCreateClick = () => {
if (activeTab === 'collection') {
setShowCreateCollectionModal(true);
} else {
setShowCreateGlobalModal(true);
}
- dropdownTippyRef.current.hide();
+ hideDropdown();
};
- // Import handler
const handleImportClick = () => {
if (activeTab === 'collection') {
setShowImportCollectionModal(true);
} else {
setShowImportGlobalModal(true);
}
- dropdownTippyRef.current.hide();
+ hideDropdown();
};
- // Calculate dropdown width based on the longest environment name.
- // To prevent resizing while switching between collection and global environments.
- const dropdownWidth = useMemo(() => {
- const allEnvironments = [...environments, ...globalEnvironments];
- if (allEnvironments.length === 0) return 0;
-
- const maxCharLength = Math.max(...allEnvironments.map((env) => env.name?.length || 0));
- // 8 pixels per character: This is a rough estimate for the average character width in most fonts
- // (monospace fonts are typically 8-10px, proportional fonts vary but 8px is a safe average)
- return maxCharLength * 8;
- }, [environments, globalEnvironments]);
-
- // Create icon component for dropdown trigger
- const Icon = forwardRef((props, ref) => {
- const hasAnyEnv = activeGlobalEnvironment || activeCollectionEnvironment;
-
- const displayContent = hasAnyEnv ? (
- <>
- {activeCollectionEnvironment && (
- <>
-
-
-
- {activeCollectionEnvironment.name}
-
-
- {activeGlobalEnvironment &&
|}
- >
- )}
- {activeGlobalEnvironment && (
-
-
-
- {activeGlobalEnvironment.name}
-
-
- )}
- >
- ) : (
-
No Environment
+ const openEnvironmentSettingsTab = (type) => {
+ dispatch(
+ addTab({
+ uid: `${collection.uid}-${type}-settings`,
+ collectionUid: collection.uid,
+ type: `${type}-settings`
+ })
);
-
- return (
-
- {displayContent}
-
-
- );
- });
+ };
return (
-
+
-
} placement="bottom-end">
+
(dropdownTippyRef.current = ref)}
+ icon={}
+ placement="bottom-end"
+ >
{/* Tab Headers */}
- {tabs.map((tab) => (
+ {TABS.map((tab) => (
-
-
+
+
+
);
};
diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js
index 920386fa1..542b93f5f 100644
--- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js
+++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js
@@ -110,6 +110,7 @@ const StyledWrapper = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
+ gap: 8px;
padding: 4px 8px;
margin-bottom: 1px;
font-size: 13px;
diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js
index 68eb2c02e..c7f40a13f 100644
--- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js
+++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js
@@ -1,5 +1,4 @@
import React, { useEffect, useState, useRef } from 'react';
-import { findEnvironmentInCollection, findItem } from 'utils/collections';
import usePrevious from 'hooks/usePrevious';
import EnvironmentDetails from './EnvironmentDetails';
import CreateEnvironment from 'components/Environments/EnvironmentSettings/CreateEnvironment';
@@ -7,6 +6,7 @@ import { IconDownload, IconUpload, IconSearch, IconPlus, IconCheck, IconX } from
import StyledWrapper from './StyledWrapper';
import ConfirmSwitchEnv from 'components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/ConfirmSwitchEnv';
import ImportEnvironmentModal from 'components/Environments/Common/ImportEnvironmentModal';
+import ColorBadge from 'components/ColorBadge';
import { isEqual } from 'lodash';
import { useDispatch } from 'react-redux';
import { addEnvironment, renameEnvironment, selectEnvironment } from 'providers/ReduxStore/slices/collections/actions';
@@ -25,10 +25,6 @@ const EnvironmentList = ({
}) => {
const dispatch = useDispatch();
-
-const EnvironmentList = ({ collection, isModified, setIsModified, onClose, setShowExportModal }) => {
- const { environments, activeEnvironmentUid } = collection;
- const [selectedEnvironment, setSelectedEnvironment] = useState(null);
const [openCreateModal, setOpenCreateModal] = useState(false);
const [openImportModal, setOpenImportModal] = useState(false);
const [searchText, setSearchText] = useState('');
@@ -43,7 +39,7 @@ const EnvironmentList = ({ collection, isModified, setIsModified, onClose, setSh
const [switchEnvConfirmClose, setSwitchEnvConfirmClose] = useState(false);
const [originalEnvironmentVariables, setOriginalEnvironmentVariables] = useState([]);
- const envUids = environments?.map((env) => env.uid) ?? [];
+ const envUids = environments ? environments.map((env) => env.uid) : [];
const prevEnvUids = usePrevious(envUids);
useEffect(() => {
@@ -68,12 +64,10 @@ const EnvironmentList = ({ collection, isModified, setIsModified, onClose, setSh
if (hasSelectedEnvironmentChanged || selectedEnvironment.uid !== _selectedEnvironment?.uid) {
setSelectedEnvironment(_selectedEnvironment);
}
- setOriginalEnvironmentVariables(selectedEnvironment?.variables||[]);
- setSelectedEnvironment(findItem(environments, selectedEnvironment.uid));
+ setOriginalEnvironmentVariables(_selectedEnvironment?.variables || []);
return;
}
-
const environment = environments?.find((env) => env.uid === activeEnvironmentUid) || environments?.[0];
setSelectedEnvironment(environment);
@@ -81,21 +75,15 @@ const EnvironmentList = ({ collection, isModified, setIsModified, onClose, setSh
}, [environments, activeEnvironmentUid, selectedEnvironment]);
useEffect(() => {
- if (selectedEnvironment) {
- setSelectedEnvironment(findEnvironmentInCollection(collection, selectedEnvironment.uid));
- }
- }, [environments]);
-
- useEffect(() => {
- if (prevEnvUids?.length && envUids.length > prevEnvUids.length) {
+ if (prevEnvUids && prevEnvUids.length && envUids.length > prevEnvUids.length) {
const newEnv = environments.find((env) => !prevEnvUids.includes(env.uid));
if (newEnv) {
setSelectedEnvironment(newEnv);
}
}
- if (prevEnvUids?.length && envUids.length < prevEnvUids.length) {
- setSelectedEnvironment(environments?.length ? environments[0] : null);
+ if (prevEnvUids && prevEnvUids.length && envUids.length < prevEnvUids.length) {
+ setSelectedEnvironment(environments && environments.length ? environments[0] : null);
}
}, [envUids, environments, prevEnvUids]);
@@ -380,6 +368,7 @@ const EnvironmentList = ({ collection, isModified, setIsModified, onClose, setSh
) : (
<>
+
{env.name}
{activeEnvironmentUid === env.uid ? (
diff --git a/packages/bruno-app/src/components/RequestTabs/StyledWrapper.js b/packages/bruno-app/src/components/RequestTabs/StyledWrapper.js
index 28fc42862..ee8197ede 100644
--- a/packages/bruno-app/src/components/RequestTabs/StyledWrapper.js
+++ b/packages/bruno-app/src/components/RequestTabs/StyledWrapper.js
@@ -71,8 +71,9 @@ const Wrapper = styled.div`
&:not(.active) {
background: ${(props) => props.theme.requestTabs.bg};
- border-bottom: 3px solid ${(props) => props.color ?? "transparent"};
+ border-color: transparent;
border-radius: ${(props) => props.theme.border.radius.base};
+
}
&:nth-last-child(1) {
@@ -112,7 +113,7 @@ const Wrapper = styled.div`
&.active {
background: ${(props) => props.theme.bg || '#ffffff'};
border: 1px solid ${(props) => props.theme.requestTabs.bottomBorder};
- border-bottom-color: ${(props) => props.color ?? props.theme.bg ?? '#ffffff'};
+ border-bottom-color: ${(props) => props.theme.bg || '#ffffff'};
border-radius: 8px 8px 0 0;
z-index: 1;
margin-bottom: -2px;
diff --git a/packages/bruno-app/src/components/RequestTabs/index.js b/packages/bruno-app/src/components/RequestTabs/index.js
index ae97c3479..b10552008 100644
--- a/packages/bruno-app/src/components/RequestTabs/index.js
+++ b/packages/bruno-app/src/components/RequestTabs/index.js
@@ -6,7 +6,6 @@ import { IconChevronRight, IconChevronLeft } from '@tabler/icons';
import { useSelector, useDispatch } from 'react-redux';
import { focusTab, reorderTabs } from 'providers/ReduxStore/slices/tabs';
import NewRequest from 'components/Sidebar/NewRequest';
-import { findEnvironmentInCollection } from 'utils/collections';
import CollectionToolBar from './CollectionToolBar';
import RequestTab from './RequestTab';
import StyledWrapper from './StyledWrapper';
@@ -86,17 +85,6 @@ const RequestTabs = () => {
return null;
}
- const activeTab = find(tabs, (t) => t.uid === activeTabUid);
- if (!activeTab) {
- return Something went wrong!;
- }
-
- const activeCollection = find(collections, (c) => c.uid === activeTab.collectionUid);
- const activeEnvironment = activeCollection
- ? findEnvironmentInCollection(activeCollection, activeCollection.activeEnvironmentUid)
- : null;
- const collectionRequestTabs = filter(tabs, (t) => t.collectionUid === activeTab.collectionUid);
-
const effectiveSidebarWidth = sidebarCollapsed ? 0 : leftSidebarWidth;
const maxTablistWidth = screenWidth - effectiveSidebarWidth - 150;
@@ -114,14 +102,9 @@ const RequestTabs = () => {
});
};
- const getRootClassname = () => {
- return classnames({
- 'has-chevrons': showChevrons
- });
- };
// Todo: Must support ephemeral requests
return (
-
+
{newRequestModalOpen && (
setNewRequestModalOpen(false)} />
)}
diff --git a/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/index.js b/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/index.js
index 362e33ac6..9f7d2705c 100644
--- a/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/index.js
+++ b/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/index.js
@@ -1,12 +1,13 @@
import { IconCopy, IconEdit, IconTrash, IconCheck, IconX } from '@tabler/icons';
import { useState, useRef } from 'react';
import { useDispatch, useSelector } from 'react-redux';
-import { renameGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
+import { renameGlobalEnvironment, updateGlobalEnvironmentColor } from 'providers/ReduxStore/slices/global-environments';
import { validateName, validateNameError } from 'utils/common/regex';
import toast from 'react-hot-toast';
import CopyEnvironment from '../../CopyEnvironment';
import DeleteEnvironment from '../../DeleteEnvironment';
import EnvironmentVariables from './EnvironmentVariables';
+import ColorPicker from 'components/ColorPicker';
import StyledWrapper from './StyledWrapper';
const EnvironmentDetails = ({ environment, setIsModified, collection }) => {
@@ -110,6 +111,16 @@ const EnvironmentDetails = ({ environment, setIsModified, collection }) => {
}
};
+ const handleColorChange = (color) => {
+ dispatch(updateGlobalEnvironmentColor(environment.uid, color))
+ .then(() => {
+ toast.success('Environment color updated!');
+ })
+ .catch(() => {
+ toast.error('An error occurred while updating the environment color');
+ });
+ };
+
return (
{openDeleteModal && (
@@ -159,7 +170,10 @@ const EnvironmentDetails = ({ environment, setIsModified, collection }) => {
>
) : (
- {environment.name}
+
+
{environment.name}
+
+
)}
{nameError && isRenaming && {nameError}
}
diff --git a/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/StyledWrapper.js b/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/StyledWrapper.js
index ead76685a..cbf76f605 100644
--- a/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/StyledWrapper.js
+++ b/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/StyledWrapper.js
@@ -110,6 +110,7 @@ const StyledWrapper = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
+ gap: 8px;
padding: 4px 8px;
margin-bottom: 1px;
font-size: 13px;
diff --git a/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/index.js b/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/index.js
index 7ce4c66ef..ed42867c7 100644
--- a/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/index.js
+++ b/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/index.js
@@ -6,6 +6,7 @@ import { IconDownload, IconUpload, IconSearch, IconPlus, IconCheck, IconX } from
import StyledWrapper from './StyledWrapper';
import ConfirmSwitchEnv from './ConfirmSwitchEnv';
import ImportEnvironmentModal from 'components/Environments/Common/ImportEnvironmentModal';
+import ColorBadge from 'components/ColorBadge';
import { isEqual } from 'lodash';
import { useDispatch, useSelector } from 'react-redux';
import { addGlobalEnvironment, renameGlobalEnvironment, selectGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
@@ -357,6 +358,7 @@ const EnvironmentList = ({ environments, activeEnvironmentUid, selectedEnvironme
) : (
<>
+
{env.name}
{activeEnvironmentUid === env.uid ? (
diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
index da06e38c4..f3f3cfef0 100644
--- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
+++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
@@ -36,7 +36,6 @@ import {
sortCollections as _sortCollections,
updateCollectionMountStatus,
moveCollection,
- saveEnvironmentColor as _saveEnvironmentColor,
workspaceEnvUpdateEvent,
requestCancelled,
resetRunResults,
@@ -50,6 +49,7 @@ import {
updateActiveConnections,
saveRequest as _saveRequest,
saveEnvironment as _saveEnvironment,
+ updateEnvironmentColor as _updateEnvironmentColor,
saveCollectionDraft,
saveFolderDraft,
addVar,
@@ -1931,6 +1931,31 @@ export const saveEnvironment = (variables, environmentUid, collectionUid) => (di
});
};
+export const updateEnvironmentColor = (environmentUid, color, collectionUid) => (dispatch, getState) => {
+ return new Promise((resolve, reject) => {
+ const state = getState();
+ const collection = findCollectionByUid(state.collections.collections, collectionUid);
+ if (!collection) {
+ return reject(new Error('Collection not found'));
+ }
+
+ const collectionCopy = cloneDeep(collection);
+ const environment = findEnvironmentInCollection(collectionCopy, environmentUid);
+ if (!environment) {
+ return reject(new Error('Environment not found'));
+ }
+
+ environment.color = color;
+ const { ipcRenderer } = window;
+ ipcRenderer.invoke('renderer:update-environment-color', collection.pathname, environment.name, color)
+ .then(() => {
+ dispatch(_updateEnvironmentColor({ environmentUid, color, collectionUid }));
+ resolve();
+ })
+ .catch(reject);
+ });
+};
+
/**
* Update a variable value directly in the file without affecting draft state
* @param {string} pathname - File path
@@ -2253,27 +2278,6 @@ export const mergeAndPersistEnvironment
});
};
-export const saveEnvironmentColor = (color, environmentUid, collectionUid) => (dispatch, getState) => {
- return new Promise((resolve, reject) => {
- const collection =
- findCollectionByUid(getState().collections.collections, collectionUid) ??
- reject(new Error('Collection not found'));
- const environment =
- findEnvironmentInCollection(collection, environmentUid) ?? reject(new Error('Environment not found'));
- const updatedEnvironment = { ...environment, color: color };
-
- const { ipcRenderer } = window;
- environmentSchema
- .validate(updatedEnvironment)
- // save to file
- .then(() => ipcRenderer.invoke('renderer:save-environment', collection.pathname, updatedEnvironment))
- // update store
- .then(() => dispatch(_saveEnvironmentColor({ color, environmentUid, collectionUid })))
- .then(resolve)
- .catch(reject);
- });
-};
-
export const selectEnvironment = (environmentUid, collectionUid) => (dispatch, getState) => {
return new Promise((resolve, reject) => {
const state = getState();
diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
index 37bd9cb03..efec9f44a 100644
--- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
+++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
@@ -258,17 +258,6 @@ export const collectionsSlice = createSlice({
if (environment) {
environment.variables = variables;
- environment.color = color;
- }
- }
- },
- saveEnvironmentColor: (state, action) => {
- const { color, environmentUid, collectionUid } = action.payload;
- const collection = findCollectionByUid(state.collections, collectionUid);
- if (collection) {
- const environment = findEnvironmentInCollection(collection, environmentUid);
- if (environment) {
- environment.color = color;
}
}
},
@@ -288,6 +277,18 @@ export const collectionsSlice = createSlice({
}
}
},
+ updateEnvironmentColor: (state, action) => {
+ const { environmentUid, color, collectionUid } = action.payload;
+ const collection = findCollectionByUid(state.collections, collectionUid);
+
+ if (collection) {
+ const environment = findEnvironmentInCollection(collection, environmentUid);
+
+ if (environment) {
+ environment.color = color;
+ }
+ }
+ },
newItem: (state, action) => {
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
@@ -3475,8 +3476,8 @@ export const {
updatedFolderSettingsSelectedTab,
collectionUnlinkEnvFileEvent,
saveEnvironment,
- saveEnvironmentColor,
selectEnvironment,
+ updateEnvironmentColor,
newItem,
deleteItem,
renameItem,
diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/global-environments.js b/packages/bruno-app/src/providers/ReduxStore/slices/global-environments.js
index 028c0db29..5eb823ada 100644
--- a/packages/bruno-app/src/providers/ReduxStore/slices/global-environments.js
+++ b/packages/bruno-app/src/providers/ReduxStore/slices/global-environments.js
@@ -81,6 +81,12 @@ export const globalEnvironmentsSlice = createSlice({
},
clearGlobalEnvironmentDraft: (state) => {
state.globalEnvironmentDraft = null;
+ },
+ _updateGlobalEnvironmentColor: (state, action) => {
+ const { environmentUid, color } = action.payload;
+ if (environmentUid) {
+ state.globalEnvironments = state.globalEnvironments.map((env) => env?.uid == environmentUid ? { ...env, color } : env);
+ }
}
}
});
@@ -93,6 +99,7 @@ export const {
_copyGlobalEnvironment,
_selectGlobalEnvironment,
_deleteGlobalEnvironment,
+ _updateGlobalEnvironmentColor,
setGlobalEnvironmentDraft,
clearGlobalEnvironmentDraft
} = globalEnvironmentsSlice.actions;
@@ -303,4 +310,16 @@ export const globalEnvironmentsUpdateEvent = ({ globalEnvironmentVariables }) =>
});
};
+export const updateGlobalEnvironmentColor = (environmentUid, color) => (dispatch, getState) => {
+ return new Promise((resolve, reject) => {
+ const { ipcRenderer } = window;
+ const state = getState();
+ const { workspaceUid, workspacePath } = getWorkspaceContext(state);
+ ipcRenderer.invoke('renderer:update-global-environment-color', { environmentUid, color, workspaceUid, workspacePath })
+ .then(() => dispatch(_updateGlobalEnvironmentColor({ environmentUid, color })))
+ .then(resolve)
+ .catch(reject);
+ });
+};
+
export default globalEnvironmentsSlice.reducer;
diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js
index eb465d8a1..e72e41195 100644
--- a/packages/bruno-electron/src/ipc/collection.js
+++ b/packages/bruno-electron/src/ipc/collection.js
@@ -620,6 +620,28 @@ const registerRendererEventHandlers = (mainWindow, watcher) => {
}
});
+ // update environment color
+ ipcMain.handle('renderer:update-environment-color', async (event, collectionPathname, environmentName, color) => {
+ try {
+ const format = getCollectionFormat(collectionPathname);
+ const envDirPath = path.join(collectionPathname, 'environments');
+ const envFilePath = path.join(envDirPath, `${environmentName}.${format}`);
+
+ if (!fs.existsSync(envFilePath)) {
+ throw new Error(`environment: ${envFilePath} does not exist`);
+ }
+
+ // Read, update color, and write back to file
+ const fileContent = fs.readFileSync(envFilePath, 'utf8');
+ const environment = parseEnvironment(fileContent, { format });
+ environment.color = color;
+ const updatedContent = stringifyEnvironment(environment, { format });
+ fs.writeFileSync(envFilePath, updatedContent, 'utf8');
+ } catch (error) {
+ return Promise.reject(error);
+ }
+ });
+
// Generic environment export handler
ipcMain.handle('renderer:export-environment', async (event, { environments, environmentType, filePath, exportFormat = 'folder' }) => {
try {
diff --git a/packages/bruno-electron/src/ipc/global-environments.js b/packages/bruno-electron/src/ipc/global-environments.js
index 2432e283e..432fd7330 100644
--- a/packages/bruno-electron/src/ipc/global-environments.js
+++ b/packages/bruno-electron/src/ipc/global-environments.js
@@ -99,6 +99,19 @@ const registerGlobalEnvironmentsIpc = (mainWindow, workspaceEnvironmentsManager)
return Promise.reject(error);
}
});
+
+ ipcMain.handle('renderer:update-global-environment-color', async (event, { environmentUid, color, workspacePath }) => {
+ try {
+ if (workspacePath && workspaceEnvironmentsManager) {
+ return await workspaceEnvironmentsManager.updateGlobalEnvironmentColorByPath(workspacePath, { environmentUid, color });
+ }
+
+ globalEnvironmentsStore.updateGlobalEnvironmentColor({ environmentUid, color });
+ } catch (error) {
+ console.error('Error in renderer:update-global-environment-color:', error);
+ return Promise.reject(error);
+ }
+ });
};
module.exports = registerGlobalEnvironmentsIpc;
diff --git a/packages/bruno-electron/src/store/global-environments.js b/packages/bruno-electron/src/store/global-environments.js
index 0bdeb6957..084803af3 100644
--- a/packages/bruno-electron/src/store/global-environments.js
+++ b/packages/bruno-electron/src/store/global-environments.js
@@ -162,6 +162,15 @@ class GlobalEnvironmentsStore {
}
this.setGlobalEnvironments(globalEnvironments);
}
+
+ updateGlobalEnvironmentColor({ environmentUid, color }) {
+ let globalEnvironments = this.getGlobalEnvironments();
+ const environment = globalEnvironments.find((env) => env?.uid == environmentUid);
+ if (environment) {
+ environment.color = color;
+ }
+ this.setGlobalEnvironments(globalEnvironments);
+ }
}
const globalEnvironmentsStore = new GlobalEnvironmentsStore();
diff --git a/packages/bruno-electron/src/store/workspace-environments.js b/packages/bruno-electron/src/store/workspace-environments.js
index b27ad67b4..3098f2b35 100644
--- a/packages/bruno-electron/src/store/workspace-environments.js
+++ b/packages/bruno-electron/src/store/workspace-environments.js
@@ -325,6 +325,30 @@ class GlobalEnvironmentsManager {
}
}
+ async updateGlobalEnvironmentColor(workspacePath, environmentUid, color) {
+ try {
+ if (!workspacePath) {
+ throw new Error('Workspace path is required');
+ }
+
+ const envFile = this.findEnvironmentFileByUid(workspacePath, environmentUid);
+
+ if (!envFile) {
+ throw new Error(`Environment file not found for uid: ${environmentUid}`);
+ }
+
+ const environment = await this.parseEnvironmentFile(envFile.filePath, workspacePath);
+ environment.color = color;
+
+ const content = stringifyEnvironment(environment, { format: 'yml' });
+ await writeFile(envFile.filePath, content);
+
+ return true;
+ } catch (error) {
+ throw error;
+ }
+ }
+
async getGlobalEnvironmentsByPath(workspacePath) {
return this.getGlobalEnvironments(workspacePath);
}
@@ -348,6 +372,10 @@ class GlobalEnvironmentsManager {
async selectGlobalEnvironmentByPath(workspacePath, params) {
return this.selectGlobalEnvironment(workspacePath, params);
}
+
+ async updateGlobalEnvironmentColorByPath(workspacePath, { environmentUid, color }) {
+ return this.updateGlobalEnvironmentColor(workspacePath, environmentUid, color);
+ }
}
const globalEnvironmentsManager = new GlobalEnvironmentsManager();
diff --git a/packages/bruno-filestore/src/formats/yml/parseEnvironment.ts b/packages/bruno-filestore/src/formats/yml/parseEnvironment.ts
index 5eaba6baf..92b4016b8 100644
--- a/packages/bruno-filestore/src/formats/yml/parseEnvironment.ts
+++ b/packages/bruno-filestore/src/formats/yml/parseEnvironment.ts
@@ -43,7 +43,8 @@ const parseEnvironment = (ymlString: string): BrunoEnvironment => {
const brunoEnvironment: BrunoEnvironment = {
uid: uuid(),
name: ensureString(ocEnvironment.name, 'Untitled Environment'),
- variables: toBrunoEnvironmentVariables(ocEnvironment.variables)
+ variables: toBrunoEnvironmentVariables(ocEnvironment.variables),
+ color: ocEnvironment.color || null
};
return brunoEnvironment;
diff --git a/packages/bruno-filestore/src/formats/yml/stringifyEnvironment.ts b/packages/bruno-filestore/src/formats/yml/stringifyEnvironment.ts
index 640cef6b2..2f57f4aad 100644
--- a/packages/bruno-filestore/src/formats/yml/stringifyEnvironment.ts
+++ b/packages/bruno-filestore/src/formats/yml/stringifyEnvironment.ts
@@ -47,7 +47,10 @@ const stringifyEnvironment = (environment: BrunoEnvironment): string => {
name: environment.name
};
- // Convert variables if they exist
+ if (environment.color) {
+ ocEnvironment.color = environment.color;
+ }
+
if (environment.variables?.length) {
const ocVariables = toOpenCollectionEnvironmentVariables(environment.variables);
if (ocVariables) {
diff --git a/packages/bruno-filestore/test-results/.last-run.json b/packages/bruno-filestore/test-results/.last-run.json
new file mode 100644
index 000000000..5fca3f84b
--- /dev/null
+++ b/packages/bruno-filestore/test-results/.last-run.json
@@ -0,0 +1,4 @@
+{
+ "status": "failed",
+ "failedTests": []
+}
\ No newline at end of file
diff --git a/packages/bruno-lang/v2/src/jsonToEnv.js b/packages/bruno-lang/v2/src/jsonToEnv.js
index 5fbbf18fd..09811734b 100644
--- a/packages/bruno-lang/v2/src/jsonToEnv.js
+++ b/packages/bruno-lang/v2/src/jsonToEnv.js
@@ -3,6 +3,8 @@ const { getValueString, indentString } = require('./utils');
const envToJson = (json) => {
const variables = _.get(json, 'variables', []);
+ const color = _.get(json, 'color', null);
+
const vars = variables
.filter((variable) => !variable.secret)
.map((variable) => {
@@ -20,8 +22,6 @@ const envToJson = (json) => {
return indentString(`${prefix}${name}`);
});
- const color = _.get(json, 'color', undefined);
-
let output = '';
if (!variables || !variables.length) {
diff --git a/packages/bruno-schema-types/src/collection/environment.ts b/packages/bruno-schema-types/src/collection/environment.ts
index 90fdbfaf3..ecc14b7d1 100644
--- a/packages/bruno-schema-types/src/collection/environment.ts
+++ b/packages/bruno-schema-types/src/collection/environment.ts
@@ -13,6 +13,7 @@ export interface Environment {
uid: UID;
name: string;
variables: EnvironmentVariable[];
+ color?: string | null;
}
export type Environments = Environment[];
diff --git a/packages/bruno-schema/src/collections/index.js b/packages/bruno-schema/src/collections/index.js
index 56736cfef..d17fa9f6c 100644
--- a/packages/bruno-schema/src/collections/index.js
+++ b/packages/bruno-schema/src/collections/index.js
@@ -17,7 +17,7 @@ const environmentSchema = Yup.object({
uid: uidSchema,
name: Yup.string().min(1).required('name is required'),
variables: Yup.array().of(environmentVariablesSchema).required('variables are required'),
- color: Yup.string().optional()
+ color: Yup.string().nullable().optional()
})
.noUnknown(true)
.strict();
diff --git a/tests/environments/color-picker/collection/bruno.json b/tests/environments/color-picker/collection/bruno.json
new file mode 100644
index 000000000..a2b575fc0
--- /dev/null
+++ b/tests/environments/color-picker/collection/bruno.json
@@ -0,0 +1,5 @@
+{
+ "version": "1",
+ "name": "global-env-config-selection",
+ "type": "collection"
+}
diff --git a/tests/environments/color-picker/collection/test-request.bru b/tests/environments/color-picker/collection/test-request.bru
new file mode 100644
index 000000000..ea3cdd03f
--- /dev/null
+++ b/tests/environments/color-picker/collection/test-request.bru
@@ -0,0 +1,17 @@
+meta {
+ name: test-request
+ type: http
+ seq: 1
+}
+
+get {
+ url: {{host}}/api/echo
+ body: none
+ auth: none
+}
+
+tests {
+ test("should get 200 response", function() {
+ expect(res.getStatus()).to.equal(200);
+ });
+}
diff --git a/tests/environments/color-picker/color-picker.spec.ts b/tests/environments/color-picker/color-picker.spec.ts
new file mode 100644
index 000000000..b74b105b3
--- /dev/null
+++ b/tests/environments/color-picker/color-picker.spec.ts
@@ -0,0 +1,156 @@
+import { test, expect } from '../../../playwright';
+import { closeAllCollections } from '../../utils/page/actions';
+
+const PRESET_COLORS = [
+ '#CE4F3B',
+ '#2E8A54',
+ '#346AB2',
+ '#C77A0F',
+ '#B83D7F',
+ '#8D44B2'
+];
+
+// Convert hex color to RGB format used by CSS
+const hexToRgb = (hex: string): string => {
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
+ if (!result) return '';
+ const r = parseInt(result[1], 16);
+ const g = parseInt(result[2], 16);
+ const b = parseInt(result[3], 16);
+ return `rgb(${r}, ${g}, ${b})`;
+};
+
+test.describe('Color Picker Tests', () => {
+ test.afterAll(async ({ pageWithUserData: page }) => {
+ await closeAllCollections(page);
+ });
+
+ test('should select a preset color for global environment', async ({ pageWithUserData: page }) => {
+ // Open the collection from sidebar
+ await page.locator('#sidebar-collection-name').filter({ hasText: 'global-env-config-selection' }).click();
+
+ // Open global environment configuration
+ await page.getByTestId('environment-selector-trigger').click();
+ await page.getByTestId('env-tab-global').click();
+ await page.getByText('Configure', { exact: true }).click();
+
+ // Wait for the environments tab to be visible
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Global Environments' });
+ await expect(envTab).toBeVisible();
+
+ // Click on the color picker icon (brush icon) next to the environment name
+ const colorPickerTrigger = page.locator('[title="Change color"]').first();
+ await colorPickerTrigger.click();
+
+ // Wait for the color picker dropdown to appear
+ const colorPickerDropdown = page.locator('.tippy-box');
+ await expect(colorPickerDropdown).toBeVisible();
+
+ // Select the first preset color (red) using title attribute
+ const presetColor = PRESET_COLORS[0];
+ const colorOption = colorPickerDropdown.locator(`[title="${presetColor}"]`);
+ await colorOption.click();
+
+ // Verify the color badge in the environment list shows the selected color
+ const activeEnvItem = page.locator('.environment-item.active');
+ const colorBadge = activeEnvItem.locator('.rounded-full').first();
+ await expect(colorBadge).toHaveCSS('background-color', hexToRgb(presetColor));
+ });
+
+ test('should remove color from environment', async ({ pageWithUserData: page }) => {
+ // Open global environment configuration
+ await page.getByTestId('environment-selector-trigger').click();
+ await page.getByTestId('env-tab-global').click();
+ await page.getByText('Configure', { exact: true }).click();
+
+ // Wait for the environments tab to be visible
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Global Environments' });
+ await expect(envTab).toBeVisible();
+
+ // Click on the color picker icon
+ const colorPickerTrigger = page.locator('[title="Change color"]').first();
+ await colorPickerTrigger.click();
+
+ // Wait for the color picker dropdown to appear
+ const colorPickerDropdown = page.locator('.tippy-box');
+ await expect(colorPickerDropdown).toBeVisible();
+
+ // Click the "No color" option (ban icon)
+ const noColorOption = colorPickerDropdown.locator('[title="No color"]');
+ await noColorOption.click();
+
+ // Verify the color badge becomes transparent (no color)
+ const activeEnvItem = page.locator('.environment-item.active');
+ const colorBadge = activeEnvItem.locator('.rounded-full').first();
+ await expect(colorBadge).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)');
+ });
+
+ test('should select custom color using slider', async ({ pageWithUserData: page }) => {
+ // Open global environment configuration
+ await page.getByTestId('environment-selector-trigger').click();
+ await page.getByTestId('env-tab-global').click();
+ await page.getByText('Configure', { exact: true }).click();
+
+ // Wait for the environments tab to be visible
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Global Environments' });
+ await expect(envTab).toBeVisible();
+
+ // Click on the color picker icon
+ const colorPickerTrigger = page.locator('[title="Change color"]').first();
+ await colorPickerTrigger.click();
+
+ // Wait for the color picker dropdown to appear
+ const colorPickerDropdown = page.locator('.tippy-box');
+ await expect(colorPickerDropdown).toBeVisible();
+
+ // Find the slider and change its value
+ const slider = colorPickerDropdown.locator('input[type="range"]');
+ await expect(slider).toBeVisible();
+
+ // Move slider to middle position (50%)
+ await slider.fill('50');
+
+ // Click the custom color preview to apply it
+ const customColorPreview = colorPickerDropdown.locator('[title="Custom color"]');
+ await customColorPreview.click();
+
+ // Verify the color badge has a color applied (not transparent)
+ const activeEnvItem = page.locator('.environment-item.active');
+ const colorBadge = activeEnvItem.locator('.rounded-full').first();
+ const bgColor = await colorBadge.evaluate((el) => getComputedStyle(el).backgroundColor);
+ expect(bgColor).not.toBe('rgba(0, 0, 0, 0)');
+ expect(bgColor).toMatch(/^rgb\(\d+, \d+, \d+\)$/);
+ });
+
+ test('should display color badge in environment list after selecting color', async ({ pageWithUserData: page }) => {
+ // Open global environment configuration
+ await page.getByTestId('environment-selector-trigger').click();
+ await page.getByTestId('env-tab-global').click();
+ await page.getByText('Configure', { exact: true }).click();
+
+ // Wait for the environments tab to be visible
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Global Environments' });
+ await expect(envTab).toBeVisible();
+
+ // Get the currently selected environment name
+ const activeEnvItem = page.locator('.environment-item.active');
+ const envName = await activeEnvItem.locator('.environment-name').textContent();
+
+ // Click on the color picker icon
+ const colorPickerTrigger = page.locator('[title="Change color"]').first();
+ await colorPickerTrigger.click();
+
+ // Wait for the color picker dropdown to appear and select a color
+ const colorPickerDropdown = page.locator('.tippy-box');
+ await expect(colorPickerDropdown).toBeVisible();
+
+ const presetColor = PRESET_COLORS[1]; // green
+ const colorOption = colorPickerDropdown.locator(`[title="${presetColor}"]`);
+ await colorOption.click();
+
+ // Verify the color badge in the environment list shows the selected color
+ const envListItem = page.locator('.environment-item').filter({ hasText: envName as string });
+ const colorBadge = envListItem.locator('.rounded-full').first();
+ await expect(colorBadge).toHaveCSS('background-color', hexToRgb(presetColor));
+ });
+});
diff --git a/tests/environments/color-picker/init-user-data/collection-security.json b/tests/environments/color-picker/init-user-data/collection-security.json
new file mode 100644
index 000000000..bd8bdb1d0
--- /dev/null
+++ b/tests/environments/color-picker/init-user-data/collection-security.json
@@ -0,0 +1,10 @@
+{
+ "collections": [
+ {
+ "path": "{{projectRoot}}/tests/environments/global-env-config-selection/collection",
+ "securityConfig": {
+ "jsSandboxMode": "safe"
+ }
+ }
+ ]
+}
diff --git a/tests/environments/color-picker/init-user-data/global-environments.json b/tests/environments/color-picker/init-user-data/global-environments.json
new file mode 100644
index 000000000..45c301067
--- /dev/null
+++ b/tests/environments/color-picker/init-user-data/global-environments.json
@@ -0,0 +1,47 @@
+{
+ "environments": [
+ {
+ "uid": "FlaexlO7lcH7UtEpWsVyz",
+ "name": "Development Environment",
+ "variables": [
+ {
+ "uid": "lflBDSYBdHkUedYhBF4Ty",
+ "name": "env_type",
+ "value": "development",
+ "type": "text",
+ "secret": false,
+ "enabled": true
+ }
+ ]
+ },
+ {
+ "uid": "MsHcnAIonZ3455OfvpTUT",
+ "name": "Production Environment",
+ "variables": [
+ {
+ "uid": "TZljXLErzW1nUWoozntZE",
+ "name": "env_type",
+ "value": "production",
+ "type": "text",
+ "secret": false,
+ "enabled": true
+ }
+ ]
+ },
+ {
+ "uid": "VdUAdMPcfapMCqjKAeUiI",
+ "name": "Staging Environment",
+ "variables": [
+ {
+ "uid": "FwoWhHvu9eLhA8H4brG6f",
+ "name": "env_type",
+ "value": "staging",
+ "type": "text",
+ "secret": false,
+ "enabled": true
+ }
+ ]
+ }
+ ],
+ "activeGlobalEnvironmentUid": "MsHcnAIonZ3455OfvpTUT"
+}
\ No newline at end of file
diff --git a/tests/environments/color-picker/init-user-data/preferences.json b/tests/environments/color-picker/init-user-data/preferences.json
new file mode 100644
index 000000000..3fa671a0b
--- /dev/null
+++ b/tests/environments/color-picker/init-user-data/preferences.json
@@ -0,0 +1,6 @@
+{
+ "maximized": true,
+ "lastOpenedCollections": [
+ "{{projectRoot}}/tests/environments/global-env-config-selection/collection"
+ ]
+}