diff --git a/package-lock.json b/package-lock.json index d85bc280..b82da274 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@floating-ui/react-dom": "^2.1.1", "classnames": "^2.5.1", "compressorjs": "^1.2.1", + "moment": "^2.30.1", "next": "15.3.1", "postcss": "^8.4.39", "postcss-preset-env": "^9.5.15", @@ -18,6 +19,7 @@ "react": "19.0.0", "react-dom": "19.0.0", "react-icons": "^5.2.1", + "recharts": "^2.15.1", "sass": "^1.77.6", "sharp": "^0.33.4" }, @@ -32,6 +34,17 @@ "typescript": "5.8.2" } }, + "node_modules/@babel/runtime": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@biomejs/biome": { "version": "1.9.4", "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz", @@ -2086,6 +2099,60 @@ "tslib": "^2.8.0" } }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==" + }, "node_modules/@types/node": { "version": "20.17.23", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.23.tgz", @@ -2262,6 +2329,14 @@ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -2419,9 +2494,123 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, "license": "MIT" }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==" + }, "node_modules/detect-libc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", @@ -2435,6 +2624,15 @@ "node": ">=0.10" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.112", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.112.tgz", @@ -2450,6 +2648,19 @@ "node": ">=6" } }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "node_modules/fast-equals": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz", + "integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -2482,6 +2693,14 @@ "integrity": "sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==", "license": "MIT" }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" + } + }, "node_modules/is-arrayish": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", @@ -2533,6 +2752,27 @@ "node": ">=0.12.0" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -2547,6 +2787,14 @@ "node": ">=8.6" } }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "engines": { + "node": "*" + } + }, "node_modules/nanoid": { "version": "3.3.8", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", @@ -3059,6 +3307,14 @@ "node": ">=0.10.0" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -3814,6 +4070,21 @@ "node": ">=6" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/react": { "version": "19.0.0", "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", @@ -3844,6 +4115,40 @@ "react": "*" } }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + }, + "node_modules/react-smooth": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", + "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -3857,6 +4162,41 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/recharts": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.1.tgz", + "integrity": "sha512-v8PUTUlyiDe56qUj82w/EDVuzEFXwEHp9/xOowGAZwfLjB9uAy3GllQVIYMWF6nU+qibx85WF75zD7AjqoT54Q==", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^18.3.1", + "react-smooth": "^4.0.4", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, "node_modules/sass": { "version": "1.85.1", "resolved": "https://registry.npmjs.org/sass/-/sass-1.85.1.tgz", @@ -3992,6 +4332,11 @@ } } }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -4067,6 +4412,27 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" + }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } } } } diff --git a/package.json b/package.json index f8d02406..d373d007 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@floating-ui/react-dom": "^2.1.1", "classnames": "^2.5.1", "compressorjs": "^1.2.1", + "moment": "^2.30.1", "next": "15.3.1", "postcss": "^8.4.39", "postcss-preset-env": "^9.5.15", @@ -20,6 +21,7 @@ "react": "19.0.0", "react-dom": "19.0.0", "react-icons": "^5.2.1", + "recharts": "^2.15.1", "sass": "^1.77.6", "sharp": "^0.33.4" }, diff --git a/src/once-ui/components/Badge.tsx b/src/once-ui/components/Badge.tsx index b7928b32..ed11faee 100644 --- a/src/once-ui/components/Badge.tsx +++ b/src/once-ui/components/Badge.tsx @@ -7,7 +7,7 @@ import styles from "./Badge.module.scss"; import { IconName } from "../icons"; import classNames from "classnames"; -interface BadgeProps extends React.ComponentProps { +export interface BadgeProps extends React.ComponentProps { title?: string; icon?: IconName; arrow?: boolean; diff --git a/src/once-ui/components/ProgressRing.module.scss b/src/once-ui/components/ProgressRing.module.scss new file mode 100644 index 00000000..8521d8b7 --- /dev/null +++ b/src/once-ui/components/ProgressRing.module.scss @@ -0,0 +1,43 @@ +.container { + --progress-color: var(--accent-alpha-strong); + + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + + &.size-s { width: 6rem; height: 6rem; } + &.size-m { width: 8rem; height: 8rem; } + &.size-l { width: 10rem; height: 10rem; } + + &.tone-warning { --progress-color: var(--warning-alpha-strong); } + &.tone-success { --progress-color: var(--success-alpha-strong); } +} + +.svg { + width: 100%; + height: 100%; + transform: rotate(-90deg); +} + +.background { + fill: none; + stroke: var(--progress-color); + opacity: 0.4; +} + +.progress { + fill: none; + stroke: var(--progress-color); + stroke-linecap: round; + transition: stroke-dashoffset 0.6s cubic-bezier(0.4, 0, 0.2, 1); +} + +.label { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: var(--foreground-strong); + font-weight: 600; +} \ No newline at end of file diff --git a/src/once-ui/components/ProgressRing.tsx b/src/once-ui/components/ProgressRing.tsx new file mode 100644 index 00000000..c5695813 --- /dev/null +++ b/src/once-ui/components/ProgressRing.tsx @@ -0,0 +1,61 @@ +"use client"; + +import styles from "./ProgressRing.module.scss"; +import { Column, Text } from "@/once-ui/components"; +import classNames from "classnames"; +import { SVG } from "./SVG"; + +interface ProgressRingProps { + value: number; + size?: "s" | "m" | "l"; + tone?: "primary" | "warning" | "success"; + label?: string; +} + +const ProgressRing = ({ + value, + size = "s", + tone = "primary", + label + }: ProgressRingProps) => { + const strokeWidth = 4; + const radiusMap = { s: 30, m: 45, l: 60 }; + const radius = radiusMap[size]; + + const normalizedRadius = radius - strokeWidth; + const circumference = normalizedRadius * 2 * Math.PI; + const strokeDashoffset = circumference * (1 - value / 100); + + return ( + + + + + + + {label && ( + + {label} + + )} + + ); +}; + +ProgressRing.displayName = "ProgressRing"; + +export { ProgressRing }; \ No newline at end of file diff --git a/src/once-ui/components/SVG.tsx b/src/once-ui/components/SVG.tsx new file mode 100644 index 00000000..0fda8174 --- /dev/null +++ b/src/once-ui/components/SVG.tsx @@ -0,0 +1,77 @@ + +"use client"; + +import React, { ElementType, ComponentPropsWithoutRef, ReactNode, forwardRef } from "react"; +import classNames from "classnames"; +import { CommonProps, SpacingProps } from "@/once-ui/interfaces"; +import { ColorScheme, ColorWeight, SpacingToken } from "@/once-ui/types"; + +type SVGProps = + ComponentPropsWithoutRef<"svg"> & + CommonProps & + SpacingProps & { + as?: T; + colorScheme?: ColorScheme; + colorWeight?: ColorWeight; + strokeScheme?: ColorScheme; + strokeWeight?: ColorWeight; + interactive?: boolean; + size?: "x" | "s" | "m" | "l" | "xl"; + children?: ReactNode; +}; + +function getColorClass(type: "fill" | "stroke", scheme?: ColorScheme, weight?: ColorWeight) { + return scheme ? `${type}-${scheme}-${weight}` : ""; +} + +function getSpacingClass(prefix: string, token?: SpacingToken) { + return token ? `${prefix}-${token}` : ""; +} + +const SVG = forwardRef>(function SVG( + { + as, + colorScheme = "neutral", + colorWeight = "weak", + strokeScheme, + strokeWeight = "weak", + interactive = false, + size = "m", + padding, + margin, + className, + style, + children, + ...rest + }, + ref +) { + const Component = as || "svg"; + const classes = classNames( + "transition-colors", + getColorClass("fill", colorScheme, colorWeight), + getColorClass("stroke", strokeScheme, strokeWeight), + getSpacingClass("p", padding), + getSpacingClass("m", margin), + { + "cursor-pointer hover:scale-105": interactive, + "opacity-hoverable": interactive, + }, + className + ); + + return ( + + {children} + + ); +}); + +SVG.displayName = "SVG"; + +export { SVG }; diff --git a/src/once-ui/components/TechCarousel.module.scss b/src/once-ui/components/TechCarousel.module.scss new file mode 100644 index 00000000..59641851 --- /dev/null +++ b/src/once-ui/components/TechCarousel.module.scss @@ -0,0 +1,8 @@ +.marqueeContent { + animation: marquee 20s linear infinite; +} + +@keyframes marquee { + 0% { transform: translateX(0%); } + 100% { transform: translateX(-50%); } +} \ No newline at end of file diff --git a/src/once-ui/components/TechCarousel.tsx b/src/once-ui/components/TechCarousel.tsx new file mode 100644 index 00000000..28212b50 --- /dev/null +++ b/src/once-ui/components/TechCarousel.tsx @@ -0,0 +1,89 @@ +import { Row, RevealFx, Flex, Badge, BadgeProps } from "@/once-ui/components"; +import styles from "./TechCarousel.module.scss"; +import { IconName } from "@/once-ui/icons"; +import React from "react"; +import {SpacingToken} from "@/once-ui/types"; + +const defaultIconMap: Record = { + java: 'java', + typescript: 'typescript', + javascript: 'js', + + spring: 'spring', + hibernate: 'hibernate', + react: 'react', + angular: 'angular', + vue: 'vue', + nodejs: 'node', + + postgresql: 'postgres', + mysql: 'mysql', + mongodb: 'mongo', + + docker: 'docker', + kubernetes: 'kubernetes', + aws: 'aws', + gcp: 'gcp', + git: 'git', + + graphql: 'graphql', + redis: 'redis', + nginx: 'nginx' +}; + +type TechItem = string | { + name: string; + icon?: IconName; +}; + +type TechCarouselProps = { + items: TechItem[]; + textVariant?: BadgeProps["textVariant"]; + spacing?: SpacingToken | "-1" | undefined; + iconMap?: Record; +} & Partial; + +const TechCarousel = ({ + items, + textVariant = "body-default-s", + spacing = "s", + iconMap = {}, + ...badgeProps + }: TechCarouselProps) => { + + const mergedIconMap = { ...defaultIconMap, ...iconMap }; + + const normalizedItems = items.map(item => + typeof item === 'string' ? { name: item } : item + ); + + return ( + + + {[...normalizedItems, ...normalizedItems].map((item, i) => { + const iconName = item.icon || mergedIconMap[item.name.toLowerCase()]; + + return ( + + + + ); + })} + + + ); +}; + +export { TechCarousel, defaultIconMap }; \ No newline at end of file diff --git a/src/once-ui/icons.ts b/src/once-ui/icons.ts index f7664794..2ae7ef95 100644 --- a/src/once-ui/icons.ts +++ b/src/once-ui/icons.ts @@ -32,6 +32,37 @@ import { HiOutlineDocumentDuplicate, } from "react-icons/hi2"; +import { RiVisaLine } from "react-icons/ri"; + +import { + FaAmazon, + FaDiscord, + FaGitAlt, + FaGithub, + FaGoogle +} from "react-icons/fa6"; + +import { LuChevronsLeftRight } from "react-icons/lu"; +import { FaJava } from "react-icons/fa"; +import { + SiAngular, + SiDocker, + SiGooglecloud, + SiGraphql, + SiHibernate, + SiKubernetes, + SiMongodb, + SiMysql, + SiNginx, + SiNodedotjs, + SiPostgresql, + SiReact, + SiRedis, + SiSpring, + SiTypescript, + SiVuedotjs +} from "react-icons/si"; + export const iconLibrary: Record = { chevronUp: HiChevronUp, chevronDown: HiChevronDown, @@ -61,7 +92,26 @@ export const iconLibrary: Record = { search: HiOutlineMagnifyingGlass, security: HiOutlineShieldCheck, sparkle: HiOutlineSparkles, - computer: HiOutlineComputerDesktop + computer: HiOutlineComputerDesktop, + java: FaJava, + spring: SiSpring, + hibernate: SiHibernate, + typescript: SiTypescript, + postgres: SiPostgresql, + mysql: SiMysql, + mongo: SiMongodb, + docker: SiDocker, + kubernetes: SiKubernetes, + aws: FaAmazon, + gcp: SiGooglecloud, + react: SiReact, + angular: SiAngular, + vue: SiVuedotjs, + node: SiNodedotjs, + graphql: SiGraphql, + redis: SiRedis, + nginx: SiNginx, + git: FaGitAlt }; export type IconLibrary = typeof iconLibrary; diff --git a/src/once-ui/modules/data/BarChart.tsx b/src/once-ui/modules/data/BarChart.tsx new file mode 100644 index 00000000..9233f3ab --- /dev/null +++ b/src/once-ui/modules/data/BarChart.tsx @@ -0,0 +1,269 @@ +import React from "react"; +import { + BarChart, + Bar, + XAxis, + YAxis, + ResponsiveContainer, + CartesianGrid, + Tooltip, + Legend, +} from "recharts"; + +import { TShirtSizes } from "../../types"; +import { Text, Flex, Column } from "../../components"; + +interface DataPoint { + name: string; + value: number; + startDate: string; + endDate: string; + color?: string; +} + +interface BarChartProps extends React.ComponentProps { + data: DataPoint[]; + xAxisKey?: string; + yAxisKey?: string; + barWidth?: TShirtSizes | "fill"; + title?: string; + description?: string; + legend?: boolean; + tooltip?: string; + xAxisTitle?: string; + yAxisTitle?: string; + labels?: "x" | "y" | "both"; +} + +const CustomTooltip = ({ active, payload, tooltipTitle, xAxisTitle }: any) => { + if (active && payload && payload.length) { + const data = payload[0].payload; + return ( + + + + {xAxisTitle && `${xAxisTitle}: `}{data.endDate} + + + + + {tooltipTitle && `${tooltipTitle} `} + + + {data.value.toLocaleString()} + + + + ); + } + return null; +}; + +const CustomLegend = ({ payload, labels, color }: any) => { + if (payload && payload.length) { + return ( + + {payload.map((entry: any, index: number) => ( + + + + {entry.value} + + + ))} + + ); + } + return null; +}; + +const BarGraph: React.FC = ({ + data, + xAxisKey = "name", + yAxisKey = "value", + yAxisTitle, + barWidth = "fill", + border = "neutral-medium", + color = "blue", + legend = false, + title, + description, + tooltip, + labels = "both", + ...flex +}) => { + return ( + + + {title && ( + + {title} + + )} + {description && ( + + {description} + + )} + + + + + + {legend && ( + } + wrapperStyle={{ + position: 'absolute', + top: 0, + right: 0, + margin: 0 + }} + /> + )} + {labels === "x" || labels === "both" && ( + + )} + {(labels === "y" || labels === "both") && ( + + )} + } + cursor={{ fill: "var(--neutral-alpha-weak)" }} + /> + + + {[ + { offset: "0%", opacity: 0.8 }, + { offset: "100%", opacity: 0 }, + ].map(({ offset, opacity }) => ( + + ))} + + + + + + + + ); +}; + +BarGraph.displayName = "BarGraph"; + +export { BarGraph }; +export type { BarChartProps }; diff --git a/src/once-ui/modules/data/GroupedBarChart.tsx b/src/once-ui/modules/data/GroupedBarChart.tsx new file mode 100644 index 00000000..98a12125 --- /dev/null +++ b/src/once-ui/modules/data/GroupedBarChart.tsx @@ -0,0 +1,315 @@ +import React from "react"; +import { + BarChart, + Bar, + XAxis, + YAxis, + ResponsiveContainer, + CartesianGrid, + Tooltip, + Legend, +} from "recharts"; + +import moment from "moment"; + +import { SpacingToken } from "../../types"; + +import { Text, Flex, Heading, Column } from "../../components"; + +// Data structure supporting multiple values +interface MultiBarDataPoint { + name: string; + value1?: number; + value2?: number; + value3?: number; + startDate?: string; + endDate?: string; +} + +interface GroupedBarChartProps extends React.ComponentProps { + data: MultiBarDataPoint[]; + /** + * Which axis labels to show + * @default "both" + */ + labels?: "x" | "y" | "both" | "none"; + + xAxisKey?: string; + yAxisKeys?: string[]; + barLabels?: string[]; + yAxisTitle?: string; + barWidth?: SpacingToken | "fill" | number | string; + isTimeSeries?: boolean; + timeFormat?: string; + legend?: boolean; + title?: string; + description?: string; + tooltipTitle?: string; +} + +const CustomLegend = ({ payload, labels, colors }: any) => { + if (!payload || !payload.length) return null; + return ( + + {payload.map((entry: any, index: number) => { + const color = colors[index]; + return ( + + + {entry.value} + + ); + })} + + ); +}; + +const CustomTooltip = ({ + active, + payload, + isTimeSeries, + barLabels, + colors, + timeFormat = "YYYY-MM-DD", +}: any) => { + if (!(active && payload && payload.length)) return null; + const data = payload[0].payload; + return ( + + + + {isTimeSeries + ? moment(data.name).format(timeFormat) + : (data.name || "—")} + + + + {payload.map((entry: any, index: number) => { + const color = colors[index]; + return ( + + + + + {barLabels?.[index] || entry.dataKey} + + + + {typeof entry.value === "number" + ? entry.value.toLocaleString() + : entry.value} + + + ); + })} + + + ); +}; + +const GroupedBarChart: React.FC = ({ + data, + labels = "both", + xAxisKey = "name", + yAxisKeys = ["value1", "value2", "value3"], + barLabels, + barWidth = "fill", + isTimeSeries = false, + timeFormat, + title, + yAxisTitle, + description, + legend = false, + tooltipTitle, + ...flexProps +}) => { + const barColors = [ + "var(--data-blue)", + "var(--data-green)", + "var(--data-orange)", + ]; + + return ( + + {(title || description) && ( + + {title && {title}} + {description && ( + + {description} + + )} + + )} + + + + + {legend && ( + } + wrapperStyle={{ position: "absolute", top: 0, right: 0 }} + /> + )} + {(labels === "x" || labels === "both") && ( + + )} + {(labels === "y" || labels === "both") && ( + + )} + + } + cursor={{ fill: "rgba(255,255,255,0.05)" }} + /> + + {barColors.map((color, index) => ( + + + + + ))} + + {yAxisKeys.map((key, index) => ( + + ))} + + + + + ); +}; + +GroupedBarChart.displayName = "GroupedBarChart"; + +export { GroupedBarChart }; +export type { GroupedBarChartProps }; \ No newline at end of file diff --git a/src/once-ui/modules/data/LineBarGraph.tsx b/src/once-ui/modules/data/LineBarGraph.tsx new file mode 100644 index 00000000..d2bac770 --- /dev/null +++ b/src/once-ui/modules/data/LineBarGraph.tsx @@ -0,0 +1,343 @@ +import React from "react"; +import moment from "moment"; +import { + ComposedChart, + Line, + Bar, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer, + Legend, + Area, +} from "recharts"; +import { Flex, Column, Text, Row } from "../../components"; +import { SpacingToken } from "../../types"; + +interface DataPoint { + [key: string]: string | number | Date; +} + +interface LineBarGraphProps extends React.ComponentProps { + data: DataPoint[]; + xAxisKey?: string; + lineDataKey?: string; + barDataKey?: string; + lineName?: string; + barName?: string; + lineColor?: string; + barColor?: string; + barWidth?: SpacingToken | "fill" | number; + showArea?: boolean; + labels?: "x" | "y" | "both" | "none"; + title?: string; + description?: string; + legend?: boolean; + dashedLine?: boolean; + curveType?: "linear" | "monotone" | "monotoneX" | "step" | "natural"; + isTimeSeries?: boolean; + timeFormat?: string; + xAxisTitle?: string; + yAxisTitle?: string; +} + +const defaultColors = { + line: "blue", + bar: "green" +}; + +const CustomTooltip = ({ active, payload, label, isTimeSeries, timeFormat = "MMM DD, YYYY", xAxisKey }: any) => { + if (active && payload && payload.length) { + // Get the proper label from the payload data instead of using the default label + const displayLabel = payload[0]?.payload?.[xAxisKey] || label; + + return ( + + + + {isTimeSeries ? moment(displayLabel).format(timeFormat) : displayLabel} + + + + {payload.map((entry: any, index: number) => ( + + + + + {entry.name} + + + + {typeof entry.value === 'number' ? entry.value.toLocaleString() : entry.value} + + + ))} + + + ); + } + return null; +}; + +const CustomLegend = ({ payload, labels }: any) => { + if (payload && payload.length) { + return ( + + {payload.map((entry: any, index: number) => ( + + + + {entry.value} + + + ))} + + ); + } + return null; +}; + +const LineBarGraph: React.FC = ({ + data, + xAxisKey = "name", + lineDataKey = "lineValue", + barDataKey = "barValue", + lineName = "Line", + barName = "Bar", + lineColor = defaultColors.line, + barColor = defaultColors.bar, + barWidth = "fill", + showArea = true, + dashedLine = false, + labels = "both", + border = "neutral-medium", + title, + description, + legend = false, + curveType = "monotone", + isTimeSeries = false, + timeFormat = "YYYY-MM-DD", + xAxisTitle, + yAxisTitle, + ...flexProps +}) => { + // Generate unique IDs for gradients + const lineGradientId = `colorLine-${React.useId()}`; + const barGradientId = `barGradient-${React.useId()}`; + + // Get the final colors with CSS variables + const finalLineColor = `var(--data-${lineColor})`; + const finalBarColor = `var(--data-${barColor})`; + + return ( + + {(title || description) && ( + + {title && ( + + {title} + + )} + {description && ( + + {description} + + )} + + )} + + + + + {/* Bar gradient */} + + + + + + + {/* Line gradient */} + + + + + + + {legend && ( + } + wrapperStyle={{ + position: 'absolute', + top: 0, + right: 0, + margin: 0 + }} + /> + )} + {(labels === "x" || labels === "both") && ( + + )} + {(labels === "y" || labels === "both") && ( + + )} + } + cursor={{ fill: "rgba(255,255,255,0.05)" }} + /> + + + {showArea ? ( + + ) : ( + + )} + + + + + ); +}; + +LineBarGraph.displayName = "LineBarGraph"; + +export { LineBarGraph }; +export type { LineBarGraphProps }; \ No newline at end of file diff --git a/src/once-ui/modules/data/LineChart.tsx b/src/once-ui/modules/data/LineChart.tsx new file mode 100644 index 00000000..8d55cb08 --- /dev/null +++ b/src/once-ui/modules/data/LineChart.tsx @@ -0,0 +1,280 @@ +import React from "react"; +import moment from 'moment' +import { + AreaChart, + Area, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer, + Legend, +} from "recharts"; +import { Flex, Column, Text, Row } from "../../components"; + +interface DataPoint { + [key: string]: string | number | Date; +} + +interface SeriesConfig { + key: string; + color?: string; +} + +interface LineChartProps extends React.ComponentProps { + data: DataPoint[]; + series: SeriesConfig[]; + colors?: string[]; + title?: string; + description?: string; + legend?: boolean; + tooltip?: string; + labels?: "x" | "y" | "both"; + curveType?: "linear" | "monotone" | "monotoneX" | "step" | "natural"; + isTimeSeries?: boolean; + timeFormat?: string; +} + +const defaultColors = ['blue', 'green', 'aqua', 'violet', 'orange', 'red', 'purple', 'magenta', 'moss', 'emerald']; + +const CustomTooltip = ({ active, payload, label, isTimeSeries, timeFormat = "MMM dd, yyyy" }: any) => { + if (active && payload && payload.length) { + return ( + + + + {isTimeSeries ? moment(label).format(timeFormat) : label} + + + + {payload.map((entry: any, index: number) => ( + + + + + {entry.name} + + + + {entry.value.toLocaleString()} + + + ))} + + + ); + } + return null; +}; + +const CustomLegend = ({ payload, labels, colors = defaultColors }: any) => { + if (payload && payload.length) { + return ( + + {payload.map((entry: any, index: number) => ( + + + + {entry.value} + + + ))} + + ); + } + return null; +}; + +const LineChart: React.FC = ({ + data, + series, + colors = defaultColors, + border = "neutral-medium", + title, + description, + legend = false, + tooltip, + labels = "both", + curveType = "natural", + isTimeSeries = false, + timeFormat, + ...flex +}) => { + // Auto-detect series from first data point if not provided + const seriesKeys = series.map(s => s.key); + const autoSeries = series || Object.keys(data[0] || {}) + .filter(key => !seriesKeys.includes(key)) + .map((key, index) => ({ + key, + color: colors[index] + })); + + const xAxisKey = Object.keys(data[0] || {}).find(key => + !seriesKeys.includes(key) + ) || 'name'; + + return ( + + {title && ( + + + {title} + + {description && ( + + {description} + + )} + + )} + + + + + {autoSeries.map(({ key, color }) => ( + + + + + ))} + + + {legend && ( + } + wrapperStyle={{ + position: 'absolute', + top: 0, + right: 0, + margin: 0 + }} + /> + )} + {(labels === "x" || labels === "both") && ( + + )} + {(labels === "y" || labels === "both") && ( + + )} + + } + /> + {autoSeries.map(({ key, color }) => ( + + ))} + + + + + ); +}; + +LineChart.displayName = "LineChart"; + +export { LineChart }; +export type { LineChartProps }; diff --git a/src/once-ui/modules/data/PieChart.tsx b/src/once-ui/modules/data/PieChart.tsx new file mode 100644 index 00000000..af9a1008 --- /dev/null +++ b/src/once-ui/modules/data/PieChart.tsx @@ -0,0 +1,216 @@ +import React from "react"; +import { + PieChart as RechartsPieChart, + Pie, + Cell, + Tooltip, + ResponsiveContainer, + Legend, +} from "recharts"; +import { Flex, Heading, Text, Column,Row } from "../../components"; + +interface DataPoint { + name: string; + value: number; + color?: string; // Optional custom color for this slice +} + +interface PieChartProps extends React.ComponentProps { + data: DataPoint[]; + /** + * Apply blur effect + * @default false + */ + blur?: boolean; + /** + * Title for the pie chart + */ + title?: string; + /** + * Show legend below the chart + * @default false + */ + showLegend?: boolean; + /** + * Inner radius for creating donut chart (percentage or pixel value) + * Set to 0 for filled pie chart, or a value like "60%" for donut chart + * @default "0" + */ + innerRadius?: number | string; + /** + * Outer radius for the pie chart (percentage or pixel value) + * @default "90%" + */ + outerRadius?: number | string; + /** + * Data key for values + * @default "value" + */ + dataKey?: string; + /** + * Name key for labels + * @default "name" + */ + nameKey?: string; + /** + * Angle between slices + * @default 0 + */ + paddingAngle?: number; + /** + * Use gradient fills for pie slices + * @default true + */ + useGradients?: boolean; + + defaultColors?: string[]; +} + +const CustomTooltip = ({ active, payload }: any) => { + if (active && payload && payload.length) { + return ( + + + {payload[0].name} + + + + + {`Value: ${payload[0].value}`} + + + + + ); + } + return null; +}; + +export const PieChart: React.FC = ({ + data, + defaultColors = ['blue', 'green', 'aqua', 'violet', 'orange', 'red', 'purple', 'magenta', 'moss', 'emerald'], + blur = false, + border, + title, + radius, + background, + showLegend = false, + innerRadius = "0", + outerRadius = "90%", + dataKey = "value", + nameKey = "name", + paddingAngle = 0, + useGradients = true, + ...flexProps +}) => { + // Convert defaults into CSS var() references + const colorPalette = defaultColors.map((c) => `var(--data-${c})`); + + // Generate unique IDs for each gradient + const gradientIds = data.map((_, index) => + `pieGradient-${Math.random().toString(36).substring(2, 9)}-${index}` + ); + + return ( + + {title && ( + + {title} + + )} + + + + + {data.map((entry, index) => { + const baseColor = entry.color || colorPalette[index % colorPalette.length]; + return ( + + + + + + ); + })} + + + {data.map((entry, index) => { + const baseColor = entry.color || colorPalette[index % colorPalette.length]; + return ( + + ); + })} + + } + /> + {showLegend && ( + + )} + + + + + ); +}; \ No newline at end of file diff --git a/src/once-ui/tokens/data.scss b/src/once-ui/tokens/data.scss new file mode 100644 index 00000000..84362bbe --- /dev/null +++ b/src/once-ui/tokens/data.scss @@ -0,0 +1,166 @@ +[data-theme="dark"] [data-viz="categorical"] { + --data-blue: var(--scheme-blue-500); + + --data-green: var(--scheme-green-500); + + --data-aqua: var(--scheme-aqua-500); + + --data-violet: var(--scheme-violet-500); + + --data-orange: var(--scheme-orange-500); + + --data-red: var(--scheme-red-500); + + --data-purple: var(--scheme-purple-500); + + --data-magenta: var(--scheme-magenta-500); + + --data-moss: var(--scheme-moss-500); + + --data-emerald: var(--scheme-emerald-500); +} + +[data-theme="light"] [data-viz="categorical"] { + --data-green: var(--scheme-green-400); + --data-green-alpha: var(--scheme-green-500-30); + + --data-aqua: var(--scheme-aqua-400); + --data-aqua-alpha: var(--scheme-aqua-500-30); + + --data-violet: var(--scheme-violet-400); + --data-violet-alpha: var(--scheme-violet-500-30); + + --data-orange: var(--scheme-orange-400); + --data-orange-alpha: var(--scheme-orange-500-30); + + --data-red: var(--scheme-red-400); + --data-red-alpha: var(--scheme-red-500-30); + + --data-purple: var(--scheme-purple-400); + --data-purple-alpha: var(--scheme-purple-500-30); + + --data-magenta: var(--scheme-magenta-400); + --data-magenta-alpha: var(--scheme-magenta-500-30); + + --data-moss: var(--scheme-moss-400); + --data-moss-alpha: var(--scheme-moss-500-30); + + --data-emerald: var(--scheme-emerald-400); + --data-emerald-alpha: var(--scheme-emerald-500-30); +} + +[data-theme="dark"] [data-viz="divergent"] { + --data-solid-100: var(--scheme-red-500); + --data-alpha-100: var(--scheme-green-700-30); + + --data-solid-200: var(--scheme-orange-500); + --data-alpha-200: var(--scheme-aqua-500-30); + + --data-solid-300: var(--scheme-yellow-700); + --data-alpha-300: var(--scheme-violet-500-30); + + --data-solid-400: var(--scheme-yellow-800); + --data-alpha-400: var(--scheme-orange-500-30); + + --data-solid-500: var(--scheme-yellow-900); + --data-alpha-500: var(--scheme-red-700-30); + + --data-solid-500: var(--scheme-moss-1000); + --data-alpha-500: var(--scheme-purple-700-30); + + --data-solid-700: var(--scheme-moss-900); + --data-alpha-700: var(--scheme-blue-500-30); + + --data-solid-800: var(--scheme-aqua-800); + --data-alpha-800: var(--scheme-blue-500-30); + + --data-solid-900: var(--scheme-cyan-700); + --data-alpha-900: var(--scheme-blue-500-30); +} + +[data-theme="light"] [data-viz="divergent"] { + --data-solid-100: var(--scheme-red-500); + --data-alpha-100: var(--scheme-green-700-30); + + --data-solid-200: var(--scheme-orange-500); + --data-alpha-200: var(--scheme-aqua-500-30); + + --data-solid-300: var(--scheme-yellow-700); + --data-alpha-300: var(--scheme-violet-500-30); + + --data-solid-400: var(--scheme-yellow-800); + --data-alpha-400: var(--scheme-orange-500-30); + + --data-solid-500: var(--scheme-yellow-900); + --data-alpha-500: var(--scheme-red-700-30); + + --data-solid-500: var(--scheme-moss-1000); + --data-alpha-500: var(--scheme-purple-700-30); + + --data-solid-700: var(--scheme-moss-900); + --data-alpha-700: var(--scheme-blue-500-30); + + --data-solid-800: var(--scheme-aqua-800); + --data-alpha-800: var(--scheme-blue-500-30); + + --data-solid-900: var(--scheme-cyan-700); + --data-alpha-900: var(--scheme-blue-500-30); +} + +[data-theme="dark"] [data-viz="sequential"] { + --data-solid-100: var(--scheme-blue-100); + --data-alpha-100: var(--scheme-blue-100-30); + + --data-solid-200: var(--scheme-blue-200); + --data-alpha-200: var(--scheme-blue-200-30); + + --data-solid-300: var(--scheme-blue-300); + --data-alpha-300: var(--scheme-blue-400-30); + + --data-solid-400: var(--scheme-blue-400); + --data-alpha-400: var(--scheme-blue-400-30); + + --data-solid-500: var(--scheme-blue-500); + --data-alpha-500: var(--scheme-blue-400-30); + + --data-solid-500: var(--scheme-blue-500); + --data-alpha-500: var(--scheme-blue-400-30); + + --data-solid-700: var(--scheme-blue-700); + --data-alpha-700: var(--scheme-blue-400-30); + + --data-solid-800: var(--scheme-blue-800); + --data-alpha-800: var(--scheme-blue-400-30); + + --data-solid-900: var(--scheme-blue-900); + --data-alpha-900: var(--scheme-blue-400-30); +} + +[data-theme="light"] [data-viz="sequential"] { + --data-solid-100: var(--scheme-blue-100); + --data-alpha-100: var(--scheme-blue-100-30); + + --data-solid-200: var(--scheme-blue-200); + --data-alpha-200: var(--scheme-blue-200-30); + + --data-solid-300: var(--scheme-blue-300); + --data-alpha-300: var(--scheme-blue-400-30); + + --data-solid-400: var(--scheme-blue-400); + --data-alpha-400: var(--scheme-blue-400-30); + + --data-solid-500: var(--scheme-blue-500); + --data-alpha-500: var(--scheme-blue-400-30); + + --data-solid-500: var(--scheme-blue-500); + --data-alpha-500: var(--scheme-blue-400-30); + + --data-solid-700: var(--scheme-blue-700); + --data-alpha-700: var(--scheme-blue-400-30); + + --data-solid-800: var(--scheme-blue-800); + --data-alpha-800: var(--scheme-blue-400-30); + + --data-solid-900: var(--scheme-blue-900); + --data-alpha-900: var(--scheme-blue-400-30); +} \ No newline at end of file diff --git a/src/once-ui/tokens/index.scss b/src/once-ui/tokens/index.scss index 80c23210..c850f9a8 100644 --- a/src/once-ui/tokens/index.scss +++ b/src/once-ui/tokens/index.scss @@ -4,4 +4,5 @@ @use "@/once-ui/tokens/border.scss"; @use "@/once-ui/tokens/shadow.scss"; @use "@/once-ui/tokens/typography.scss"; -@use "@/once-ui/tokens/theme.scss"; \ No newline at end of file +@use "@/once-ui/tokens/theme.scss"; +@use "@/once-ui/tokens/data.scss"; \ No newline at end of file