diff --git a/biome.json b/biome.json index c96170e..87b3337 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.3.7/schema.json", + "$schema": "https://biomejs.dev/schemas/2.3.8/schema.json", "vcs": { "enabled": true, "clientKind": "git", @@ -44,6 +44,11 @@ } } }, + "css": { + "parser": { + "tailwindDirectives": true + } + }, "files": { "includes": [ "client/**/*", diff --git a/client/components.json b/client/components.json index 03ca16a..0a08cbe 100644 --- a/client/components.json +++ b/client/components.json @@ -5,7 +5,7 @@ "tsx": true, "tailwind": { "config": "tailwind.config.js", - "css": "src/globals.css", + "css": "src/index.css", "baseColor": "zinc", "cssVariables": true, "prefix": "" diff --git a/client/package.json b/client/package.json index 1fdc78f..d2cc7fc 100644 --- a/client/package.json +++ b/client/package.json @@ -32,19 +32,24 @@ "@radix-ui/react-toggle": "^1.1.10", "@radix-ui/react-toggle-group": "^1.1.11", "@radix-ui/react-tooltip": "^1.2.8", - "@semoss/sdk": "1.0.0-beta.31", + "@semoss/sdk": "1.0.0-beta.32", "@tailwindcss/postcss": "^4.1.17", + "@tailwindcss/vite": "^4.1.17", "autoprefixer": "^10.4.22", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "date-fns": "^4.1.0", "lucide-react": "^0.553.0", + "next-themes": "^0.4.6", "postcss": "^8.5.6", "react": "^18.3.1", + "react-day-picker": "^9.11.3", "react-dom": "^18.3.1", - "react-router-dom": "^7.9.6", + "react-router-dom": "^7.10.0", + "sonner": "^2.0.7", "tailwind-merge": "^3.4.0", "tailwindcss": "^4.1.17", - "tw-animate-css": "^1.4.0" + "tailwindcss-animate": "^1.0.7" }, "devDependencies": { "@types/node": "^24.10.1", @@ -52,6 +57,6 @@ "@types/react-dom": "^18.3.7", "@vitejs/plugin-react": "^5.1.1", "typescript": "^5.9.3", - "vite": "^7.2.4" + "vite": "^7.2.6" } } diff --git a/client/pnpm-lock.yaml b/client/pnpm-lock.yaml index 1d4505c..9570c47 100644 --- a/client/pnpm-lock.yaml +++ b/client/pnpm-lock.yaml @@ -87,11 +87,14 @@ importers: specifier: ^1.2.8 version: 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@semoss/sdk': - specifier: 1.0.0-beta.31 - version: 1.0.0-beta.31(react@18.3.1) + specifier: 1.0.0-beta.32 + version: 1.0.0-beta.32(react@18.3.1) '@tailwindcss/postcss': specifier: ^4.1.17 version: 4.1.17 + '@tailwindcss/vite': + specifier: ^4.1.17 + version: 4.1.17(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)) autoprefixer: specifier: ^10.4.22 version: 10.4.22(postcss@8.5.6) @@ -101,30 +104,42 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + date-fns: + specifier: ^4.1.0 + version: 4.1.0 lucide-react: specifier: ^0.553.0 version: 0.553.0(react@18.3.1) + next-themes: + specifier: ^0.4.6 + version: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) postcss: specifier: ^8.5.6 version: 8.5.6 react: specifier: ^18.3.1 version: 18.3.1 + react-day-picker: + specifier: ^9.11.3 + version: 9.11.3(react@18.3.1) react-dom: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) react-router-dom: - specifier: ^7.9.6 - version: 7.9.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^7.10.0 + version: 7.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + sonner: + specifier: ^2.0.7 + version: 2.0.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1) tailwind-merge: specifier: ^3.4.0 version: 3.4.0 tailwindcss: specifier: ^4.1.17 version: 4.1.17 - tw-animate-css: - specifier: ^1.4.0 - version: 1.4.0 + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@4.1.17) devDependencies: '@types/node': specifier: ^24.10.1 @@ -137,13 +152,13 @@ importers: version: 18.3.7(@types/react@18.3.27) '@vitejs/plugin-react': specifier: ^5.1.1 - version: 5.1.1(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)) + version: 5.1.1(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)) typescript: specifier: ^5.9.3 version: 5.9.3 vite: - specifier: ^7.2.4 - version: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0) + specifier: ^7.2.6 + version: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0) packages: @@ -234,6 +249,9 @@ packages: resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} + '@date-fns/tz@1.4.1': + resolution: {integrity: sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==} + '@esbuild/aix-ppc64@0.25.12': resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} engines: {node: '>=18'} @@ -1180,8 +1198,8 @@ packages: cpu: [x64] os: [win32] - '@semoss/sdk@1.0.0-beta.31': - resolution: {integrity: sha512-JIo4TvJvYhkw1aa3DsQ1rEC4zJXF9Qp7ImcdyrRTZpTXC1mxzFB0tqUC4hCGSwrbXN79q20Qr+nxo2fyO7H2Rg==} + '@semoss/sdk@1.0.0-beta.32': + resolution: {integrity: sha512-645fPMDQZxwbmzwXHlJi/JhxHT/upMRQHpHd9wrm3cfgdfjvG5MDDdPGGUlhk5xFPKANUsjnTx8WNQv9QzqvVQ==} peerDependencies: react: 18.3.1 peerDependenciesMeta: @@ -1276,6 +1294,11 @@ packages: '@tailwindcss/postcss@4.1.17': resolution: {integrity: sha512-+nKl9N9mN5uJ+M7dBOOCzINw94MPstNR/GtIhz1fpZysxL/4a+No64jCBD6CPN+bIHWFx3KWuu8XJRrj/572Dw==} + '@tailwindcss/vite@4.1.17': + resolution: {integrity: sha512-4+9w8ZHOiGnpcGI6z1TVVfWaX/koK7fKeSYF3qlYg2xpBtbteP2ddBxiarL+HVgfSJGeK5RIxRQmKm4rTJJAwA==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -1327,8 +1350,8 @@ packages: peerDependencies: postcss: ^8.1.0 - baseline-browser-mapping@2.8.31: - resolution: {integrity: sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==} + baseline-browser-mapping@2.8.32: + resolution: {integrity: sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw==} hasBin: true browserslist@4.28.0: @@ -1355,13 +1378,19 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - cookie@1.0.2: - resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + date-fns-jalali@4.1.0-0: + resolution: {integrity: sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==} + + date-fns@4.1.0: + resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -1378,8 +1407,8 @@ packages: detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} - electron-to-chromium@1.5.260: - resolution: {integrity: sha512-ov8rBoOBhVawpzdre+Cmz4FB+y66Eqrk6Gwqd8NGxuhv99GQ8XqMAr351KEkOt7gukXWDg6gJWEMKgL2RLMPtA==} + electron-to-chromium@1.5.263: + resolution: {integrity: sha512-DrqJ11Knd+lo+dv+lltvfMDLU27g14LMdH2b0O3Pio4uk0x+z7OR+JrmyacTPN2M8w3BrZ7/RTwG3R9B7irPlg==} enhanced-resolve@5.18.3: resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} @@ -1532,6 +1561,12 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + next-themes@0.4.6: + resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} + peerDependencies: + react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} @@ -1553,6 +1588,12 @@ packages: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} + react-day-picker@9.11.3: + resolution: {integrity: sha512-7lD12UvGbkyXqgzbYIGQTbl+x29B9bAf+k0pP5Dcs1evfpKk6zv4EdH/edNc8NxcmCiTNXr2HIYPrSZ3XvmVBg==} + engines: {node: '>=18'} + peerDependencies: + react: '>=16.8.0' + react-dom@18.3.1: resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} peerDependencies: @@ -1572,8 +1613,8 @@ packages: '@types/react': optional: true - react-remove-scroll@2.7.1: - resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==} + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} engines: {node: '>=10'} peerDependencies: '@types/react': '*' @@ -1582,15 +1623,15 @@ packages: '@types/react': optional: true - react-router-dom@7.9.6: - resolution: {integrity: sha512-2MkC2XSXq6HjGcihnx1s0DBWQETI4mlis4Ux7YTLvP67xnGxCvq+BcCQSO81qQHVUTM1V53tl4iVVaY5sReCOA==} + react-router-dom@7.10.0: + resolution: {integrity: sha512-Q4haR150pN/5N75O30iIsRJcr3ef7p7opFaKpcaREy0GQit6uCRu1NEiIFIwnHJQy0bsziRFBweR/5EkmHgVUQ==} engines: {node: '>=20.0.0'} peerDependencies: react: '>=18' react-dom: '>=18' - react-router@7.9.6: - resolution: {integrity: sha512-Y1tUp8clYRXpfPITyuifmSoE2vncSME18uVLgaqyxh9H35JWpIfzHo+9y3Fzh5odk/jxPW29IgLgzcdwxGqyNA==} + react-router@7.10.0: + resolution: {integrity: sha512-FVyCOH4IZ0eDDRycODfUqoN8ZSR2LbTvtx6RPsBgzvJ8xAXlMZNCrOFpu+jb8QbtZnpAd/cEki2pwE848pNGxw==} engines: {node: '>=20.0.0'} peerDependencies: react: '>=18' @@ -1628,6 +1669,12 @@ packages: set-cookie-parser@2.7.2: resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + sonner@2.0.7: + resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -1642,6 +1689,11 @@ packages: tailwind-merge@3.4.0: resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==} + tailwindcss-animate@1.0.7: + resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders' + tailwindcss@4.1.17: resolution: {integrity: sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==} @@ -1661,9 +1713,6 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tw-animate-css@1.4.0: - resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} - typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -1703,8 +1752,8 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - vite@7.2.4: - resolution: {integrity: sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==} + vite@7.2.6: + resolution: {integrity: sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -1862,6 +1911,8 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@date-fns/tz@1.4.1': {} + '@esbuild/aix-ppc64@0.25.12': optional: true @@ -2141,7 +2192,7 @@ snapshots: aria-hidden: 1.2.6 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.7.1(@types/react@18.3.27)(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.27)(react@18.3.1) optionalDependencies: '@types/react': 18.3.27 '@types/react-dom': 18.3.7(@types/react@18.3.27) @@ -2251,7 +2302,7 @@ snapshots: aria-hidden: 1.2.6 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.7.1(@types/react@18.3.27)(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.27)(react@18.3.1) optionalDependencies: '@types/react': 18.3.27 '@types/react-dom': 18.3.7(@types/react@18.3.27) @@ -2314,7 +2365,7 @@ snapshots: aria-hidden: 1.2.6 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.7.1(@types/react@18.3.27)(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.27)(react@18.3.1) optionalDependencies: '@types/react': 18.3.27 '@types/react-dom': 18.3.7(@types/react@18.3.27) @@ -2461,7 +2512,7 @@ snapshots: aria-hidden: 1.2.6 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.7.1(@types/react@18.3.27)(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.27)(react@18.3.1) optionalDependencies: '@types/react': 18.3.27 '@types/react-dom': 18.3.7(@types/react@18.3.27) @@ -2725,7 +2776,7 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.53.3': optional: true - '@semoss/sdk@1.0.0-beta.31(react@18.3.1)': + '@semoss/sdk@1.0.0-beta.32(react@18.3.1)': optionalDependencies: react: 18.3.1 @@ -2798,6 +2849,13 @@ snapshots: postcss: 8.5.6 tailwindcss: 4.1.17 + '@tailwindcss/vite@4.1.17(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0))': + dependencies: + '@tailwindcss/node': 4.1.17 + '@tailwindcss/oxide': 4.1.17 + tailwindcss: 4.1.17 + vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0) + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.28.5 @@ -2836,7 +2894,7 @@ snapshots: '@types/prop-types': 15.7.15 csstype: 3.2.3 - '@vitejs/plugin-react@5.1.1(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0))': + '@vitejs/plugin-react@5.1.1(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0))': dependencies: '@babel/core': 7.28.5 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) @@ -2844,7 +2902,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.47 '@types/babel__core': 7.20.5 react-refresh: 0.18.0 - vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0) + vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0) transitivePeerDependencies: - supports-color @@ -2865,13 +2923,13 @@ snapshots: postcss: 8.5.6 postcss-value-parser: 4.2.0 - baseline-browser-mapping@2.8.31: {} + baseline-browser-mapping@2.8.32: {} browserslist@4.28.0: dependencies: - baseline-browser-mapping: 2.8.31 + baseline-browser-mapping: 2.8.32 caniuse-lite: 1.0.30001757 - electron-to-chromium: 1.5.260 + electron-to-chromium: 1.5.263 node-releases: 2.0.27 update-browserslist-db: 1.1.4(browserslist@4.28.0) @@ -2891,10 +2949,14 @@ snapshots: convert-source-map@2.0.0: {} - cookie@1.0.2: {} + cookie@1.1.1: {} csstype@3.2.3: {} + date-fns-jalali@4.1.0-0: {} + + date-fns@4.1.0: {} + debug@4.4.3: dependencies: ms: 2.1.3 @@ -2903,7 +2965,7 @@ snapshots: detect-node-es@1.1.0: {} - electron-to-chromium@1.5.260: {} + electron-to-chromium@1.5.263: {} enhanced-resolve@5.18.3: dependencies: @@ -3033,6 +3095,11 @@ snapshots: nanoid@3.3.11: {} + next-themes@0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + node-releases@2.0.27: {} normalize-range@0.1.2: {} @@ -3049,6 +3116,13 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + react-day-picker@9.11.3(react@18.3.1): + dependencies: + '@date-fns/tz': 1.4.1 + date-fns: 4.1.0 + date-fns-jalali: 4.1.0-0 + react: 18.3.1 + react-dom@18.3.1(react@18.3.1): dependencies: loose-envify: 1.4.0 @@ -3065,7 +3139,7 @@ snapshots: optionalDependencies: '@types/react': 18.3.27 - react-remove-scroll@2.7.1(@types/react@18.3.27)(react@18.3.1): + react-remove-scroll@2.7.2(@types/react@18.3.27)(react@18.3.1): dependencies: react: 18.3.1 react-remove-scroll-bar: 2.3.8(@types/react@18.3.27)(react@18.3.1) @@ -3076,15 +3150,15 @@ snapshots: optionalDependencies: '@types/react': 18.3.27 - react-router-dom@7.9.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + react-router-dom@7.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-router: 7.9.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-router: 7.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-router@7.9.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + react-router@7.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - cookie: 1.0.2 + cookie: 1.1.1 react: 18.3.1 set-cookie-parser: 2.7.2 optionalDependencies: @@ -3138,6 +3212,11 @@ snapshots: set-cookie-parser@2.7.2: {} + sonner@2.0.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + source-map-js@1.2.1: {} source-map-support@0.5.21: @@ -3151,6 +3230,10 @@ snapshots: tailwind-merge@3.4.0: {} + tailwindcss-animate@1.0.7(tailwindcss@4.1.17): + dependencies: + tailwindcss: 4.1.17 + tailwindcss@4.1.17: {} tapable@2.3.0: {} @@ -3170,8 +3253,6 @@ snapshots: tslib@2.8.1: {} - tw-animate-css@1.4.0: {} - typescript@5.9.3: {} undici-types@7.16.0: {} @@ -3201,7 +3282,7 @@ snapshots: dependencies: react: 18.3.1 - vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0): + vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) diff --git a/client/src/App.tsx b/client/src/App.tsx index 8809b9f..b730c71 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,5 +1,6 @@ import { Env } from "@semoss/sdk"; import { InsightProvider } from "@semoss/sdk/react"; +import { Toaster } from "sonner"; import { AppContextProvider } from "./contexts"; import { Router } from "./pages"; @@ -26,6 +27,9 @@ export const App = () => { This component is custom to this project, and can be edited in Router.tsx */} + + {/* Toaster for displaying toast notifications */} + ); }; diff --git a/client/src/components/base/LoadingScreen.tsx b/client/src/components/base/LoadingScreen.tsx index 46b996a..aa7bbc1 100644 --- a/client/src/components/base/LoadingScreen.tsx +++ b/client/src/components/base/LoadingScreen.tsx @@ -1,12 +1,21 @@ import { Spinner } from "@/components/ui/spinner"; +interface LoadingScreenProps { + /** Whether to overlay the loading screen on top of existing content */ + overlay?: boolean; +} + /** * Returns a loading screen with a centered circular progress indicator * * @component */ -export const LoadingScreen = () => ( -
+export const LoadingScreen = ({ overlay = false }: LoadingScreenProps) => ( +
); diff --git a/client/src/components/base/MainNavigation.tsx b/client/src/components/base/MainNavigation.tsx index 6cacc8a..aa0e44d 100644 --- a/client/src/components/base/MainNavigation.tsx +++ b/client/src/components/base/MainNavigation.tsx @@ -1,5 +1,4 @@ import { useInsight } from "@semoss/sdk/react"; -import { useState } from "react"; import { useNavigate } from "react-router-dom"; import { SemossBlueLogo } from "@/assets"; import { Button } from "@/components/ui/button"; @@ -25,11 +24,6 @@ export const MainNavigation = () => { const { isAuthorized } = useInsight(); // Read whether the user is authorized, so that buttons only work if they are const navigate = useNavigate(); - /** - * State - */ - const [userMenuOpen, setUserMenuOpen] = useState(false); - return (
@@ -77,12 +71,7 @@ export const MainNavigation = () => {
{/* If the user is logged in, allow them to see their info */} - {isAuthorized && ( - - )} + {isAuthorized && }
); diff --git a/client/src/components/base/MessageSnackbar.tsx b/client/src/components/base/MessageSnackbar.tsx deleted file mode 100644 index afffd87..0000000 --- a/client/src/components/base/MessageSnackbar.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { AlertCircle, AlertTriangle, CheckCircle, Info, X } from "lucide-react"; -import { useEffect } from "react"; -import { Alert } from "@/components/ui/alert"; -import { Button } from "@/components/ui/button"; -import { useAppContext } from "@/contexts"; - -export interface MessageSnackbarProps { - message: string; - severity: "success" | "error" | "info" | "warning"; - open: boolean; -} - -/** - * Renders a snackbar for displaying messages, typically used for error or success notifications - * - * @component - */ -export const MessageSnackbar = ({ - open, - severity, - message, -}: MessageSnackbarProps) => { - const { setMessageSnackbarProps } = useAppContext(); - - /** - * Functions - */ - const handleClose = () => { - setMessageSnackbarProps((prev) => ({ - ...prev, - open: false, - })); - }; - - // Auto close after 5 seconds - useEffect(() => { - if (open) { - const timer = setTimeout(() => { - setMessageSnackbarProps((prev) => ({ - ...prev, - open: false, - })); - }, 5000); - return () => clearTimeout(timer); - } - }, [open, setMessageSnackbarProps]); - - const getIcon = () => { - switch (severity) { - case "success": - return ; - case "error": - return ; - case "warning": - return ; - default: - return ; - } - }; - - const getVariant = () => { - switch (severity) { - case "error": - return "destructive" as const; - default: - return "default" as const; - } - }; - - const getColors = () => { - switch (severity) { - case "success": - return "border-green-500/50 text-green-600 bg-green-50 dark:border-green-500 [&>svg]:text-green-600"; - case "warning": - return "border-yellow-500/50 text-yellow-600 bg-yellow-50 dark:border-yellow-500 [&>svg]:text-yellow-600"; - case "info": - return "border-blue-500/50 text-blue-600 bg-blue-50 dark:border-blue-500 [&>svg]:text-blue-600"; - default: - return ""; - } - }; - - if (!open) return null; - - return ( -
- - {getIcon()} -
{message}
- -
-
- ); -}; diff --git a/client/src/components/base/UserProfileMenu.tsx b/client/src/components/base/UserProfileMenu.tsx index c21c5eb..6019857 100644 --- a/client/src/components/base/UserProfileMenu.tsx +++ b/client/src/components/base/UserProfileMenu.tsx @@ -9,42 +9,26 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { useAppContext } from "@/contexts"; -import { useLoadingState } from "@/hooks"; - -export interface UserProfileMenuProps { - open: boolean; - onOpenChange?: (open: boolean) => void; -} /** * Renders a menu showing users their name and allowing them to log out * * @component */ -export const UserProfileMenu = ({ - open, - onOpenChange, -}: UserProfileMenuProps) => { +export const UserProfileMenu = () => { const { logout, userLoginName } = useAppContext(); - /** - * State - */ - const [isLogoutLoading, setIsLogoutLoading] = useLoadingState(); - /** * Functions */ const handleLogout = async () => { - const loadingKey = setIsLogoutLoading(true); const success = await logout(); if (success) localStorage.clear(); window.location.reload(); - setIsLogoutLoading(false, loadingKey); }; return ( - + + + + { + if (maxDate && date > maxDate) return true; + if (minDate && date < minDate) return true; + return false; + }} + /> + + + ); +}; diff --git a/client/src/components/library/index.ts b/client/src/components/library/index.ts new file mode 100644 index 0000000..0965b8a --- /dev/null +++ b/client/src/components/library/index.ts @@ -0,0 +1,2 @@ +export * from "./ConfirmationDialog"; +export * from "./DatePicker"; diff --git a/client/src/components/ui/alert-dialog.tsx b/client/src/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..1d22728 --- /dev/null +++ b/client/src/components/ui/alert-dialog.tsx @@ -0,0 +1,138 @@ +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"; +import * as React from "react"; +import { buttonVariants } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; + +const AlertDialog = AlertDialogPrimitive.Root; + +const AlertDialogTrigger = AlertDialogPrimitive.Trigger; + +const AlertDialogPortal = AlertDialogPrimitive.Portal; + +const AlertDialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName; + +const AlertDialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + +)); +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName; + +const AlertDialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +AlertDialogHeader.displayName = "AlertDialogHeader"; + +const AlertDialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +AlertDialogFooter.displayName = "AlertDialogFooter"; + +const AlertDialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName; + +const AlertDialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogDescription.displayName = + AlertDialogPrimitive.Description.displayName; + +const AlertDialogAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName; + +const AlertDialogCancel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName; + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +}; diff --git a/client/src/components/ui/alert.tsx b/client/src/components/ui/alert.tsx index e4ec228..4e323d4 100644 --- a/client/src/components/ui/alert.tsx +++ b/client/src/components/ui/alert.tsx @@ -1,15 +1,16 @@ import { cva, type VariantProps } from "class-variance-authority"; -import type * as React from "react"; +import * as React from "react"; + import { cn } from "@/lib/utils"; const alertVariants = cva( - "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current", + "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-card text-card-foreground", + default: "bg-background text-foreground", destructive: - "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90", + "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", }, }, defaultVariants: { @@ -18,48 +19,44 @@ const alertVariants = cva( }, ); -function Alert({ - className, - variant, - ...props -}: React.ComponentProps<"div"> & VariantProps) { - return ( -
- ); -} +const Alert = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & VariantProps +>(({ className, variant, ...props }, ref) => ( +
+)); +Alert.displayName = "Alert"; -function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ); -} +const AlertTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +AlertTitle.displayName = "AlertTitle"; -function AlertDescription({ - className, - ...props -}: React.ComponentProps<"div">) { - return ( -
- ); -} +const AlertDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +AlertDescription.displayName = "AlertDescription"; export { Alert, AlertTitle, AlertDescription }; diff --git a/client/src/components/ui/badge.tsx b/client/src/components/ui/badge.tsx index cdc838a..86bed41 100644 --- a/client/src/components/ui/badge.tsx +++ b/client/src/components/ui/badge.tsx @@ -1,21 +1,20 @@ -import { Slot } from "@radix-ui/react-slot"; import { cva, type VariantProps } from "class-variance-authority"; import type * as React from "react"; + import { cn } from "@/lib/utils"; const badgeVariants = cva( - "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden", + "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", { variants: { variant: { default: - "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90", + "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", secondary: - "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", destructive: - "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", - outline: - "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", + "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", + outline: "text-foreground", }, }, defaultVariants: { @@ -24,21 +23,13 @@ const badgeVariants = cva( }, ); -function Badge({ - className, - variant, - asChild = false, - ...props -}: React.ComponentProps<"span"> & - VariantProps & { asChild?: boolean }) { - const Comp = asChild ? Slot : "span"; +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} +function Badge({ className, variant, ...props }: BadgeProps) { return ( - +
); } diff --git a/client/src/components/ui/button.tsx b/client/src/components/ui/button.tsx index 46a3e82..083b466 100644 --- a/client/src/components/ui/button.tsx +++ b/client/src/components/ui/button.tsx @@ -4,20 +4,20 @@ import * as React from "react"; import { cn } from "@/lib/utils"; const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all cursor-pointer disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", { variants: { variant: { default: - "bg-[var(--primary)] text-[var(--primary-foreground)] hover:bg-[var(--primary)]/90", + "bg-primary text-primary-foreground hover:bg-primary/90", destructive: - "bg-[var(--destructive)] text-white hover:bg-[var(--destructive)]/90 focus-visible:ring-[var(--destructive)]/20 dark:focus-visible:ring-[var(--destructive)]/40 dark:bg-[var(--destructive)]/60", + "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", outline: - "border bg-[var(--background)] shadow-xs hover:bg-[var(--accent)] hover:text-[var(--accent-foreground)] dark:bg-[var(--input)]/30 dark:border-[var(--input)] dark:hover:bg-[var(--input)]/50", + "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", secondary: - "bg-[var(--secondary)] text-[var(--secondary-foreground)] hover:bg-[var(--secondary)]/80", - ghost: "hover:bg-[var(--accent)] hover:text-[var(--accent-foreground)] dark:hover:bg-[var(--accent)]/50", - link: "text-[var(--primary)] underline-offset-4 hover:underline", + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", + link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-9 px-4 py-2 has-[>svg]:px-3", @@ -46,9 +46,9 @@ const Button = React.forwardRef< return ( ); diff --git a/client/src/components/ui/calendar.tsx b/client/src/components/ui/calendar.tsx new file mode 100644 index 0000000..f057197 --- /dev/null +++ b/client/src/components/ui/calendar.tsx @@ -0,0 +1,229 @@ +import { + ChevronDownIcon, + ChevronLeftIcon, + ChevronRightIcon, +} from "lucide-react"; +import * as React from "react"; +import { + type DayButton, + DayPicker, + getDefaultClassNames, +} from "react-day-picker"; +import { Button, buttonVariants } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; + +function Calendar({ + className, + classNames, + showOutsideDays = true, + captionLayout = "label", + buttonVariant = "ghost", + formatters, + components, + ...props +}: React.ComponentProps & { + buttonVariant?: React.ComponentProps["variant"]; +}) { + const defaultClassNames = getDefaultClassNames(); + + return ( + svg]:rotate-180`, + String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`, + className, + )} + captionLayout={captionLayout} + formatters={{ + formatMonthDropdown: (date) => + date.toLocaleString("default", { month: "short" }), + ...formatters, + }} + classNames={{ + root: cn("w-fit min-w-[250px]", defaultClassNames.root), + months: cn( + "relative flex flex-col gap-4 md:flex-row", + defaultClassNames.months, + ), + month: cn( + "flex w-full flex-col gap-4", + defaultClassNames.month, + ), + nav: cn( + "absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1", + defaultClassNames.nav, + ), + button_previous: cn( + buttonVariants({ variant: buttonVariant }), + "h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50", + defaultClassNames.button_previous, + ), + button_next: cn( + buttonVariants({ variant: buttonVariant }), + "h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50", + defaultClassNames.button_next, + ), + month_caption: cn( + "flex h-[--cell-size] w-full items-center justify-center px-[--cell-size]", + defaultClassNames.month_caption, + ), + dropdowns: cn( + "flex h-[--cell-size] w-full items-center justify-center gap-1.5 text-sm font-medium", + defaultClassNames.dropdowns, + ), + dropdown_root: cn( + "has-focus:border-ring border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] relative rounded-md border", + defaultClassNames.dropdown_root, + ), + dropdown: cn( + "bg-popover absolute inset-0 opacity-0", + defaultClassNames.dropdown, + ), + caption_label: cn( + "select-none font-medium", + captionLayout === "label" + ? "text-sm" + : "[&>svg]:text-muted-foreground flex h-8 items-center gap-1 rounded-md pl-2 pr-1 text-sm [&>svg]:size-3.5", + defaultClassNames.caption_label, + ), + table: "w-full border-collapse", + weekdays: cn("flex", defaultClassNames.weekdays), + weekday: cn( + "text-muted-foreground flex-1 select-none rounded-md text-[0.8rem] font-normal", + defaultClassNames.weekday, + ), + week: cn("mt-2 flex w-full", defaultClassNames.week), + week_number_header: cn( + "w-[--cell-size] select-none", + defaultClassNames.week_number_header, + ), + week_number: cn( + "text-muted-foreground select-none text-[0.8rem]", + defaultClassNames.week_number, + ), + day: cn( + "group/day relative aspect-square h-full w-full select-none p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md", + defaultClassNames.day, + ), + range_start: cn( + "bg-accent rounded-l-md", + defaultClassNames.range_start, + ), + range_middle: cn( + "rounded-none", + defaultClassNames.range_middle, + ), + range_end: cn( + "bg-accent rounded-r-md", + defaultClassNames.range_end, + ), + today: cn( + "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none", + defaultClassNames.today, + ), + outside: cn( + "text-muted-foreground aria-selected:text-muted-foreground", + defaultClassNames.outside, + ), + disabled: cn( + "text-muted-foreground opacity-50", + defaultClassNames.disabled, + ), + hidden: cn("invisible", defaultClassNames.hidden), + ...classNames, + }} + components={{ + Root: ({ className, rootRef, ...props }) => { + return ( +
+ ); + }, + Chevron: ({ className, orientation, ...props }) => { + if (orientation === "left") { + return ( + + ); + } + + if (orientation === "right") { + return ( + + ); + } + + return ( + + ); + }, + DayButton: CalendarDayButton, + WeekNumber: ({ children, ...props }) => { + return ( + +
+ {children} +
+ + ); + }, + ...components, + }} + {...props} + /> + ); +} + +function CalendarDayButton({ + className, + day, + modifiers, + ...props +}: React.ComponentProps) { + const defaultClassNames = getDefaultClassNames(); + + const ref = React.useRef(null); + React.useEffect(() => { + if (modifiers.focused) ref.current?.focus(); + }, [modifiers.focused]); + + return ( +