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 (
-
+
diff --git a/client/src/components/base/index.ts b/client/src/components/base/index.ts
index 5b4dd7a..52b9297 100644
--- a/client/src/components/base/index.ts
+++ b/client/src/components/base/index.ts
@@ -1,5 +1,4 @@
export * from "./DefaultToolView";
export * from "./LoadingScreen";
export * from "./MainNavigation";
-export * from "./MessageSnackbar";
export * from "./PageWrapper";
diff --git a/client/src/components/examples/ExampleComponent.tsx b/client/src/components/examples/ExampleComponent.tsx
index 224e57a..c3cb71e 100644
--- a/client/src/components/examples/ExampleComponent.tsx
+++ b/client/src/components/examples/ExampleComponent.tsx
@@ -1,3 +1,4 @@
+import { useInsight } from "@semoss/sdk/react";
import { useState } from "react";
import { Input } from "@/components/ui/input";
import { useLoadingPixel } from "@/hooks";
@@ -16,6 +17,7 @@ export const ExampleComponent = () => {
/**
* Library hooks
*/
+ const { tool } = useInsight();
const [helloUserResponse, isLoadingHelloUser] =
useLoadingPixel("HelloUser()");
const [callPythonResponse, isLoadingCallPython] = useLoadingPixel(
@@ -68,6 +70,16 @@ export const ExampleComponent = () => {
+
+ Tool call sent from Playground:
+
+
);
};
diff --git a/client/src/components/index.ts b/client/src/components/index.ts
index 955fdd1..a2d45be 100644
--- a/client/src/components/index.ts
+++ b/client/src/components/index.ts
@@ -1 +1,2 @@
export * from "./base";
+export * from "./library";
diff --git a/client/src/components/library/ConfirmationDialog.tsx b/client/src/components/library/ConfirmationDialog.tsx
new file mode 100644
index 0000000..3b3fb29
--- /dev/null
+++ b/client/src/components/library/ConfirmationDialog.tsx
@@ -0,0 +1,39 @@
+import {
+ AlertDialog,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+} from "@/components/ui/alert-dialog";
+
+export interface ConfirmationDialogProps {
+ open: boolean;
+ title: string;
+ text: string;
+ buttons: React.ReactNode;
+}
+
+/**
+ * Reusable confirmation dialog component
+ *
+ * @component
+ */
+export const ConfirmationDialog = ({
+ open,
+ title,
+ text,
+ buttons,
+}: ConfirmationDialogProps) => {
+ return (
+
+
+
+ {title}
+ {text}
+
+ {buttons}
+
+
+ );
+};
diff --git a/client/src/components/library/DatePicker.tsx b/client/src/components/library/DatePicker.tsx
new file mode 100644
index 0000000..b639e91
--- /dev/null
+++ b/client/src/components/library/DatePicker.tsx
@@ -0,0 +1,81 @@
+import { format } from "date-fns";
+import { Calendar as CalendarIcon } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import { Calendar } from "@/components/ui/calendar";
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover";
+
+export interface DatePickerProps {
+ value: string | null; // YYYY-MM-DD
+ onChange: (value: string | null) => void;
+ label?: string;
+ placeholder?: string;
+ maxDate?: Date;
+ minDate?: Date;
+ disabled?: boolean;
+}
+
+/**
+ * Date picker component using shadcn calendar
+ *
+ * @component
+ */
+export const DatePicker = ({
+ value,
+ onChange,
+ placeholder = "Pick a date",
+ maxDate,
+ minDate,
+ disabled = false,
+}: DatePickerProps) => {
+ const dateValue = value
+ ? (() => {
+ const [year, month, day] = value.split("-").map(Number);
+ return new Date(year, month - 1, day);
+ })()
+ : undefined;
+
+ const handleSelect = (date: Date | undefined) => {
+ if (date) {
+ // Format as YYYY-MM-DD for consistency
+ const formattedDate = format(date, "yyyy-MM-dd");
+ onChange(formattedDate);
+ } else {
+ onChange(null);
+ }
+ };
+
+ 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 (
+