From 338fdb9bb19bc85f331b19895d628e60c05769af Mon Sep 17 00:00:00 2001 From: mehedi-hasan Date: Fri, 19 Apr 2024 23:36:52 +0600 Subject: [PATCH] feat: create new analysis page --- package-lock.json | 700 +++++++++++++++++- package.json | 3 + .../dashboard/create-new-analysis/page.tsx | 126 ++++ src/components/file-upload.tsx | 0 src/components/stepper/context.tsx | 78 ++ src/components/stepper/horizontal-step.tsx | 114 +++ src/components/stepper/index.tsx | 184 +++++ .../stepper/step-button-container.tsx | 59 ++ src/components/stepper/step-icon.tsx | 133 ++++ src/components/stepper/step-label.tsx | 90 +++ src/components/stepper/step.tsx | 76 ++ src/components/stepper/types.ts | 88 +++ src/components/stepper/use-media-query.tsx | 19 + src/components/stepper/use-stepper.tsx | 42 ++ src/components/stepper/vertical-step.tsx | 189 +++++ src/components/ui/alert.tsx | 59 ++ src/components/ui/collapsible.tsx | 11 + src/config/nav.ts | 2 +- tailwind.config.ts | 26 +- 19 files changed, 1977 insertions(+), 22 deletions(-) create mode 100644 src/app/(dashboard)/dashboard/create-new-analysis/page.tsx create mode 100644 src/components/file-upload.tsx create mode 100644 src/components/stepper/context.tsx create mode 100644 src/components/stepper/horizontal-step.tsx create mode 100644 src/components/stepper/index.tsx create mode 100644 src/components/stepper/step-button-container.tsx create mode 100644 src/components/stepper/step-icon.tsx create mode 100644 src/components/stepper/step-label.tsx create mode 100644 src/components/stepper/step.tsx create mode 100644 src/components/stepper/types.ts create mode 100644 src/components/stepper/use-media-query.tsx create mode 100644 src/components/stepper/use-stepper.tsx create mode 100644 src/components/stepper/vertical-step.tsx create mode 100644 src/components/ui/alert.tsx create mode 100644 src/components/ui/collapsible.tsx diff --git a/package-lock.json b/package-lock.json index 5a4e0a1..a52c056 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 24a6a6c..bdfa7b9 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/app/(dashboard)/dashboard/create-new-analysis/page.tsx b/src/app/(dashboard)/dashboard/create-new-analysis/page.tsx new file mode 100644 index 0000000..0226a3a --- /dev/null +++ b/src/app/(dashboard)/dashboard/create-new-analysis/page.tsx @@ -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([]); + // const [preview, setPreview] = useState<(string | ArrayBuffer | null)[]>([]); + + const onDrop = useCallback((acceptedFiles: Array) => { + 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 ( +
+
+ + {steps.map((stepProps, index) => { + return ; + })} + +
+ + + Please upload the extraction.zip file + + You should have a script that creates it. + + + +
+ +
+ {uploadedFiles.length !== 0 ? ( + uploadedFiles.map((f, i) => ( +
+
+ + +
+

{f.file.name}

+

{f.size}

+
+ )) + ) : ( +

+ Drag and drop files here or click to browse. +

+ )} +
+
+ + +
+ ); +}; + +export default Page; diff --git a/src/components/file-upload.tsx b/src/components/file-upload.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/components/stepper/context.tsx b/src/components/stepper/context.tsx new file mode 100644 index 0000000..8a93fc1 --- /dev/null +++ b/src/components/stepper/context.tsx @@ -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 + 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 ( + + {children} + + ) +} + +export { StepperContext, StepperProvider } \ No newline at end of file diff --git a/src/components/stepper/horizontal-step.tsx b/src/components/stepper/horizontal-step.tsx new file mode 100644 index 0000000..edc6607 --- /dev/null +++ b/src/components/stepper/horizontal-step.tsx @@ -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( + (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 ( +
onClickStep?.(index || 0, setStep)} + ref={ref} + > +
+ + + + +
+
+ ); + }, +); + +export { HorizontalStep }; \ No newline at end of file diff --git a/src/components/stepper/index.tsx b/src/components/stepper/index.tsx new file mode 100644 index 0000000..9291bb9 --- /dev/null +++ b/src/components/stepper/index.tsx @@ -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( + (props, ref: React.Ref) => { + 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 ( + +
+ {items} +
+ {orientation === "horizontal" && ( + {items} + )} + {footer} +
+ ); + }, +); + +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 }; \ No newline at end of file diff --git a/src/components/stepper/step-button-container.tsx b/src/components/stepper/step-button-container.tsx new file mode 100644 index 0000000..2cd48cd --- /dev/null +++ b/src/components/stepper/step-button-container.tsx @@ -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 ( + + ); +}; + +export { StepButtonContainer }; \ No newline at end of file diff --git a/src/components/stepper/step-icon.tsx b/src/components/stepper/step-icon.tsx new file mode 100644 index 0000000..71a3891 --- /dev/null +++ b/src/components/stepper/step-icon.tsx @@ -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( + (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 ( +
+ +
+ ); + } + return ( +
+ +
+ ); + } + if (isCurrentStep) { + if (isError && ErrorIcon) { + return ( +
+ +
+ ); + } + if (isError) { + return ( +
+ +
+ ); + } + if (isLoading) { + return ( + + ); + } + } + if (Icon) { + return ( +
+ +
+ ); + } + return ( + + {(index || 0) + 1} + + ); + }, [ + isCompletedStep, + isCurrentStep, + isError, + isLoading, + Icon, + index, + Check, + ErrorIcon, + isKeepError, + ref, + size, + ]); + }, +); + +export { StepIcon }; \ No newline at end of file diff --git a/src/components/stepper/step-label.tsx b/src/components/stepper/step-label.tsx new file mode 100644 index 0000000..bedbb35 --- /dev/null +++ b/src/components/stepper/step-label.tsx @@ -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 ? ( +
+ {!!label && ( + + {label} + + )} + {!!description && ( + + {description} + + )} +
+ ) : null; +}; + +export { StepLabel }; \ No newline at end of file diff --git a/src/components/stepper/step.tsx b/src/components/stepper/step.tsx new file mode 100644 index 0000000..66fa8c2 --- /dev/null +++ b/src/components/stepper/step.tsx @@ -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( + (props, ref: React.Ref) => { + 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 ( + + {children} + + ); + default: + return ; + } + }; + + return renderStep(); + }, +); + +export { Step }; \ No newline at end of file diff --git a/src/components/stepper/types.ts b/src/components/stepper/types.ts new file mode 100644 index 0000000..46a161a --- /dev/null +++ b/src/components/stepper/types.ts @@ -0,0 +1,88 @@ +import type { LucideIcon } from "lucide-react"; + +type IconType = LucideIcon | React.ComponentType | 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 { + 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, +}; \ No newline at end of file diff --git a/src/components/stepper/use-media-query.tsx b/src/components/stepper/use-media-query.tsx new file mode 100644 index 0000000..47a34ac --- /dev/null +++ b/src/components/stepper/use-media-query.tsx @@ -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; +} \ No newline at end of file diff --git a/src/components/stepper/use-stepper.tsx b/src/components/stepper/use-stepper.tsx new file mode 100644 index 0000000..b13eeff --- /dev/null +++ b/src/components/stepper/use-stepper.tsx @@ -0,0 +1,42 @@ +import * as React from "react"; +import { StepperContext } from "./context"; + +function usePrevious(value: T): T | undefined { + const ref = React.useRef(); + + 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, + }; +} \ No newline at end of file diff --git a/src/components/stepper/vertical-step.tsx b/src/components/stepper/vertical-step.tsx new file mode 100644 index 0000000..323722d --- /dev/null +++ b/src/components/stepper/vertical-step.tsx @@ -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( + (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 ( + + { + 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} + + + ); + } + return children; + }; + + return ( +
+ onClickStep?.(index || 0, setStep) || + onClickStepGeneral?.(index || 0, setStep) + } + > +
+ + + + +
+
+ {renderChildren()} +
+
+ ); + }, +); + +export { VerticalStep }; \ No newline at end of file diff --git a/src/components/ui/alert.tsx b/src/components/ui/alert.tsx new file mode 100644 index 0000000..5afd41d --- /dev/null +++ b/src/components/ui/alert.tsx @@ -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 & VariantProps +>(({ className, variant, ...props }, ref) => ( +
+)) +Alert.displayName = "Alert" + +const AlertTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +AlertTitle.displayName = "AlertTitle" + +const AlertDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +AlertDescription.displayName = "AlertDescription" + +export { Alert, AlertTitle, AlertDescription } diff --git a/src/components/ui/collapsible.tsx b/src/components/ui/collapsible.tsx new file mode 100644 index 0000000..9fa4894 --- /dev/null +++ b/src/components/ui/collapsible.tsx @@ -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 } diff --git a/src/config/nav.ts b/src/config/nav.ts index 4310c1a..117e9d7 100644 --- a/src/config/nav.ts +++ b/src/config/nav.ts @@ -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, }, diff --git a/tailwind.config.ts b/tailwind.config.ts index 84287e8..c9a6e4f 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,13 +1,13 @@ -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: { container: { @@ -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 \ No newline at end of file +export default config;