feat: create new analysis page

This commit is contained in:
mehedi-hasan 2024-04-19 23:36:52 +06:00
parent 98475437a9
commit 338fdb9bb1
19 changed files with 1977 additions and 22 deletions

700
package-lock.json generated
View File

@ -11,6 +11,7 @@
"@hookform/resolvers": "^3.3.4",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-hover-card": "^1.0.7",
@ -34,6 +35,8 @@
"next-themes": "^0.3.0",
"react": "^18",
"react-dom": "^18",
"react-drag-drop-files": "^2.3.10",
"react-dropzone": "^14.2.3",
"react-hook-form": "^7.51.3",
"react-resizable-panels": "^2.0.16",
"tailwind-merge": "^2.2.2",
@ -72,6 +75,390 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
"peer": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@babel/code-frame": {
"version": "7.24.2",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz",
"integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==",
"dependencies": {
"@babel/highlight": "^7.24.2",
"picocolors": "^1.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/compat-data": {
"version": "7.24.4",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz",
"integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==",
"peer": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/core": {
"version": "7.24.4",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.4.tgz",
"integrity": "sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==",
"peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.24.2",
"@babel/generator": "^7.24.4",
"@babel/helper-compilation-targets": "^7.23.6",
"@babel/helper-module-transforms": "^7.23.3",
"@babel/helpers": "^7.24.4",
"@babel/parser": "^7.24.4",
"@babel/template": "^7.24.0",
"@babel/traverse": "^7.24.1",
"@babel/types": "^7.24.0",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
"json5": "^2.2.3",
"semver": "^6.3.1"
},
"engines": {
"node": ">=6.9.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/babel"
}
},
"node_modules/@babel/core/node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"peer": true,
"bin": {
"json5": "lib/cli.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@babel/core/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"peer": true,
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/@babel/generator": {
"version": "7.24.4",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.4.tgz",
"integrity": "sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw==",
"dependencies": {
"@babel/types": "^7.24.0",
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25",
"jsesc": "^2.5.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-annotate-as-pure": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz",
"integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==",
"dependencies": {
"@babel/types": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-compilation-targets": {
"version": "7.23.6",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz",
"integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==",
"peer": true,
"dependencies": {
"@babel/compat-data": "^7.23.5",
"@babel/helper-validator-option": "^7.23.5",
"browserslist": "^4.22.2",
"lru-cache": "^5.1.1",
"semver": "^6.3.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"peer": true,
"dependencies": {
"yallist": "^3.0.2"
}
},
"node_modules/@babel/helper-compilation-targets/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"peer": true,
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/@babel/helper-compilation-targets/node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"peer": true
},
"node_modules/@babel/helper-environment-visitor": {
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
"integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-function-name": {
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
"integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
"dependencies": {
"@babel/template": "^7.22.15",
"@babel/types": "^7.23.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-hoist-variables": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
"integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
"dependencies": {
"@babel/types": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-imports": {
"version": "7.24.3",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz",
"integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==",
"dependencies": {
"@babel/types": "^7.24.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-transforms": {
"version": "7.23.3",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz",
"integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==",
"peer": true,
"dependencies": {
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-module-imports": "^7.22.15",
"@babel/helper-simple-access": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
"@babel/helper-validator-identifier": "^7.22.20"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
}
},
"node_modules/@babel/helper-plugin-utils": {
"version": "7.24.0",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz",
"integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-simple-access": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz",
"integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==",
"peer": true,
"dependencies": {
"@babel/types": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-split-export-declaration": {
"version": "7.22.6",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
"integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
"dependencies": {
"@babel/types": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz",
"integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-option": {
"version": "7.23.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz",
"integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==",
"peer": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helpers": {
"version": "7.24.4",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.4.tgz",
"integrity": "sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==",
"peer": true,
"dependencies": {
"@babel/template": "^7.24.0",
"@babel/traverse": "^7.24.1",
"@babel/types": "^7.24.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/highlight": {
"version": "7.24.2",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz",
"integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==",
"dependencies": {
"@babel/helper-validator-identifier": "^7.22.20",
"chalk": "^2.4.2",
"js-tokens": "^4.0.0",
"picocolors": "^1.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/highlight/node_modules/ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dependencies": {
"color-convert": "^1.9.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/highlight/node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dependencies": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/highlight/node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dependencies": {
"color-name": "1.1.3"
}
},
"node_modules/@babel/highlight/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
},
"node_modules/@babel/highlight/node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/@babel/highlight/node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/highlight/node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dependencies": {
"has-flag": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/parser": {
"version": "7.24.4",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz",
"integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==",
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@babel/plugin-syntax-jsx": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz",
"integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/runtime": {
"version": "7.24.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz",
@ -83,6 +470,83 @@
"node": ">=6.9.0"
}
},
"node_modules/@babel/template": {
"version": "7.24.0",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz",
"integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==",
"dependencies": {
"@babel/code-frame": "^7.23.5",
"@babel/parser": "^7.24.0",
"@babel/types": "^7.24.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz",
"integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==",
"dependencies": {
"@babel/code-frame": "^7.24.1",
"@babel/generator": "^7.24.1",
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-function-name": "^7.23.0",
"@babel/helper-hoist-variables": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
"@babel/parser": "^7.24.1",
"@babel/types": "^7.24.0",
"debug": "^4.3.1",
"globals": "^11.1.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse/node_modules/globals": {
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/types": {
"version": "7.24.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz",
"integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==",
"dependencies": {
"@babel/helper-string-parser": "^7.23.4",
"@babel/helper-validator-identifier": "^7.22.20",
"to-fast-properties": "^2.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@emotion/is-prop-valid": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz",
"integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==",
"dependencies": {
"@emotion/memoize": "^0.8.1"
}
},
"node_modules/@emotion/memoize": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
"integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA=="
},
"node_modules/@emotion/stylis": {
"version": "0.8.5",
"resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz",
"integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ=="
},
"node_modules/@emotion/unitless": {
"version": "0.7.5",
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
"integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
},
"node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@ -583,6 +1047,36 @@
}
}
},
"node_modules/@radix-ui/react-collapsible": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.0.3.tgz",
"integrity": "sha512-UBmVDkmR6IvDsloHVN+3rtx4Mi5TFvylYXpluuv0f37dtaz3H99bp8No0LGXRigVpl3UAT4l9j6bIchh42S/Gg==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/primitive": "1.0.1",
"@radix-ui/react-compose-refs": "1.0.1",
"@radix-ui/react-context": "1.0.1",
"@radix-ui/react-id": "1.0.1",
"@radix-ui/react-presence": "1.0.1",
"@radix-ui/react-primitive": "1.0.3",
"@radix-ui/react-use-controllable-state": "1.0.1",
"@radix-ui/react-use-layout-effect": "1.0.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-collection": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.3.tgz",
@ -1953,6 +2447,14 @@
"integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==",
"dev": true
},
"node_modules/attr-accept": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
"integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==",
"engines": {
"node": ">=4"
}
},
"node_modules/autoprefixer": {
"version": "10.4.19",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz",
@ -2023,6 +2525,21 @@
"dequal": "^2.0.3"
}
},
"node_modules/babel-plugin-styled-components": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.4.tgz",
"integrity": "sha512-Xgp9g+A/cG47sUyRwwYxGM4bR/jDRg5N6it/8+HxCnbT5XNKSKDT9xm4oag/osgqjC2It/vH0yXsomOG6k558g==",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.22.5",
"@babel/helper-module-imports": "^7.22.5",
"@babel/plugin-syntax-jsx": "^7.22.5",
"lodash": "^4.17.21",
"picomatch": "^2.3.1"
},
"peerDependencies": {
"styled-components": ">= 2"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@ -2064,7 +2581,6 @@
"version": "4.23.0",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz",
"integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==",
"dev": true,
"funding": [
{
"type": "opencollective",
@ -2139,6 +2655,14 @@
"node": ">= 6"
}
},
"node_modules/camelize": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
"integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001608",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001608.tgz",
@ -2283,6 +2807,12 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true
},
"node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"peer": true
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -2296,6 +2826,24 @@
"node": ">= 8"
}
},
"node_modules/css-color-keywords": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
"integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
"engines": {
"node": ">=4"
}
},
"node_modules/css-to-react-native": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
"integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
"dependencies": {
"camelize": "^1.0.0",
"css-color-keywords": "^1.0.0",
"postcss-value-parser": "^4.0.2"
}
},
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@ -2383,7 +2931,6 @@
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dev": true,
"dependencies": {
"ms": "2.1.2"
},
@ -2492,8 +3039,7 @@
"node_modules/electron-to-chromium": {
"version": "1.4.733",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.733.tgz",
"integrity": "sha512-gUI9nhI2iBGF0OaYYLKOaOtliFMl+Bt1rY7VmEjwxOxqoYLub/D9xmduPEhbw2imE6gYkJKhIE5it+KE2ulVxQ==",
"dev": true
"integrity": "sha512-gUI9nhI2iBGF0OaYYLKOaOtliFMl+Bt1rY7VmEjwxOxqoYLub/D9xmduPEhbw2imE6gYkJKhIE5it+KE2ulVxQ=="
},
"node_modules/emoji-regex": {
"version": "9.2.2",
@ -2675,7 +3221,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
"integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
"dev": true,
"engines": {
"node": ">=6"
}
@ -3168,6 +3713,17 @@
"node": "^10.12.0 || >=12.0.0"
}
},
"node_modules/file-selector": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz",
"integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==",
"dependencies": {
"tslib": "^2.4.0"
},
"engines": {
"node": ">= 12"
}
},
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@ -3306,6 +3862,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
"peer": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/get-intrinsic": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
@ -3569,6 +4134,14 @@
"node": ">= 0.4"
}
},
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"dependencies": {
"react-is": "^16.7.0"
}
},
"node_modules/ignore": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
@ -4068,6 +4641,17 @@
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/jsesc": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
"bin": {
"jsesc": "bin/jsesc"
},
"engines": {
"node": ">=4"
}
},
"node_modules/json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
@ -4181,6 +4765,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@ -4266,8 +4855,7 @@
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/mz": {
"version": "2.7.0",
@ -4386,8 +4974,7 @@
"node_modules/node-releases": {
"version": "2.0.14",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
"integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
"dev": true
"integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw=="
},
"node_modules/normalize-path": {
"version": "3.0.0",
@ -4856,7 +5443,6 @@
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"dev": true,
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
@ -4914,6 +5500,35 @@
"react": "^18.2.0"
}
},
"node_modules/react-drag-drop-files": {
"version": "2.3.10",
"resolved": "https://registry.npmjs.org/react-drag-drop-files/-/react-drag-drop-files-2.3.10.tgz",
"integrity": "sha512-Fv614W9+OtXFB5O+gjompTxQZLYGO7wJeT4paETGiXtiADB9yPOMGYD4A3PMCTY9Be874/wcpl+2dm3MvCIRzg==",
"dependencies": {
"prop-types": "^15.7.2",
"styled-components": "^5.3.0"
},
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
},
"node_modules/react-dropzone": {
"version": "14.2.3",
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz",
"integrity": "sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==",
"dependencies": {
"attr-accept": "^2.2.2",
"file-selector": "^0.6.0",
"prop-types": "^15.8.1"
},
"engines": {
"node": ">= 10.13"
},
"peerDependencies": {
"react": ">= 16.8 || 18.0.0"
}
},
"node_modules/react-hook-form": {
"version": "7.51.3",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.51.3.tgz",
@ -4932,8 +5547,7 @@
"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==",
"dev": true
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/react-remove-scroll": {
"version": "2.5.5",
@ -5276,6 +5890,11 @@
"node": ">= 0.4"
}
},
"node_modules/shallowequal": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@ -5528,6 +6147,54 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/styled-components": {
"version": "5.3.11",
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.11.tgz",
"integrity": "sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==",
"dependencies": {
"@babel/helper-module-imports": "^7.0.0",
"@babel/traverse": "^7.4.5",
"@emotion/is-prop-valid": "^1.1.0",
"@emotion/stylis": "^0.8.4",
"@emotion/unitless": "^0.7.4",
"babel-plugin-styled-components": ">= 1.12.0",
"css-to-react-native": "^3.0.0",
"hoist-non-react-statics": "^3.0.0",
"shallowequal": "^1.1.0",
"supports-color": "^5.5.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/styled-components"
},
"peerDependencies": {
"react": ">= 16.8.0",
"react-dom": ">= 16.8.0",
"react-is": ">= 16.8.0"
}
},
"node_modules/styled-components/node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"engines": {
"node": ">=4"
}
},
"node_modules/styled-components/node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dependencies": {
"has-flag": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/styled-jsx": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz",
@ -5684,6 +6351,14 @@
"node": ">=0.8"
}
},
"node_modules/to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
"engines": {
"node": ">=4"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@ -5864,7 +6539,6 @@
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
"integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
"dev": true,
"funding": [
{
"type": "opencollective",

View File

@ -12,6 +12,7 @@
"@hookform/resolvers": "^3.3.4",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-hover-card": "^1.0.7",
@ -35,6 +36,8 @@
"next-themes": "^0.3.0",
"react": "^18",
"react-dom": "^18",
"react-drag-drop-files": "^2.3.10",
"react-dropzone": "^14.2.3",
"react-hook-form": "^7.51.3",
"react-resizable-panels": "^2.0.16",
"tailwind-merge": "^2.2.2",

View File

@ -0,0 +1,126 @@
"use client";
import { Step, type StepItem, Stepper } from "@/components/stepper";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { ArrowRight, X } from "lucide-react";
import Image from "next/image";
import { useCallback, useState } from "react";
// import { FileUploader } from "react-drag-drop-files";
import { useDropzone } from "react-dropzone";
const steps = [
{ label: "Upload extraction.zip" },
{ label: "Result of analysis" },
{ label: "Course recommendation" },
] satisfies StepItem[];
// const fileTypes = ["JPEG", "PNG", "GIF"];
const Page = () => {
const [uploadedFiles, setUploadedFiles] = useState<
{ file: File; preview: string; size: string }[]
>([]);
// const [uploadedFiles, setUploadedFiles] = useState<File[]>([]);
// const [preview, setPreview] = useState<(string | ArrayBuffer | null)[]>([]);
const onDrop = useCallback((acceptedFiles: Array<File>) => {
console.log(acceptedFiles);
console.log(acceptedFiles[0].size / 1024);
acceptedFiles.forEach((f) => {
const file = new FileReader();
file.onload = function () {
let fileSize = "";
if (f.size < 1024) {
// File size is less than 1KB
fileSize = `${f.size} bytes`;
} else if (f.size < 1024 * 1024) {
// File size is less than 1MB
fileSize = `${(f.size / 1024).toFixed(2)} kb`;
} else {
// File size is in MB
fileSize = `${(f.size / (1024 * 1024)).toFixed(2)} mb`;
}
setUploadedFiles((uploadFile) => [
...uploadFile,
{ file: f, preview: file.result as string, size: fileSize },
]);
};
file.readAsDataURL(f);
});
}, []);
const { getRootProps, getInputProps } = useDropzone({
onDrop,
});
return (
<div className="flex-1 space-y-8 p-4 md:p-8 pt-6 md:pt-10">
<div className="flex w-full flex-col gap-4">
<Stepper initialStep={0} steps={steps} variant="circle-alt">
{steps.map((stepProps, index) => {
return <Step key={stepProps.label} {...stepProps}></Step>;
})}
</Stepper>
</div>
<Alert className="py-6 space-y-2 bg-secondary">
<AlertTitle className="text-xl font-bold">Please upload the extraction.zip file</AlertTitle>
<AlertDescription>
You should have a script that creates it.
</AlertDescription>
</Alert>
<div
{...getRootProps()}
className="border border-dashed min-h-40 rounded-md relative cursor-pointer p-4"
>
<input {...getInputProps()} />
<div className="flex gap-4 flex-wrap">
{uploadedFiles.length !== 0 ? (
uploadedFiles.map((f, i) => (
<div key={i} className="w-28 h-full">
<div className="relative w-28 h-28">
<Image
src={(f.preview || "") as string}
alt=""
fill
className="object-cover rounded"
/>
<button
onClick={(e) => {
e.stopPropagation();
setUploadedFiles((files) => {
return files.filter(
(file) => file.file.name !== f.file.name
);
});
}}
className="absolute top-1 right-1 p-1 bg-background rounded-md text-foreground"
>
<X className="size-4 text-rose-100" />
</button>
</div>
<p className="truncate">{f.file.name}</p>
<p className="text-sm text-muted-foreground">{f.size}</p>
</div>
))
) : (
<p className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
Drag and drop files here or click to browse.
</p>
)}
</div>
</div>
<Button size="lg" className="inline-flex items-center gap-2"> <ArrowRight/> Start Analysis</Button>
</div>
);
};
export default Page;

View File

View File

@ -0,0 +1,78 @@
import * as React from "react"
import type { StepperProps } from "./types"
interface StepperContextValue extends StepperProps {
clickable?: boolean
isError?: boolean
isLoading?: boolean
isVertical?: boolean
stepCount?: number
expandVerticalSteps?: boolean
activeStep: number
initialStep: number
}
type StepperContextProviderProps = {
value: Omit<StepperContextValue, "activeStep">
children: React.ReactNode
}
const StepperContext = React.createContext<
StepperContextValue & {
nextStep: () => void
prevStep: () => void
resetSteps: () => void
setStep: (step: number) => void
}
>({
steps: [],
activeStep: 0,
initialStep: 0,
nextStep: () => {},
prevStep: () => {},
resetSteps: () => {},
setStep: () => {},
})
const StepperProvider = ({ value, children }: StepperContextProviderProps) => {
const isError = value.state === "error"
const isLoading = value.state === "loading"
const [activeStep, setActiveStep] = React.useState(value.initialStep)
const nextStep = () => {
setActiveStep((prev) => prev + 1)
}
const prevStep = () => {
setActiveStep((prev) => prev - 1)
}
const resetSteps = () => {
setActiveStep(value.initialStep)
}
const setStep = (step: number) => {
setActiveStep(step)
}
return (
<StepperContext.Provider
value={{
...value,
isError,
isLoading,
activeStep,
nextStep,
prevStep,
resetSteps,
setStep,
}}
>
{children}
</StepperContext.Provider>
)
}
export { StepperContext, StepperProvider }

View File

@ -0,0 +1,114 @@
/* eslint-disable react/display-name */
import { cn } from "@/lib/utils";
import * as React from "react";
import { StepButtonContainer } from "./step-button-container";
import { StepIcon } from "./step-icon";
import { StepLabel } from "./step-label";
import type { StepSharedProps } from "./types";
import { useStepper } from "./use-stepper";
const HorizontalStep = React.forwardRef<HTMLDivElement, StepSharedProps>(
(props, ref) => {
const {
isError,
isLoading,
onClickStep,
variant,
clickable,
checkIcon: checkIconContext,
errorIcon: errorIconContext,
styles,
steps,
setStep,
} = useStepper();
const {
index,
isCompletedStep,
isCurrentStep,
hasVisited,
icon,
label,
description,
isKeepError,
state,
checkIcon: checkIconProp,
errorIcon: errorIconProp,
} = props;
const localIsLoading = isLoading || state === "loading";
const localIsError = isError || state === "error";
const opacity = hasVisited ? 1 : 0.8;
const active =
variant === "line" ? isCompletedStep || isCurrentStep : isCompletedStep;
const checkIcon = checkIconProp || checkIconContext;
const errorIcon = errorIconProp || errorIconContext;
return (
<div
aria-disabled={!hasVisited}
className={cn(
"stepper__horizontal-step",
"flex items-center relative transition-all duration-200",
"[&:not(:last-child)]:flex-1",
"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200",
"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:h-[2px] [&:not(:last-child)]:after:bg-border",
"data-[completed=true]:[&:not(:last-child)]:after:bg-primary",
"data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive",
variant === "circle-alt" &&
"justify-start flex-col flex-1 [&:not(:last-child)]:after:relative [&:not(:last-child)]:after:order-[-1] [&:not(:last-child)]:after:start-[50%] [&:not(:last-child)]:after:end-[50%] [&:not(:last-child)]:after:top-[calc(var(--step-icon-size)/2)] [&:not(:last-child)]:after:w-[calc((100%-var(--step-icon-size))-(var(--step-gap)))]",
variant === "circle" &&
"[&:not(:last-child)]:after:flex-1 [&:not(:last-child)]:after:ms-[var(--step-gap)] [&:not(:last-child)]:after:me-[var(--step-gap)]",
variant === "line" &&
"flex-col flex-1 border-t-[3px] data-[active=true]:border-primary",
styles?.["horizontal-step"],
)}
data-optional={steps[index || 0]?.optional}
data-completed={isCompletedStep}
data-active={active}
data-invalid={localIsError}
data-clickable={clickable}
onClick={() => onClickStep?.(index || 0, setStep)}
ref={ref}
>
<div
className={cn(
"stepper__horizontal-step-container",
"flex items-center",
variant === "circle-alt" && "flex-col justify-center gap-1",
variant === "line" && "w-full",
styles?.["horizontal-step-container"],
)}
>
<StepButtonContainer
{...{ ...props, isError: localIsError, isLoading: localIsLoading }}
>
<StepIcon
{...{
index,
isCompletedStep,
isCurrentStep,
isError: localIsError,
isKeepError,
isLoading: localIsLoading,
}}
icon={icon}
checkIcon={checkIcon}
errorIcon={errorIcon}
/>
</StepButtonContainer>
<StepLabel
label={label}
description={description}
{...{ isCurrentStep, opacity }}
/>
</div>
</div>
);
},
);
export { HorizontalStep };

View File

@ -0,0 +1,184 @@
/* eslint-disable react/display-name */
"use client";
import { cn } from "@/lib/utils";
import * as React from "react";
import { StepperProvider } from "./context";
import { Step } from "./step";
import type { StepItem, StepProps, StepperProps } from "./types";
import { useMediaQuery } from "./use-media-query";
import { useStepper } from "./use-stepper";
const VARIABLE_SIZES = {
sm: "36px",
md: "40px",
lg: "44px",
};
const Stepper = React.forwardRef<HTMLDivElement, StepperProps>(
(props, ref: React.Ref<HTMLDivElement>) => {
const {
className,
children,
orientation: orientationProp,
state,
responsive,
checkIcon,
errorIcon,
onClickStep,
mobileBreakpoint,
expandVerticalSteps = false,
initialStep = 0,
size,
steps,
variant,
styles,
variables,
scrollTracking = false,
...rest
} = props;
const childArr = React.Children.toArray(children);
const items = [] as React.ReactElement[];
const footer = childArr.map((child, _index) => {
if (!React.isValidElement(child)) {
throw new Error("Stepper children must be valid React elements.");
}
if (child.type === Step) {
items.push(child);
return null;
}
return child;
});
const stepCount = items.length;
const isMobile = useMediaQuery(
`(max-width: ${mobileBreakpoint || "768px"})`,
);
const clickable = !!onClickStep;
const orientation = isMobile && responsive ? "vertical" : orientationProp;
const isVertical = orientation === "vertical";
return (
<StepperProvider
value={{
initialStep,
orientation,
state,
size,
responsive,
checkIcon,
errorIcon,
onClickStep,
clickable,
stepCount,
isVertical,
variant: variant || "circle",
expandVerticalSteps,
steps,
scrollTracking,
styles,
}}
>
<div
ref={ref}
className={cn(
"stepper__main-container",
"flex w-full flex-wrap",
stepCount === 1 ? "justify-end" : "justify-between",
orientation === "vertical" ? "flex-col" : "flex-row",
variant === "line" && orientation === "horizontal" && "gap-4",
className,
styles?.["main-container"],
)}
style={
{
"--step-icon-size":
variables?.["--step-icon-size"] ||
`${VARIABLE_SIZES[size || "md"]}`,
"--step-gap": variables?.["--step-gap"] || "8px",
} as React.CSSProperties
}
{...rest}
>
<VerticalContent>{items}</VerticalContent>
</div>
{orientation === "horizontal" && (
<HorizontalContent>{items}</HorizontalContent>
)}
{footer}
</StepperProvider>
);
},
);
Stepper.defaultProps = {
size: "md",
orientation: "horizontal",
responsive: true,
};
const VerticalContent = ({ children }: { children: React.ReactNode }) => {
const { activeStep } = useStepper();
const childArr = React.Children.toArray(children);
const stepCount = childArr.length;
return (
<>
{React.Children.map(children, (child, i) => {
const isCompletedStep =
(React.isValidElement(child) &&
(child.props as any).isCompletedStep) ??
i < activeStep;
const isLastStep = i === stepCount - 1;
const isCurrentStep = i === activeStep;
const stepProps = {
index: i,
isCompletedStep,
isCurrentStep,
isLastStep,
};
if (React.isValidElement(child)) {
return React.cloneElement(child, stepProps);
}
return null;
})}
</>
);
};
const HorizontalContent = ({ children }: { children: React.ReactNode }) => {
const { activeStep } = useStepper();
const childArr = React.Children.toArray(children);
if (activeStep > childArr.length) {
return null;
}
return (
<>
{React.Children.map(childArr[activeStep], (node) => {
if (!React.isValidElement(node)) {
return null;
}
return React.Children.map(
node.props.children,
(childNode) => childNode,
);
})}
</>
);
};
export { Stepper, Step, useStepper };
export type { StepProps, StepperProps, StepItem };

View File

@ -0,0 +1,59 @@
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import type { StepSharedProps } from "./types";
import { useStepper } from "./use-stepper";
type StepButtonContainerProps = StepSharedProps & {
children?: React.ReactNode;
};
const StepButtonContainer = ({
isCurrentStep,
isCompletedStep,
children,
isError,
isLoading: isLoadingProp,
onClickStep,
}: StepButtonContainerProps) => {
const {
clickable,
isLoading: isLoadingContext,
variant,
styles,
} = useStepper();
const currentStepClickable = clickable || !!onClickStep;
const isLoading = isLoadingProp || isLoadingContext;
if (variant === "line") {
return null;
}
return (
<Button
variant="ghost"
className={cn(
"stepper__step-button-container",
"rounded-full p-0 pointer-events-none",
"w-[var(--step-icon-size)] h-[var(--step-icon-size)]",
"border-2 flex rounded-full justify-center items-center",
"data-[clickable=true]:pointer-events-auto",
"data-[active=true]:bg-primary data-[active=true]:border-primary data-[active=true]:text-primary-foreground",
"data-[current=true]:border-primary data-[current=true]:bg-secondary",
"data-[invalid=true]:bg-destructive data-[invalid=true]:border-destructive data-[invalid=true]:text-destructive-foreground",
styles?.["step-button-container"],
)}
aria-current={isCurrentStep ? "step" : undefined}
data-current={isCurrentStep}
data-invalid={isError && (isCurrentStep || isCompletedStep)}
data-active={isCompletedStep}
data-clickable={currentStepClickable}
data-loading={isLoading && (isCurrentStep || isCompletedStep)}
>
{children}
</Button>
);
};
export { StepButtonContainer };

View File

@ -0,0 +1,133 @@
/* eslint-disable react/display-name */
import { cn } from "@/lib/utils";
import { cva } from "class-variance-authority";
import { CheckIcon, Loader2, X } from "lucide-react";
import * as React from "react";
import type { IconType } from "./types";
import { useStepper } from "./use-stepper";
interface StepIconProps {
isCompletedStep?: boolean;
isCurrentStep?: boolean;
isError?: boolean;
isLoading?: boolean;
isKeepError?: boolean;
icon?: IconType;
index?: number;
checkIcon?: IconType;
errorIcon?: IconType;
}
const iconVariants = cva("", {
variants: {
size: {
sm: "size-4",
md: "size-4",
lg: "size-5",
},
},
defaultVariants: {
size: "md",
},
});
const StepIcon = React.forwardRef<HTMLDivElement, StepIconProps>(
(props, ref) => {
const { size } = useStepper();
const {
isCompletedStep,
isCurrentStep,
isError,
isLoading,
isKeepError,
icon: CustomIcon,
index,
checkIcon: CustomCheckIcon,
errorIcon: CustomErrorIcon,
} = props;
const Icon = React.useMemo(
() => (CustomIcon ? CustomIcon : null),
[CustomIcon],
);
const ErrorIcon = React.useMemo(
() => (CustomErrorIcon ? CustomErrorIcon : null),
[CustomErrorIcon],
);
const Check = React.useMemo(
() => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),
[CustomCheckIcon],
);
return React.useMemo(() => {
if (isCompletedStep) {
if (isError && isKeepError) {
return (
<div key="icon">
<X className={cn(iconVariants({ size }))} />
</div>
);
}
return (
<div key="check-icon">
<Check className={cn(iconVariants({ size }))} />
</div>
);
}
if (isCurrentStep) {
if (isError && ErrorIcon) {
return (
<div key="error-icon">
<ErrorIcon className={cn(iconVariants({ size }))} />
</div>
);
}
if (isError) {
return (
<div key="icon">
<X className={cn(iconVariants({ size }))} />
</div>
);
}
if (isLoading) {
return (
<Loader2 className={cn(iconVariants({ size }), "animate-spin")} />
);
}
}
if (Icon) {
return (
<div key="step-icon">
<Icon className={cn(iconVariants({ size }))} />
</div>
);
}
return (
<span
ref={ref}
key="label"
className={cn("font-medium text-center text-md")}
>
{(index || 0) + 1}
</span>
);
}, [
isCompletedStep,
isCurrentStep,
isError,
isLoading,
Icon,
index,
Check,
ErrorIcon,
isKeepError,
ref,
size,
]);
},
);
export { StepIcon };

View File

@ -0,0 +1,90 @@
import { cn } from "@/lib/utils";
import { cva } from "class-variance-authority";
import { useStepper } from "./use-stepper";
interface StepLabelProps {
isCurrentStep?: boolean;
opacity: number;
label?: string | React.ReactNode;
description?: string | null;
}
const labelVariants = cva("", {
variants: {
size: {
sm: "text-sm",
md: "text-sm",
lg: "text-base",
},
},
defaultVariants: {
size: "md",
},
});
const descriptionVariants = cva("", {
variants: {
size: {
sm: "text-xs",
md: "text-xs",
lg: "text-sm",
},
},
defaultVariants: {
size: "md",
},
});
const StepLabel = ({
isCurrentStep,
opacity,
label,
description,
}: StepLabelProps) => {
const { variant, styles, size, orientation } = useStepper();
const shouldRender = !!label || !!description;
return shouldRender ? (
<div
aria-current={isCurrentStep ? "step" : undefined}
className={cn(
"stepper__step-label-container",
"flex-col flex",
variant !== "line" ? "ms-2" : orientation === "horizontal" && "my-2",
variant === "circle-alt" && "text-center",
variant === "circle-alt" && orientation === "horizontal" && "ms-0",
variant === "circle-alt" && orientation === "vertical" && "text-start",
styles?.["step-label-container"],
)}
style={{
opacity,
}}
>
{!!label && (
<span
className={cn(
"stepper__step-label",
labelVariants({ size }),
styles?.["step-label"],
)}
>
{label}
</span>
)}
{!!description && (
<span
className={cn(
"stepper__step-description",
"text-muted-foreground",
descriptionVariants({ size }),
styles?.["step-description"],
)}
>
{description}
</span>
)}
</div>
) : null;
};
export { StepLabel };

View File

@ -0,0 +1,76 @@
/* eslint-disable react/display-name */
import * as React from "react";
import { HorizontalStep } from "./horizontal-step";
import type { StepProps } from "./types";
import { useStepper } from "./use-stepper";
import { VerticalStep } from "./vertical-step";
// Props which shouldn't be passed to to the Step component from the user
interface StepInternalConfig {
index: number;
isCompletedStep?: boolean;
isCurrentStep?: boolean;
isLastStep?: boolean;
}
interface FullStepProps extends StepProps, StepInternalConfig {}
const Step = React.forwardRef<HTMLLIElement, StepProps>(
(props, ref: React.Ref<any>) => {
const {
children,
description,
icon,
state,
checkIcon,
errorIcon,
index,
isCompletedStep,
isCurrentStep,
isLastStep,
isKeepError,
label,
onClickStep,
} = props as FullStepProps;
const { isVertical, isError, isLoading, clickable } = useStepper();
const hasVisited = isCurrentStep || isCompletedStep;
const sharedProps = {
isLastStep,
isCompletedStep,
isCurrentStep,
index,
isError,
isLoading,
clickable,
label,
description,
hasVisited,
icon,
isKeepError,
checkIcon,
state,
errorIcon,
onClickStep,
};
const renderStep = () => {
switch (isVertical) {
case true:
return (
<VerticalStep ref={ref} {...sharedProps}>
{children}
</VerticalStep>
);
default:
return <HorizontalStep ref={ref} {...sharedProps} />;
}
};
return renderStep();
},
);
export { Step };

View File

@ -0,0 +1,88 @@
import type { LucideIcon } from "lucide-react";
type IconType = LucideIcon | React.ComponentType<any> | undefined;
type StepItem = {
id?: string;
label?: string;
description?: string;
icon?: IconType;
optional?: boolean;
};
interface StepOptions {
orientation?: "vertical" | "horizontal";
state?: "loading" | "error";
responsive?: boolean;
checkIcon?: IconType;
errorIcon?: IconType;
onClickStep?: (step: number, setStep: (step: number) => void) => void;
mobileBreakpoint?: string;
variant?: "circle" | "circle-alt" | "line";
expandVerticalSteps?: boolean;
size?: "sm" | "md" | "lg";
styles?: {
/** Styles for the main container */
"main-container"?: string;
/** Styles for the horizontal step */
"horizontal-step"?: string;
/** Styles for the horizontal step container (button and labels) */
"horizontal-step-container"?: string;
/** Styles for the vertical step */
"vertical-step"?: string;
/** Styles for the vertical step container (button and labels) */
"vertical-step-container"?: string;
/** Styles for the vertical step content */
"vertical-step-content"?: string;
/** Styles for the step button container */
"step-button-container"?: string;
/** Styles for the label and description container */
"step-label-container"?: string;
/** Styles for the step label */
"step-label"?: string;
/** Styles for the step description */
"step-description"?: string;
};
variables?: {
"--step-icon-size"?: string;
"--step-gap"?: string;
};
scrollTracking?: boolean;
}
interface StepperProps extends StepOptions {
children?: React.ReactNode;
className?: string;
initialStep: number;
steps: StepItem[];
}
interface StepProps extends React.HTMLAttributes<HTMLLIElement> {
label?: string | React.ReactNode;
description?: string;
icon?: IconType;
state?: "loading" | "error";
checkIcon?: IconType;
errorIcon?: IconType;
isCompletedStep?: boolean;
isKeepError?: boolean;
onClickStep?: (step: number, setStep: (step: number) => void) => void;
}
interface StepSharedProps extends StepProps {
isLastStep?: boolean;
isCurrentStep?: boolean;
index?: number;
hasVisited: boolean | undefined;
isError?: boolean;
isLoading?: boolean;
}
export type {
IconType,
StepItem,
StepOptions,
StepperProps,
StepProps,
StepSharedProps,
};

View File

@ -0,0 +1,19 @@
import * as React from "react";
export function useMediaQuery(query: string) {
const [value, setValue] = React.useState(false);
React.useEffect(() => {
function onChange(event: MediaQueryListEvent) {
setValue(event.matches);
}
const result = matchMedia(query);
result.addEventListener("change", onChange);
setValue(result.matches);
return () => result.removeEventListener("change", onChange);
}, [query]);
return value;
}

View File

@ -0,0 +1,42 @@
import * as React from "react";
import { StepperContext } from "./context";
function usePrevious<T>(value: T): T | undefined {
const ref = React.useRef<T>();
React.useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
export function useStepper() {
const context = React.useContext(StepperContext);
if (context === undefined) {
throw new Error("useStepper must be used within a StepperProvider");
}
const { children, className, ...rest } = context;
const isLastStep = context.activeStep === context.steps.length - 1;
const hasCompletedAllSteps = context.activeStep === context.steps.length;
const previousActiveStep = usePrevious(context.activeStep);
const currentStep = context.steps[context.activeStep];
const isOptionalStep = !!currentStep?.optional;
const isDisabledStep = context.activeStep === 0;
return {
...rest,
isLastStep,
hasCompletedAllSteps,
isOptionalStep,
isDisabledStep,
currentStep,
previousActiveStep,
};
}

View File

@ -0,0 +1,189 @@
/* eslint-disable react/display-name */
import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible";
import { cn } from "@/lib/utils";
import { cva } from "class-variance-authority";
import * as React from "react";
import { StepButtonContainer } from "./step-button-container";
import { StepIcon } from "./step-icon";
import { StepLabel } from "./step-label";
import type { StepSharedProps } from "./types";
import { useStepper } from "./use-stepper";
type VerticalStepProps = StepSharedProps & {
children?: React.ReactNode;
};
const verticalStepVariants = cva(
[
"flex flex-col relative transition-all duration-200",
"data-[completed=true]:[&:not(:last-child)]:after:bg-primary",
"data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive",
],
{
variants: {
variant: {
circle: cn(
"[&:not(:last-child)]:pb-[var(--step-gap)] [&:not(:last-child)]:gap-[var(--step-gap)]",
"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border",
"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]",
"[&:not(:last-child)]:after:absolute",
"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap))]",
"[&:not(:last-child)]:after:bottom-[var(--step-gap)]",
"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200",
),
line: "flex-1 border-t-0 mb-4",
},
},
},
);
const VerticalStep = React.forwardRef<HTMLDivElement, VerticalStepProps>(
(props, ref) => {
const {
children,
index,
isCompletedStep,
isCurrentStep,
label,
description,
icon,
hasVisited,
state,
checkIcon: checkIconProp,
errorIcon: errorIconProp,
onClickStep,
} = props;
const {
checkIcon: checkIconContext,
errorIcon: errorIconContext,
isError,
isLoading,
variant,
onClickStep: onClickStepGeneral,
clickable,
expandVerticalSteps,
styles,
scrollTracking,
orientation,
steps,
setStep,
isLastStep: isLastStepCurrentStep,
previousActiveStep,
} = useStepper();
const opacity = hasVisited ? 1 : 0.8;
const localIsLoading = isLoading || state === "loading";
const localIsError = isError || state === "error";
const isLastStep = index === steps.length - 1;
const active =
variant === "line" ? isCompletedStep || isCurrentStep : isCompletedStep;
const checkIcon = checkIconProp || checkIconContext;
const errorIcon = errorIconProp || errorIconContext;
const renderChildren = () => {
if (!expandVerticalSteps) {
return (
<Collapsible open={isCurrentStep}>
<CollapsibleContent
ref={(node: any) => {
if (
// If the step is the first step and the previous step
// was the last step or if the step is not the first step
// This prevents initial scrolling when the stepper
// is located anywhere other than the top of the view.
scrollTracking &&
((index === 0 &&
previousActiveStep &&
previousActiveStep === steps.length) ||
(index && index > 0))
) {
node?.scrollIntoView({
behavior: "smooth",
block: "center",
});
}
}}
className="overflow-hidden data-[state=open]:animate-collapsible-down data-[state=closed]:animate-collapsible-up"
>
{children}
</CollapsibleContent>
</Collapsible>
);
}
return children;
};
return (
<div
ref={ref}
className={cn(
"stepper__vertical-step",
verticalStepVariants({
variant: variant?.includes("circle") ? "circle" : "line",
}),
isLastStepCurrentStep && "gap-[var(--step-gap)]",
styles?.["vertical-step"],
)}
data-optional={steps[index || 0]?.optional}
data-completed={isCompletedStep}
data-active={active}
data-clickable={clickable || !!onClickStep}
data-invalid={localIsError}
onClick={() =>
onClickStep?.(index || 0, setStep) ||
onClickStepGeneral?.(index || 0, setStep)
}
>
<div
data-vertical={true}
data-active={active}
className={cn(
"stepper__vertical-step-container",
"flex items-center",
variant === "line" &&
"border-s-[3px] data-[active=true]:border-primary py-2 ps-3",
styles?.["vertical-step-container"],
)}
>
<StepButtonContainer
{...{ isLoading: localIsLoading, isError: localIsError, ...props }}
>
<StepIcon
{...{
index,
isError: localIsError,
isLoading: localIsLoading,
isCurrentStep,
isCompletedStep,
}}
icon={icon}
checkIcon={checkIcon}
errorIcon={errorIcon}
/>
</StepButtonContainer>
<StepLabel
label={label}
description={description}
{...{ isCurrentStep, opacity }}
/>
</div>
<div
className={cn(
"stepper__vertical-step-content",
!isLastStep && "min-h-4",
variant !== "line" && "ps-[--step-icon-size]",
variant === "line" && orientation === "vertical" && "min-h-0",
styles?.["vertical-step-content"],
)}
>
{renderChildren()}
</div>
</div>
);
},
);
export { VerticalStep };

View File

@ -0,0 +1,59 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const alertVariants = cva(
"relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
{
variants: {
variant: {
default: "bg-background text-foreground",
destructive:
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
},
},
defaultVariants: {
variant: "default",
},
}
)
const Alert = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => (
<div
ref={ref}
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
))
Alert.displayName = "Alert"
const AlertTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h5
ref={ref}
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
{...props}
/>
))
AlertTitle.displayName = "AlertTitle"
const AlertDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm [&_p]:leading-relaxed", className)}
{...props}
/>
))
AlertDescription.displayName = "AlertDescription"
export { Alert, AlertTitle, AlertDescription }

View File

@ -0,0 +1,11 @@
"use client"
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
const Collapsible = CollapsiblePrimitive.Root
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
export { Collapsible, CollapsibleTrigger, CollapsibleContent }

View File

@ -41,7 +41,7 @@ export const sideNavItems: GroupedNavItems[] = [
icon: Rocket,
},
{
href: "/dashboard/new-analysis",
href: "/dashboard/create-new-analysis",
title: "Create New Analysis",
icon: FlaskConical,
},

View File

@ -1,12 +1,12 @@
import type { Config } from "tailwindcss"
import type { Config } from "tailwindcss";
const config = {
darkMode: ["class"],
content: [
'./pages/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}',
'./app/**/*.{ts,tsx}',
'./src/**/*.{ts,tsx}',
"./pages/**/*.{ts,tsx}",
"./components/**/*.{ts,tsx}",
"./app/**/*.{ts,tsx}",
"./src/**/*.{ts,tsx}",
],
prefix: "",
theme: {
@ -67,14 +67,24 @@ const config = {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" },
},
"collapsible-down": {
from: { height: "0" },
to: { height: "var(--radix-collapsible-content-height)" },
},
"collapsible-up": {
from: { height: "var(--radix-collapsible-content-height)" },
to: { height: "0" },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
"collapsible-down": "collapsible-down 0.2s ease-out",
"collapsible-up": "collapsible-up 0.2s ease-out",
},
},
},
plugins: [require("tailwindcss-animate")],
} satisfies Config
} satisfies Config;
export default config
export default config;