feat: update linter rules and improve code structure

- Added overrides in biome.json to disable specific linter rules for UI components.
- Reorganized imports and improved code readability in various UI components.
- Refactored components to use fieldset instead of div for better semantics.
- Enhanced accessibility by updating roles and aria attributes in components.
- Fixed minor issues with key props in map functions for better performance.
- Updated focus styles and improved CSS for better visual feedback.
This commit is contained in:
asepharyana
2026-07-23 17:05:08 +07:00
parent 4dbaba7529
commit e528b6a726
53 changed files with 6537 additions and 236 deletions
+30 -29
View File
@@ -1,36 +1,37 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). # asepharyana-hub-hub
## Getting Started Personal portfolio SPA — backend/infrastructure projects and live monitoring dashboard.
First, run the development server: Built with **Next.js 16**, **shadcn/ui** (base-nova), **Tailwind CSS v4**, and `next-themes`.
## Stack
- **Framework:** Next.js 16 (Turbopack, React Compiler)
- **UI:** shadcn/ui v4 (base-nova style) + Tailwind CSS v4
- **Font:** Geist (sans) + Geist Mono via `next/font`
- **Theme:** `next-themes` — light/dark with amber "server LED" focus glow (dark mode)
- **Icons:** Lucide React
- **Linting:** Biome
## Pages
| Route | Description |
|-------|-------------|
| `/` | Portfolio landing page — hero, about, skills, featured project (ZeaVis Edu), infra CTA |
| `/dashboard` | Live infrastructure monitor — Docker services, Traefik metrics, Jaeger traces, Prometheus resources |
## Theme
Twilight Terminal — warm off-white (light) / deep indigo-slate (dark) with amber-gold primary in dark mode. All component styling is driven by CSS custom properties in `globals.css`.
## Dev
```bash ```bash
npm run dev bun dev # Start dev server
# or bun run lint # Biome lint
yarn dev bun run ci # Biome CI mode
# or
pnpm dev
# or
bun dev
``` ```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. ## Deploy
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. Deployed as a Docker container behind Traefik on VPS. See `infra/` in the monorepo root for compose files and Traefik config.
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
+17 -2
View File
@@ -1,5 +1,5 @@
{ {
"$schema": "https://biomejs.dev/schemas/2.2.0/schema.json", "$schema": "https://biomejs.dev/schemas/2.5.3/schema.json",
"vcs": { "vcs": {
"enabled": true, "enabled": true,
"clientKind": "git", "clientKind": "git",
@@ -17,7 +17,7 @@
"linter": { "linter": {
"enabled": true, "enabled": true,
"rules": { "rules": {
"recommended": true, "preset": "recommended",
"suspicious": { "suspicious": {
"noUnknownAtRules": "off" "noUnknownAtRules": "off"
} }
@@ -27,6 +27,21 @@
"react": "recommended" "react": "recommended"
} }
}, },
"overrides": [
{
"includes": ["src/components/ui/**"],
"linter": {
"rules": {
"security": {
"noDangerouslySetInnerHtml": "off"
},
"suspicious": {
"noArrayIndexKey": "off"
}
}
}
}
],
"assist": { "assist": {
"actions": { "actions": {
"source": { "source": {
+24 -30
View File
@@ -13,28 +13,28 @@
"date-fns": "^4.4.0", "date-fns": "^4.4.0",
"embla-carousel-react": "^8.6.0", "embla-carousel-react": "^8.6.0",
"input-otp": "^1.4.2", "input-otp": "^1.4.2",
"lucide-react": "^1.25.0", "lucide-react": "^1.26.0",
"next": "16.2.11", "next": "16.2.11",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"react": "19.2.4", "react": "19.2.8",
"react-day-picker": "^10.0.1", "react-day-picker": "^10.0.1",
"react-dom": "19.2.4", "react-dom": "19.2.8",
"react-resizable-panels": "^4.12.2", "react-resizable-panels": "^4.12.2",
"recharts": "3.8.0", "recharts": "3.10.0",
"shadcn": "^4.14.0", "shadcn": "^4.14.0",
"sonner": "^2.0.7", "sonner": "^2.0.7",
"tailwind-merge": "^3.6.0", "tailwind-merge": "^3.6.0",
"tw-animate-css": "^1.4.0", "tw-animate-css": "^1.4.0",
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "2.2.0", "@biomejs/biome": "2.5.5",
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",
"@types/node": "^20", "@types/node": "^26",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
"babel-plugin-react-compiler": "1.0.0", "babel-plugin-react-compiler": "1.0.0",
"tailwindcss": "^4", "tailwindcss": "^4",
"typescript": "^5", "typescript": "^5.9.3",
}, },
}, },
}, },
@@ -106,23 +106,23 @@
"@base-ui/utils": ["@base-ui/utils@0.3.1", "", { "dependencies": { "@babel/runtime": "^7.29.2", "@floating-ui/utils": "^0.2.11", "reselect": "^5.2.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-gFFiltORVmW/N6IILTGxizP3PBpVpysqML1ALY5Vk0mH+7faVkCknOU31goYHN5Aoek2dkjxva1XOD2Ce9WuIg=="], "@base-ui/utils": ["@base-ui/utils@0.3.1", "", { "dependencies": { "@babel/runtime": "^7.29.2", "@floating-ui/utils": "^0.2.11", "reselect": "^5.2.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-gFFiltORVmW/N6IILTGxizP3PBpVpysqML1ALY5Vk0mH+7faVkCknOU31goYHN5Aoek2dkjxva1XOD2Ce9WuIg=="],
"@biomejs/biome": ["@biomejs/biome@2.2.0", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.2.0", "@biomejs/cli-darwin-x64": "2.2.0", "@biomejs/cli-linux-arm64": "2.2.0", "@biomejs/cli-linux-arm64-musl": "2.2.0", "@biomejs/cli-linux-x64": "2.2.0", "@biomejs/cli-linux-x64-musl": "2.2.0", "@biomejs/cli-win32-arm64": "2.2.0", "@biomejs/cli-win32-x64": "2.2.0" }, "bin": { "biome": "bin/biome" } }, "sha512-3On3RSYLsX+n9KnoSgfoYlckYBoU6VRM22cw1gB4Y0OuUVSYd/O/2saOJMrA4HFfA1Ff0eacOvMN1yAAvHtzIw=="], "@biomejs/biome": ["@biomejs/biome@2.5.5", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.5.5", "@biomejs/cli-darwin-x64": "2.5.5", "@biomejs/cli-linux-arm64": "2.5.5", "@biomejs/cli-linux-arm64-musl": "2.5.5", "@biomejs/cli-linux-x64": "2.5.5", "@biomejs/cli-linux-x64-musl": "2.5.5", "@biomejs/cli-win32-arm64": "2.5.5", "@biomejs/cli-win32-x64": "2.5.5" }, "bin": { "biome": "bin/biome" } }, "sha512-r1S8nFsAG1MY+vJFZALzIvwXAJv6ejDQ0mxP21Tgr9YK3ZFtjrvbBwDdNhx1rUqvccEIeNg20cYCNzl6Cr69pQ=="],
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.2.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zKbwUUh+9uFmWfS8IFxmVD6XwqFcENjZvEyfOxHs1epjdH3wyyMQG80FGDsmauPwS2r5kXdEM0v/+dTIA9FXAg=="], "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.5.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kUrAhXVWUrwmAUnV2iXSK7umxKFysTwvqK+Ty6ptUcLY/7T3SnCAjUowE4uvwaEej6nXZ7hu/dTtbokKdsPeag=="],
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.2.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-+OmT4dsX2eTfhD5crUOPw3RPhaR+SKVspvGVmSdZ9y9O/AgL8pla6T4hOn1q+VAFBHuHhsdxDRJgFCSC7RaMOw=="], "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.5.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-DamiYc5bUYZ2uxlfc+RLEPtz1Abb6PO5eTbOkufLpSGwd/7AMQAdxhFYiXmwwkJL8IsT8S7GvdgwDHqaMFAvKw=="],
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.2.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-6eoRdF2yW5FnW9Lpeivh7Mayhq0KDdaDMYOJnH9aT02KuSIX5V1HmWJCQQPwIQbhDh68Zrcpl8inRlTEan0SXw=="], "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.5.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-lRKF/pH/1RiYiBKExi3TCZVAtvzEm77aifrvcNiDFrR9WxeAnDUjDnseb6y2XV85mjitLs6SILGm2XG77cHtSQ=="],
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.2.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-egKpOa+4FL9YO+SMUMLUvf543cprjevNc3CAgDNFLcjknuNMcZ0GLJYa3EGTCR2xIkIUJDVneBV3O9OcIlCEZQ=="], "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.5.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-U4WMl/sy/E/Q73vf15VspakLRRs2LDFcCeBxJnQfXzssb88zpV6PJPaQ3ezhQ7H6Ht2/8bvuZeHgJWzmoxllZg=="],
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.2.0", "", { "os": "linux", "cpu": "x64" }, "sha512-5UmQx/OZAfJfi25zAnAGHUMuOd+LOsliIt119x2soA2gLggQYrVPA+2kMUxR6Mw5M1deUF/AWWP2qpxgH7Nyfw=="], "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.5.5", "", { "os": "linux", "cpu": "x64" }, "sha512-H/O39nJEw/2Zm/fm7hrmxxoF8kK/aU1uCoPp70ruXVbomaAdLpJJnCmL11Q2JotT8QVHH06So04Oq53lCSwSwQ=="],
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.2.0", "", { "os": "linux", "cpu": "x64" }, "sha512-I5J85yWwUWpgJyC1CcytNSGusu2p9HjDnOPAFG4Y515hwRD0jpR9sT9/T1cKHtuCvEQ/sBvx+6zhz9l9wEJGAg=="], "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.5.5", "", { "os": "linux", "cpu": "x64" }, "sha512-m7wC7tjX5Lrmo69dc4md8FeKpPU1NTCY1v7xUoQQ2vadWwNnBS0KZOG8471otFPHrTHihQJAjQPgMObpLvDe6A=="],
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.2.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-n9a1/f2CwIDmNMNkFs+JI0ZjFnMO0jdOyGNtihgUNFnlmd84yIYY2KMTBmMV58ZlVHjgmY5Y6E1hVTnSRieggA=="], "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.5.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-7BryINPuYypLUAH3o/o5ZdgomJ4zn3EDR0ChZJst7n32S6ZhKbgHXuYydLu+YAnx59ehGFR0z/MG6qnzQi3Yyw=="],
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.2.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Nawu5nHjP/zPKTIryh2AavzTc/KEg4um/MxWdXW0A6P/RZOyIpa7+QSjeXwAwX/utJGaCoXRPWtF3m5U/bB3Ww=="], "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.5.5", "", { "os": "win32", "cpu": "x64" }, "sha512-bIBFo+n6MIxdNcVFy5CrurbKiZQiUciK3bt8+O9I4wjFZNTfXLpi+giq47522eXqW5NBc9ulx7dR1SlZKi2J5g=="],
"@date-fns/tz": ["@date-fns/tz@1.5.0", "", {}, "sha512-lwYN/vDPeNRULcepoE/LO2Pgx+7/RV+S9ARfbc9lr2DtGkOD7pAiruHvbR1RX3Qyf6ja47EWJDMsNK5vK08DJg=="], "@date-fns/tz": ["@date-fns/tz@1.5.0", "", {}, "sha512-lwYN/vDPeNRULcepoE/LO2Pgx+7/RV+S9ARfbc9lr2DtGkOD7pAiruHvbR1RX3Qyf6ja47EWJDMsNK5vK08DJg=="],
@@ -324,7 +324,7 @@
"@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="], "@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="],
"@types/node": ["@types/node@20.19.43", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA=="], "@types/node": ["@types/node@26.1.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw=="],
"@types/react": ["@types/react@19.2.17", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw=="], "@types/react": ["@types/react@19.2.17", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw=="],
@@ -584,7 +584,7 @@
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
"immer": ["immer@10.2.0", "", {}, "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw=="], "immer": ["immer@11.1.15", "", {}, "sha512-VrNANlmnWQnh5COXIIOQXM9oOJw7naGKlBT74ZOOR6lpVXc3gFEu9FJLDFcpCJ2j+NWr8TIwtWD//T6ZX6TKiQ=="],
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
@@ -684,7 +684,7 @@
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
"lucide-react": ["lucide-react@1.25.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-/mdJTRbiwcLOQ1NZZK1amZF9rIZyvO18D6r9TngE6TG1NmqHgFuT4eE7Xrkm9UsXMbBJD1NlfwHVltCDWHrOTw=="], "lucide-react": ["lucide-react@1.26.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-raglYVR2+VkMfJL158krjVmE+rV5ST2lzA/KQm1FRSjMHT4MnWaegHxoVEpmc2So3nOEhp9oGejJwAPX8MoAjg=="],
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
@@ -792,11 +792,11 @@
"raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="], "raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
"react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], "react": ["react@19.2.8", "", {}, "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw=="],
"react-day-picker": ["react-day-picker@10.0.1", "", { "dependencies": { "@date-fns/tz": "^1.4.1", "date-fns": "^4.1.0" }, "peerDependencies": { "@types/react": ">=16.8.0", "react": ">=16.8.0" }, "optionalPeers": ["@types/react"] }, "sha512-eNh6BlwcYInWaJtRv18mXQ06Ys/H6rdTZAnTaSdOYJuTpwP1JMCHNd1FDRadA+gbeinq+psdULN5Xnowy9mV8w=="], "react-day-picker": ["react-day-picker@10.0.1", "", { "dependencies": { "@date-fns/tz": "^1.4.1", "date-fns": "^4.1.0" }, "peerDependencies": { "@types/react": ">=16.8.0", "react": ">=16.8.0" }, "optionalPeers": ["@types/react"] }, "sha512-eNh6BlwcYInWaJtRv18mXQ06Ys/H6rdTZAnTaSdOYJuTpwP1JMCHNd1FDRadA+gbeinq+psdULN5Xnowy9mV8w=="],
"react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], "react-dom": ["react-dom@19.2.8", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.8" } }, "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ=="],
"react-is": ["react-is@19.2.8", "", {}, "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ=="], "react-is": ["react-is@19.2.8", "", {}, "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ=="],
@@ -812,7 +812,7 @@
"recast": ["recast@0.23.12", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-dEWRjcINDu/F4l2dYx57ugBtD7HV9KXESyxhzw/MqWLeglJrsjJKqACPyUPg+6AF8mIgm+Zi0dZ3ACoIg+QtpA=="], "recast": ["recast@0.23.12", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-dEWRjcINDu/F4l2dYx57ugBtD7HV9KXESyxhzw/MqWLeglJrsjJKqACPyUPg+6AF8mIgm+Zi0dZ3ACoIg+QtpA=="],
"recharts": ["recharts@3.8.0", "", { "dependencies": { "@reduxjs/toolkit": "^1.9.0 || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", "eventemitter3": "^5.0.1", "immer": "^10.1.1", "react-redux": "8.x.x || 9.x.x", "reselect": "5.1.1", "tiny-invariant": "^1.3.3", "use-sync-external-store": "^1.2.2", "victory-vendor": "^37.0.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Z/m38DX3L73ExO4Tpc9/iZWHmHnlzWG4njQbxsF5aSjwqmHNDDIm0rdEBArkwsBvR8U6EirlEHiQNYWCVh9sGQ=="], "recharts": ["recharts@3.10.0", "", { "dependencies": { "@reduxjs/toolkit": "^1.9.0 || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", "eventemitter3": "^5.0.1", "immer": "^11.1.8", "react-redux": "8.x.x || 9.x.x", "reselect": "5.2.0", "tiny-invariant": "^1.3.3", "use-sync-external-store": "^1.2.2", "victory-vendor": "^37.0.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-wulMvfncpIlmu2uFtRU/mE5/+NiVtASXkw2KdwJTdHs3WsASX0WxZlX+rpKgyn5BDbIhkPtCpUKkB9XNK5KE0w=="],
"redux": ["redux@5.0.1", "", {}, "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="], "redux": ["redux@5.0.1", "", {}, "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="],
@@ -820,7 +820,7 @@
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
"reselect": ["reselect@5.1.1", "", {}, "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="], "reselect": ["reselect@5.2.0", "", {}, "sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw=="],
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
@@ -916,7 +916,7 @@
"undici": ["undici@7.28.0", "", {}, "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA=="], "undici": ["undici@7.28.0", "", {}, "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA=="],
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], "undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
"unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], "unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="],
@@ -962,18 +962,12 @@
"@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"@base-ui/utils/reselect": ["reselect@5.2.0", "", {}, "sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw=="],
"@dotenvx/dotenvx/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], "@dotenvx/dotenvx/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
"@dotenvx/dotenvx/execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="], "@dotenvx/dotenvx/execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="],
"@dotenvx/dotenvx/open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="], "@dotenvx/dotenvx/open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="],
"@reduxjs/toolkit/immer": ["immer@11.1.15", "", {}, "sha512-VrNANlmnWQnh5COXIIOQXM9oOJw7naGKlBT74ZOOR6lpVXc3gFEu9FJLDFcpCJ2j+NWr8TIwtWD//T6ZX6TKiQ=="],
"@reduxjs/toolkit/reselect": ["reselect@5.2.0", "", {}, "sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA=="],
+6292
View File
File diff suppressed because it is too large Load Diff
+7 -7
View File
@@ -18,28 +18,28 @@
"date-fns": "^4.4.0", "date-fns": "^4.4.0",
"embla-carousel-react": "^8.6.0", "embla-carousel-react": "^8.6.0",
"input-otp": "^1.4.2", "input-otp": "^1.4.2",
"lucide-react": "^1.25.0", "lucide-react": "^1.26.0",
"next": "16.2.11", "next": "16.2.11",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"react": "19.2.4", "react": "19.2.8",
"react-day-picker": "^10.0.1", "react-day-picker": "^10.0.1",
"react-dom": "19.2.4", "react-dom": "19.2.8",
"react-resizable-panels": "^4.12.2", "react-resizable-panels": "^4.12.2",
"recharts": "3.8.0", "recharts": "3.10.0",
"shadcn": "^4.14.0", "shadcn": "^4.14.0",
"sonner": "^2.0.7", "sonner": "^2.0.7",
"tailwind-merge": "^3.6.0", "tailwind-merge": "^3.6.0",
"tw-animate-css": "^1.4.0" "tw-animate-css": "^1.4.0"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "2.2.0", "@biomejs/biome": "2.5.5",
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",
"@types/node": "^20", "@types/node": "^26",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
"babel-plugin-react-compiler": "1.0.0", "babel-plugin-react-compiler": "1.0.0",
"tailwindcss": "^4", "tailwindcss": "^4",
"typescript": "^5" "typescript": "^5.9.3"
}, },
"ignoreScripts": [ "ignoreScripts": [
"sharp", "sharp",
+5 -3
View File
@@ -1,5 +1,5 @@
import { NextResponse } from "next/server";
import http from "node:http"; import http from "node:http";
import { NextResponse } from "next/server";
const JAEGER = "http://jaeger:16686"; const JAEGER = "http://jaeger:16686";
const PROMETHEUS = "http://prometheus:9090"; const PROMETHEUS = "http://prometheus:9090";
@@ -31,7 +31,9 @@ function dockerFetch(path: string): Promise<unknown> {
http http
.get({ socketPath: DOCKER_SOCK, path }, (res) => { .get({ socketPath: DOCKER_SOCK, path }, (res) => {
let data = ""; let data = "";
res.on("data", (c: string) => (data += c)); res.on("data", (c: string) => {
data += c;
});
res.on("end", () => { res.on("end", () => {
try { try {
resolve(JSON.parse(data)); resolve(JSON.parse(data));
@@ -129,7 +131,7 @@ async function promRange(query: string, steps = 20): Promise<number[]> {
if (!result?.[0]?.values) return []; if (!result?.[0]?.values) return [];
return result[0].values.map((v) => { return result[0].values.map((v) => {
const f = parseFloat(v[1] as string); const f = parseFloat(v[1] as string);
return isNaN(f) ? 0 : f; return Number.isNaN(f) ? 0 : f;
}); });
} }
+3 -1
View File
@@ -133,7 +133,9 @@
.dark *:focus-visible { .dark *:focus-visible {
@apply ring-[3px]; @apply ring-[3px];
outline: none; outline: none;
box-shadow: 0 0 8px oklch(0.7 0.15 75 / 0.3), 0 0 0 3px oklch(0.7 0.15 75 / 0.5); box-shadow:
0 0 8px oklch(0.7 0.15 75 / 0.3),
0 0 0 3px oklch(0.7 0.15 75 / 0.5);
} }
html:not(.dark) *:focus-visible { html:not(.dark) *:focus-visible {
@apply ring-[3px]; @apply ring-[3px];
+6 -1
View File
@@ -32,7 +32,12 @@ export default function RootLayout({
suppressHydrationWarning suppressHydrationWarning
> >
<body className="flex min-h-full flex-col bg-background text-foreground"> <body className="flex min-h-full flex-col bg-background text-foreground">
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange> <ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<DashboardHeader /> <DashboardHeader />
{children} {children}
</ThemeProvider> </ThemeProvider>
+8 -8
View File
@@ -98,15 +98,15 @@ export default function Home() {
I&rsquo;m a Teknik Informatika student passionate about backend I&rsquo;m a Teknik Informatika student passionate about backend
engineering, distributed systems, and infrastructure. I spend my engineering, distributed systems, and infrastructure. I spend my
time building RESTful APIs, learning container orchestration with time building RESTful APIs, learning container orchestration with
Docker &amp; Traefik, and exploring observability with OpenTelemetry Docker &amp; Traefik, and exploring observability with
&amp; Jaeger. OpenTelemetry &amp; Jaeger.
</p> </p>
<p> <p>
I was the Back-End lead on the ZeaVis Edu capstone team (&ldquo;AI I was the Back-End lead on the ZeaVis Edu capstone team (&ldquo;AI
for Smart Education&rdquo; by Pijak &times; IBM SkillsBuild), for Smart Education&rdquo; by Pijak &times; IBM SkillsBuild), a
a 5-person team with task division across Back-End, Front-End, 5-person team with task division across Back-End, Front-End, and
and Machine Learning. I handled the system architecture, API Machine Learning. I handled the system architecture, API design,
design, RESTful API development, and Docker deployment on VPS. RESTful API development, and Docker deployment on VPS.
</p> </p>
</div> </div>
</div> </div>
@@ -260,8 +260,8 @@ export default function Home() {
</p> </p>
<p className="max-w-md text-sm text-muted-foreground"> <p className="max-w-md text-sm text-muted-foreground">
The dashboard tracks Docker services, Traefik request metrics, The dashboard tracks Docker services, Traefik request metrics,
Prometheus system resources, and Jaeger traces all running on Prometheus system resources, and Jaeger traces all running on my
my VPS deployment. VPS deployment.
</p> </p>
<a href="/dashboard" className={cn(buttonVariants())}> <a href="/dashboard" className={cn(buttonVariants())}>
Live Dashboard Live Dashboard
+1 -2
View File
@@ -1,7 +1,6 @@
import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion"; import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion";
import { cn } from "@/lib/utils";
import { ChevronDownIcon, ChevronUpIcon } from "lucide-react"; import { ChevronDownIcon, ChevronUpIcon } from "lucide-react";
import { cn } from "@/lib/utils";
function Accordion({ className, ...props }: AccordionPrimitive.Root.Props) { function Accordion({ className, ...props }: AccordionPrimitive.Root.Props) {
return ( return (
+2 -3
View File
@@ -1,10 +1,9 @@
"use client"; "use client";
import * as React from "react";
import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog"; import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog";
import type * as React from "react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
function AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) { function AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) {
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />; return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />;
+1 -1
View File
@@ -1,5 +1,5 @@
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
+2 -3
View File
@@ -1,10 +1,9 @@
import * as React from "react";
import { mergeProps } from "@base-ui/react/merge-props"; import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render"; import { useRender } from "@base-ui/react/use-render";
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import type * as React from "react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
const attachmentVariants = cva( const attachmentVariants = cva(
"group/attachment relative flex w-fit max-w-full min-w-0 shrink-0 flex-wrap rounded-xl border bg-card text-card-foreground transition-colors focus-within:ring-1 focus-within:ring-ring/50 has-[>a,>button]:hover:bg-muted/50 data-[state=error]:border-destructive/30 data-[state=idle]:border-dashed", "group/attachment relative flex w-fit max-w-full min-w-0 shrink-0 flex-wrap rounded-xl border bg-card text-card-foreground transition-colors focus-within:ring-1 focus-within:ring-ring/50 has-[>a,>button]:hover:bg-muted/50 data-[state=error]:border-destructive/30 data-[state=idle]:border-dashed",
+1 -1
View File
@@ -1,7 +1,7 @@
"use client"; "use client";
import * as React from "react";
import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar"; import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar";
import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
+2 -5
View File
@@ -1,9 +1,8 @@
import * as React from "react";
import { mergeProps } from "@base-ui/react/merge-props"; import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render"; import { useRender } from "@base-ui/react/use-render";
import { cn } from "@/lib/utils";
import { ChevronRightIcon, MoreHorizontalIcon } from "lucide-react"; import { ChevronRightIcon, MoreHorizontalIcon } from "lucide-react";
import type * as React from "react";
import { cn } from "@/lib/utils";
function Breadcrumb({ className, ...props }: React.ComponentProps<"nav">) { function Breadcrumb({ className, ...props }: React.ComponentProps<"nav">) {
return ( return (
@@ -63,8 +62,6 @@ function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
return ( return (
<span <span
data-slot="breadcrumb-page" data-slot="breadcrumb-page"
role="link"
aria-disabled="true"
aria-current="page" aria-current="page"
className={cn("font-normal text-foreground", className)} className={cn("font-normal text-foreground", className)}
{...props} {...props}
+1 -1
View File
@@ -1,7 +1,7 @@
import * as React from "react";
import { mergeProps } from "@base-ui/react/merge-props"; import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render"; import { useRender } from "@base-ui/react/use-render";
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
+4 -6
View File
@@ -1,12 +1,11 @@
import { mergeProps } from "@base-ui/react/merge-props"; import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render"; import { useRender } from "@base-ui/react/use-render";
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils";
const buttonGroupVariants = cva( const buttonGroupVariants = cva(
"flex w-fit items-stretch *:focus-visible:relative *:focus-visible:z-10 has-[>[data-slot=button-group]]:gap-2 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-lg [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1", "flex w-fit items-stretch border-0 p-0 m-0 *:focus-visible:relative *:focus-visible:z-10 has-[>[data-slot=button-group]]:gap-2 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-lg [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
{ {
variants: { variants: {
orientation: { orientation: {
@@ -26,10 +25,9 @@ function ButtonGroup({
className, className,
orientation, orientation,
...props ...props
}: React.ComponentProps<"div"> & VariantProps<typeof buttonGroupVariants>) { }: React.ComponentProps<"fieldset"> & VariantProps<typeof buttonGroupVariants>) {
return ( return (
<div <fieldset
role="group"
data-slot="button-group" data-slot="button-group"
data-orientation={orientation} data-orientation={orientation}
className={cn(buttonGroupVariants({ orientation }), className)} className={cn(buttonGroupVariants({ orientation }), className)}
+10 -11
View File
@@ -1,20 +1,19 @@
"use client"; "use client";
import * as React from "react";
import {
DayPicker,
getDefaultClassNames,
type DayButton,
type Locale,
} from "react-day-picker";
import { cn } from "@/lib/utils";
import { Button, buttonVariants } from "@/components/ui/button";
import { import {
ChevronDownIcon,
ChevronLeftIcon, ChevronLeftIcon,
ChevronRightIcon, ChevronRightIcon,
ChevronDownIcon,
} from "lucide-react"; } from "lucide-react";
import * as React from "react";
import {
type DayButton,
DayPicker,
getDefaultClassNames,
type Locale,
} from "react-day-picker";
import { Button, buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils";
function Calendar({ function Calendar({
className, className,
+1 -1
View File
@@ -1,4 +1,4 @@
import * as React from "react"; import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
+9 -11
View File
@@ -1,13 +1,12 @@
"use client"; "use client";
import * as React from "react";
import useEmblaCarousel, { import useEmblaCarousel, {
type UseEmblaCarouselType, type UseEmblaCarouselType,
} from "embla-carousel-react"; } from "embla-carousel-react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"; import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
import * as React from "react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
type CarouselApi = UseEmblaCarouselType[1]; type CarouselApi = UseEmblaCarouselType[1];
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>; type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
@@ -118,16 +117,16 @@ function Carousel({
canScrollNext, canScrollNext,
}} }}
> >
<div {/* biome-ignore lint/a11y/useAriaPropsSupportedByRole: <section> has implicit role="region" which supports aria-roledescription */}
<section
onKeyDownCapture={handleKeyDown} onKeyDownCapture={handleKeyDown}
className={cn("relative", className)} className={cn("relative", className)}
role="region"
aria-roledescription="carousel" aria-roledescription="carousel"
data-slot="carousel" data-slot="carousel"
{...props} {...props}
> >
{children} {children}
</div> </section>
</CarouselContext.Provider> </CarouselContext.Provider>
); );
} }
@@ -153,16 +152,15 @@ function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
); );
} }
function CarouselItem({ className, ...props }: React.ComponentProps<"div">) { function CarouselItem({ className, ...props }: React.ComponentProps<"fieldset">) {
const { orientation } = useCarousel(); const { orientation } = useCarousel();
return ( return (
<div <fieldset
role="group"
aria-roledescription="slide" aria-roledescription="slide"
data-slot="carousel-item" data-slot="carousel-item"
className={cn( className={cn(
"min-w-0 shrink-0 grow-0 basis-full", "min-w-0 shrink-0 grow-0 basis-full border-0 p-0 m-0",
orientation === "horizontal" ? "pl-4" : "pt-4", orientation === "horizontal" ? "pl-4" : "pt-4",
className, className,
)} )}
+11 -7
View File
@@ -1,8 +1,8 @@
"use client"; "use client";
import * as React from "react"; import * as React from "react";
import * as RechartsPrimitive from "recharts";
import type { TooltipValueType } from "recharts"; import type { TooltipValueType } from "recharts";
import * as RechartsPrimitive from "recharts";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -200,13 +200,17 @@ function ChartTooltipContent({
{payload {payload
.filter((item) => item.type !== "none") .filter((item) => item.type !== "none")
.map((item, index) => { .map((item, index) => {
const key = `${nameKey ?? item.name ?? item.dataKey ?? "value"}`; const itemKey = `${nameKey ?? item.name ?? item.dataKey ?? "value"}-${index}`;
const itemConfig = getPayloadConfigFromPayload(config, item, key); const itemConfig = getPayloadConfigFromPayload(
config,
item,
itemKey,
);
const indicatorColor = color ?? item.payload?.fill ?? item.color; const indicatorColor = color ?? item.payload?.fill ?? item.color;
return ( return (
<div <div
key={index} key={itemKey}
className={cn( className={cn(
"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground", "flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
indicator === "dot" && "items-center", indicator === "dot" && "items-center",
@@ -299,12 +303,12 @@ function ChartLegendContent({
{payload {payload
.filter((item) => item.type !== "none") .filter((item) => item.type !== "none")
.map((item, index) => { .map((item, index) => {
const key = `${nameKey ?? item.dataKey ?? "value"}`; const itemKey = `${nameKey ?? item.dataKey ?? "value"}-${index}`;
const itemConfig = getPayloadConfigFromPayload(config, item, key); const itemConfig = getPayloadConfigFromPayload(config, item, itemKey);
return ( return (
<div <div
key={index} key={itemKey}
className={cn( className={cn(
"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground", "flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground",
)} )}
+1 -2
View File
@@ -1,9 +1,8 @@
"use client"; "use client";
import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox"; import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox";
import { cn } from "@/lib/utils";
import { CheckIcon } from "lucide-react"; import { CheckIcon } from "lucide-react";
import { cn } from "@/lib/utils";
function Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) { function Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {
return ( return (
+3 -4
View File
@@ -1,9 +1,8 @@
"use client"; "use client";
import * as React from "react";
import { Combobox as ComboboxPrimitive } from "@base-ui/react"; import { Combobox as ComboboxPrimitive } from "@base-ui/react";
import { CheckIcon, ChevronDownIcon, XIcon } from "lucide-react";
import { cn } from "@/lib/utils"; import * as React from "react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
InputGroup, InputGroup,
@@ -11,7 +10,7 @@ import {
InputGroupButton, InputGroupButton,
InputGroupInput, InputGroupInput,
} from "@/components/ui/input-group"; } from "@/components/ui/input-group";
import { ChevronDownIcon, XIcon, CheckIcon } from "lucide-react"; import { cn } from "@/lib/utils";
const Combobox = ComboboxPrimitive.Root; const Combobox = ComboboxPrimitive.Root;
+3 -4
View File
@@ -1,9 +1,8 @@
"use client"; "use client";
import * as React from "react";
import { Command as CommandPrimitive } from "cmdk"; import { Command as CommandPrimitive } from "cmdk";
import { CheckIcon, SearchIcon } from "lucide-react";
import { cn } from "@/lib/utils"; import type * as React from "react";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
@@ -12,7 +11,7 @@ import {
DialogTitle, DialogTitle,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { InputGroup, InputGroupAddon } from "@/components/ui/input-group"; import { InputGroup, InputGroupAddon } from "@/components/ui/input-group";
import { SearchIcon, CheckIcon } from "lucide-react"; import { cn } from "@/lib/utils";
function Command({ function Command({
className, className,
+2 -3
View File
@@ -1,10 +1,9 @@
"use client"; "use client";
import * as React from "react";
import { ContextMenu as ContextMenuPrimitive } from "@base-ui/react/context-menu"; import { ContextMenu as ContextMenuPrimitive } from "@base-ui/react/context-menu";
import { CheckIcon, ChevronRightIcon } from "lucide-react";
import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { ChevronRightIcon, CheckIcon } from "lucide-react";
function ContextMenu({ ...props }: ContextMenuPrimitive.Root.Props) { function ContextMenu({ ...props }: ContextMenuPrimitive.Root.Props) {
return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />; return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />;
+3 -4
View File
@@ -1,11 +1,10 @@
"use client"; "use client";
import * as React from "react";
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"; import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { XIcon } from "lucide-react"; import { XIcon } from "lucide-react";
import type * as React from "react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
function Dialog({ ...props }: DialogPrimitive.Root.Props) { function Dialog({ ...props }: DialogPrimitive.Root.Props) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />; return <DialogPrimitive.Root data-slot="dialog" {...props} />;
+1 -1
View File
@@ -1,7 +1,7 @@
"use client"; "use client";
import * as React from "react";
import { Drawer as DrawerPrimitive } from "@base-ui/react/drawer"; import { Drawer as DrawerPrimitive } from "@base-ui/react/drawer";
import * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
+2 -3
View File
@@ -1,10 +1,9 @@
"use client"; "use client";
import * as React from "react";
import { Menu as MenuPrimitive } from "@base-ui/react/menu"; import { Menu as MenuPrimitive } from "@base-ui/react/menu";
import { CheckIcon, ChevronRightIcon } from "lucide-react";
import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { ChevronRightIcon, CheckIcon } from "lucide-react";
function DropdownMenu({ ...props }: MenuPrimitive.Root.Props) { function DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {
return <MenuPrimitive.Root data-slot="dropdown-menu" {...props} />; return <MenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
+9 -9
View File
@@ -1,11 +1,10 @@
"use client"; "use client";
import { useMemo } from "react";
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import { useMemo } from "react";
import { cn } from "@/lib/utils";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils";
function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) { function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
return ( return (
@@ -52,7 +51,7 @@ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
} }
const fieldVariants = cva( const fieldVariants = cva(
"group/field flex w-full gap-2 data-[invalid=true]:text-destructive", "group/field flex w-full gap-2 border-0 p-0 m-0 data-[invalid=true]:text-destructive",
{ {
variants: { variants: {
orientation: { orientation: {
@@ -73,10 +72,9 @@ function Field({
className, className,
orientation = "vertical", orientation = "vertical",
...props ...props
}: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) { }: React.ComponentProps<"fieldset"> & VariantProps<typeof fieldVariants>) {
return ( return (
<div <fieldset
role="group"
data-slot="field" data-slot="field"
data-orientation={orientation} data-orientation={orientation}
className={cn(fieldVariants({ orientation }), className)} className={cn(fieldVariants({ orientation }), className)}
@@ -194,7 +192,7 @@ function FieldError({
...new Map(errors.map((error) => [error?.message, error])).values(), ...new Map(errors.map((error) => [error?.message, error])).values(),
]; ];
if (uniqueErrors?.length == 1) { if (uniqueErrors?.length === 1) {
return uniqueErrors[0]?.message; return uniqueErrors[0]?.message;
} }
@@ -202,7 +200,9 @@ function FieldError({
<ul className="ml-4 flex list-disc flex-col gap-1"> <ul className="ml-4 flex list-disc flex-col gap-1">
{uniqueErrors.map( {uniqueErrors.map(
(error, index) => (error, index) =>
error?.message && <li key={index}>{error.message}</li>, error?.message && (
<li key={`${error.message}-${index}`}>{error.message}</li>
),
)} )}
</ul> </ul>
); );
+17 -9
View File
@@ -1,18 +1,16 @@
"use client"; "use client";
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import type * as React from "react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { cn } from "@/lib/utils";
function InputGroup({ className, ...props }: React.ComponentProps<"div">) { function InputGroup({ className, ...props }: React.ComponentProps<"fieldset">) {
return ( return (
<div <fieldset
data-slot="input-group" data-slot="input-group"
role="group"
className={cn( className={cn(
"group/input-group relative flex h-8 w-full min-w-0 items-center rounded-lg border border-input transition-colors outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-disabled:bg-input/50 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto dark:bg-input/30 dark:has-disabled:bg-input/80 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5", "group/input-group relative flex h-8 w-full min-w-0 items-center rounded-lg border border-input transition-colors outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-disabled:bg-input/50 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto dark:bg-input/30 dark:has-disabled:bg-input/80 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5",
className, className,
@@ -47,19 +45,29 @@ function InputGroupAddon({
className, className,
align = "inline-start", align = "inline-start",
...props ...props
}: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) { }: React.ComponentProps<"button"> &
VariantProps<typeof inputGroupAddonVariants>) {
return ( return (
<div <button
role="group" type="button"
data-slot="input-group-addon" data-slot="input-group-addon"
data-align={align} data-align={align}
className={cn(inputGroupAddonVariants({ align }), className)} className={cn(inputGroupAddonVariants({ align }), className)}
tabIndex={-1}
onClick={(e) => { onClick={(e) => {
if ((e.target as HTMLElement).closest("button")) { if ((e.target as HTMLElement).closest("button")) {
return; return;
} }
e.currentTarget.parentElement?.querySelector("input")?.focus(); e.currentTarget.parentElement?.querySelector("input")?.focus();
}} }}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
if ((e.target as HTMLElement).closest("button")) {
return;
}
e.currentTarget.parentElement?.querySelector("input")?.focus();
}
}}
{...props} {...props}
/> />
); );
+3 -4
View File
@@ -1,10 +1,9 @@
"use client"; "use client";
import * as React from "react";
import { OTPInput, OTPInputContext } from "input-otp"; import { OTPInput, OTPInputContext } from "input-otp";
import { cn } from "@/lib/utils";
import { MinusIcon } from "lucide-react"; import { MinusIcon } from "lucide-react";
import * as React from "react";
import { cn } from "@/lib/utils";
function InputOTP({ function InputOTP({
className, className,
@@ -75,7 +74,7 @@ function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
<div <div
data-slot="input-otp-separator" data-slot="input-otp-separator"
className="flex items-center [&_svg:not([class*='size-'])]:size-4" className="flex items-center [&_svg:not([class*='size-'])]:size-4"
role="separator" aria-hidden="true"
{...props} {...props}
> >
<MinusIcon /> <MinusIcon />
+1 -1
View File
@@ -1,5 +1,5 @@
import * as React from "react";
import { Input as InputPrimitive } from "@base-ui/react/input"; import { Input as InputPrimitive } from "@base-ui/react/input";
import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
+4 -6
View File
@@ -1,15 +1,13 @@
import * as React from "react";
import { mergeProps } from "@base-ui/react/merge-props"; import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render"; import { useRender } from "@base-ui/react/use-render";
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import type * as React from "react";
import { cn } from "@/lib/utils";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils";
function ItemGroup({ className, ...props }: React.ComponentProps<"div">) { function ItemGroup({ className, ...props }: React.ComponentProps<"ul">) {
return ( return (
<div <ul
role="list"
data-slot="item-group" data-slot="item-group"
className={cn( className={cn(
"group/item-group flex w-full flex-col gap-4 has-data-[size=sm]:gap-2.5 has-data-[size=xs]:gap-2", "group/item-group flex w-full flex-col gap-4 has-data-[size=sm]:gap-2.5 has-data-[size=xs]:gap-2",
+2 -1
View File
@@ -1,11 +1,12 @@
"use client"; "use client";
import * as React from "react"; import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
function Label({ className, ...props }: React.ComponentProps<"label">) { function Label({ className, ...props }: React.ComponentProps<"label">) {
return ( return (
/* biome-ignore lint/a11y/noLabelWithoutControl: reusable label component, htmlFor or wrapping input provided by consumer */
<label <label
data-slot="label" data-slot="label"
className={cn( className={cn(
+1 -1
View File
@@ -1,7 +1,7 @@
import * as React from "react";
import { mergeProps } from "@base-ui/react/merge-props"; import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render"; import { useRender } from "@base-ui/react/use-render";
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
+3 -4
View File
@@ -1,10 +1,9 @@
"use client"; "use client";
import * as React from "react";
import { Menu as MenuPrimitive } from "@base-ui/react/menu"; import { Menu as MenuPrimitive } from "@base-ui/react/menu";
import { Menubar as MenubarPrimitive } from "@base-ui/react/menubar"; import { Menubar as MenubarPrimitive } from "@base-ui/react/menubar";
import { CheckIcon } from "lucide-react";
import { cn } from "@/lib/utils"; import type * as React from "react";
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
@@ -20,7 +19,7 @@ import {
DropdownMenuSubTrigger, DropdownMenuSubTrigger,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { CheckIcon } from "lucide-react"; import { cn } from "@/lib/utils";
function Menubar({ className, ...props }: MenubarPrimitive.Props) { function Menubar({ className, ...props }: MenubarPrimitive.Props) {
return ( return (
+3 -4
View File
@@ -1,16 +1,15 @@
"use client"; "use client";
import * as React from "react";
import { import {
MessageScroller as MessageScrollerPrimitive, MessageScroller as MessageScrollerPrimitive,
useMessageScroller, useMessageScroller,
useMessageScrollerScrollable, useMessageScrollerScrollable,
useMessageScrollerVisibility, useMessageScrollerVisibility,
} from "@shadcn/react/message-scroller"; } from "@shadcn/react/message-scroller";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { ArrowDownIcon } from "lucide-react"; import { ArrowDownIcon } from "lucide-react";
import type * as React from "react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
function MessageScrollerProvider( function MessageScrollerProvider(
props: React.ComponentProps<typeof MessageScrollerPrimitive.Provider>, props: React.ComponentProps<typeof MessageScrollerPrimitive.Provider>,
+1 -1
View File
@@ -1,4 +1,4 @@
import * as React from "react"; import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
+2 -3
View File
@@ -1,7 +1,6 @@
import * as React from "react";
import { cn } from "@/lib/utils";
import { ChevronDownIcon } from "lucide-react"; import { ChevronDownIcon } from "lucide-react";
import type * as React from "react";
import { cn } from "@/lib/utils";
type NativeSelectProps = Omit<React.ComponentProps<"select">, "size"> & { type NativeSelectProps = Omit<React.ComponentProps<"select">, "size"> & {
size?: "sm" | "default"; size?: "sm" | "default";
+1 -2
View File
@@ -1,8 +1,7 @@
import { NavigationMenu as NavigationMenuPrimitive } from "@base-ui/react/navigation-menu"; import { NavigationMenu as NavigationMenuPrimitive } from "@base-ui/react/navigation-menu";
import { cva } from "class-variance-authority"; import { cva } from "class-variance-authority";
import { cn } from "@/lib/utils";
import { ChevronDownIcon } from "lucide-react"; import { ChevronDownIcon } from "lucide-react";
import { cn } from "@/lib/utils";
function NavigationMenu({ function NavigationMenu({
align = "start", align = "start",
+3 -5
View File
@@ -1,17 +1,15 @@
import * as React from "react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { import {
ChevronLeftIcon, ChevronLeftIcon,
ChevronRightIcon, ChevronRightIcon,
MoreHorizontalIcon, MoreHorizontalIcon,
} from "lucide-react"; } from "lucide-react";
import type * as React from "react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
function Pagination({ className, ...props }: React.ComponentProps<"nav">) { function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
return ( return (
<nav <nav
role="navigation"
aria-label="pagination" aria-label="pagination"
data-slot="pagination" data-slot="pagination"
className={cn("mx-auto flex w-full justify-center", className)} className={cn("mx-auto flex w-full justify-center", className)}
+1 -1
View File
@@ -1,7 +1,7 @@
"use client"; "use client";
import * as React from "react";
import { Popover as PopoverPrimitive } from "@base-ui/react/popover"; import { Popover as PopoverPrimitive } from "@base-ui/react/popover";
import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
-1
View File
@@ -1,6 +1,5 @@
"use client"; "use client";
import * as React from "react";
import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area"; import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
+2 -3
View File
@@ -1,10 +1,9 @@
"use client"; "use client";
import * as React from "react";
import { Select as SelectPrimitive } from "@base-ui/react/select"; import { Select as SelectPrimitive } from "@base-ui/react/select";
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react";
const Select = SelectPrimitive.Root; const Select = SelectPrimitive.Root;
+3 -4
View File
@@ -1,11 +1,10 @@
"use client"; "use client";
import * as React from "react";
import { Dialog as SheetPrimitive } from "@base-ui/react/dialog"; import { Dialog as SheetPrimitive } from "@base-ui/react/dialog";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { XIcon } from "lucide-react"; import { XIcon } from "lucide-react";
import type * as React from "react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
function Sheet({ ...props }: SheetPrimitive.Root.Props) { function Sheet({ ...props }: SheetPrimitive.Root.Props) {
return <SheetPrimitive.Root data-slot="sheet" {...props} />; return <SheetPrimitive.Root data-slot="sheet" {...props} />;
+16 -8
View File
@@ -1,12 +1,10 @@
"use client"; "use client";
import * as React from "react";
import { mergeProps } from "@base-ui/react/merge-props"; import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render"; import { useRender } from "@base-ui/react/use-render";
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import { PanelLeftIcon } from "lucide-react";
import { useIsMobile } from "@/hooks/use-mobile"; import * as React from "react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
@@ -23,10 +21,20 @@ import {
TooltipContent, TooltipContent,
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { PanelLeftIcon } from "lucide-react"; import { useIsMobile } from "@/hooks/use-mobile";
import { cn } from "@/lib/utils";
const SIDEBAR_COOKIE_NAME = "sidebar_state"; const SIDEBAR_COOKIE_NAME = "sidebar_state";
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
function setCookie(name: string, value: boolean, maxAge: number) {
Reflect.set(
document,
"cookie",
`${name}=${value}; path=/; max-age=${maxAge}`,
);
}
const SIDEBAR_WIDTH = "16rem"; const SIDEBAR_WIDTH = "16rem";
const SIDEBAR_WIDTH_MOBILE = "18rem"; const SIDEBAR_WIDTH_MOBILE = "18rem";
const SIDEBAR_WIDTH_ICON = "3rem"; const SIDEBAR_WIDTH_ICON = "3rem";
@@ -83,7 +91,7 @@ function SidebarProvider({
} }
// This sets the cookie to keep the sidebar state. // This sets the cookie to keep the sidebar state.
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`; setCookie(SIDEBAR_COOKIE_NAME, openState, SIDEBAR_COOKIE_MAX_AGE);
}, },
[setOpenProp, open], [setOpenProp, open],
); );
@@ -91,7 +99,7 @@ function SidebarProvider({
// Helper to toggle the sidebar. // Helper to toggle the sidebar.
const toggleSidebar = React.useCallback(() => { const toggleSidebar = React.useCallback(() => {
return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open); return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);
}, [isMobile, setOpen, setOpenMobile]); }, [isMobile, setOpen]);
// Adds a keyboard shortcut to toggle the sidebar. // Adds a keyboard shortcut to toggle the sidebar.
React.useEffect(() => { React.useEffect(() => {
@@ -123,7 +131,7 @@ function SidebarProvider({
setOpenMobile, setOpenMobile,
toggleSidebar, toggleSidebar,
}), }),
[state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar], [state, open, setOpen, isMobile, openMobile, toggleSidebar],
); );
return ( return (
+2 -2
View File
@@ -37,10 +37,10 @@ function Slider({
className="bg-primary select-none data-horizontal:h-full data-vertical:w-full" className="bg-primary select-none data-horizontal:h-full data-vertical:w-full"
/> />
</SliderPrimitive.Track> </SliderPrimitive.Track>
{Array.from({ length: _values.length }, (_, index) => ( {_values.map((_, i) => (
<SliderPrimitive.Thumb <SliderPrimitive.Thumb
data-slot="slider-thumb" data-slot="slider-thumb"
key={index} key={i}
className="relative block size-3 shrink-0 rounded-full border border-ring bg-white ring-ring/50 transition-[color,box-shadow] select-none after:absolute after:-inset-2 hover:ring-3 focus-visible:ring-3 focus-visible:outline-hidden active:ring-3 disabled:pointer-events-none disabled:opacity-50" className="relative block size-3 shrink-0 rounded-full border border-ring bg-white ring-ring/50 transition-[color,box-shadow] select-none after:absolute after:-inset-2 hover:ring-3 focus-visible:ring-3 focus-visible:outline-hidden active:ring-3 disabled:pointer-events-none disabled:opacity-50"
/> />
))} ))}
+4 -4
View File
@@ -1,14 +1,14 @@
"use client"; "use client";
import { useTheme } from "next-themes";
import { Toaster as Sonner, type ToasterProps } from "sonner";
import { import {
CircleCheckIcon, CircleCheckIcon,
InfoIcon, InfoIcon,
TriangleAlertIcon,
OctagonXIcon,
Loader2Icon, Loader2Icon,
OctagonXIcon,
TriangleAlertIcon,
} from "lucide-react"; } from "lucide-react";
import { useTheme } from "next-themes";
import { Toaster as Sonner, type ToasterProps } from "sonner";
const Toaster = ({ ...props }: ToasterProps) => { const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme(); const { theme = "system" } = useTheme();
+1 -2
View File
@@ -1,11 +1,10 @@
import { cn } from "@/lib/utils";
import { Loader2Icon } from "lucide-react"; import { Loader2Icon } from "lucide-react";
import { cn } from "@/lib/utils";
function Spinner({ className, ...props }: React.ComponentProps<"svg">) { function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
return ( return (
<Loader2Icon <Loader2Icon
data-slot="spinner" data-slot="spinner"
role="status"
aria-label="Loading" aria-label="Loading"
className={cn("size-4 animate-spin", className)} className={cn("size-4 animate-spin", className)}
{...props} {...props}
+1 -1
View File
@@ -1,6 +1,6 @@
"use client"; "use client";
import * as React from "react"; import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
+1 -1
View File
@@ -1,4 +1,4 @@
import * as React from "react"; import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
+3 -4
View File
@@ -1,12 +1,11 @@
"use client"; "use client";
import * as React from "react";
import { Toggle as TogglePrimitive } from "@base-ui/react/toggle"; import { Toggle as TogglePrimitive } from "@base-ui/react/toggle";
import { ToggleGroup as ToggleGroupPrimitive } from "@base-ui/react/toggle-group"; import { ToggleGroup as ToggleGroupPrimitive } from "@base-ui/react/toggle-group";
import { type VariantProps } from "class-variance-authority"; import type { VariantProps } from "class-variance-authority";
import * as React from "react";
import { cn } from "@/lib/utils";
import { toggleVariants } from "@/components/ui/toggle"; import { toggleVariants } from "@/components/ui/toggle";
import { cn } from "@/lib/utils";
const ToggleGroupContext = React.createContext< const ToggleGroupContext = React.createContext<
VariantProps<typeof toggleVariants> & { VariantProps<typeof toggleVariants> & {
+1 -1
View File
@@ -1,4 +1,4 @@
import { clsx, type ClassValue } from "clsx"; import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {