diff --git a/.claude/settings.local.json b/.claude/settings.local.json index e8d4a79..d4b90d0 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,22 +1,21 @@ { - "permissions": { - "allow": [ - "Bash(pnpm tsc:*)", - "mcp__exa__get_code_context_exa", - "mcp__context7__resolve-library-id", - "mcp__context7__get-library-docs", - "mcp__exa__web_search_exa", - "Bash(pnpm dlx shadcn@latest add native-select)", - "mcp__shadcn__get_project_registries", - "mcp__shadcn__search_items_in_registries", - "mcp__shadcn__get_add_command_for_items", - "Bash(pnpm dlx shadcn@latest add:*)" - ], - "deny": [], - "ask": [] - }, - "enableAllProjectMcpServers": true, - "enabledMcpjsonServers": [ - "shadcn" - ] + "permissions": { + "allow": [ + "Bash(pnpm tsc:*)", + "mcp__exa__get_code_context_exa", + "mcp__context7__resolve-library-id", + "mcp__context7__get-library-docs", + "mcp__exa__web_search_exa", + "Bash(pnpm dlx shadcn@latest add native-select)", + "mcp__shadcn__get_project_registries", + "mcp__shadcn__search_items_in_registries", + "mcp__shadcn__get_add_command_for_items", + "Bash(pnpm dlx shadcn@latest add:*)", + "Bash(pnpm exec biome check:*)" + ], + "deny": [], + "ask": [] + }, + "enableAllProjectMcpServers": true, + "enabledMcpjsonServers": ["shadcn"] } diff --git a/.mcp.json b/.mcp.json index bd98b4f..9cc1c19 100644 --- a/.mcp.json +++ b/.mcp.json @@ -1,11 +1,8 @@ { - "mcpServers": { - "shadcn": { - "command": "npx", - "args": [ - "shadcn@latest", - "mcp" - ] - } - } + "mcpServers": { + "shadcn": { + "command": "npx", + "args": ["shadcn@latest", "mcp"] + } + } } diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 64fc736..6e7bbe8 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,3 +1,7 @@ { - "recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer", "Lokalise.i18n-ally"] + "recommendations": [ + "tauri-apps.tauri-vscode", + "rust-lang.rust-analyzer", + "Lokalise.i18n-ally" + ] } diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..9fd71bd --- /dev/null +++ b/biome.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.3.1/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "ignoreUnknown": false + }, + "formatter": { + "enabled": true, + "indentStyle": "tab" + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "css": { + "parser": { + "allowWrongLineComments": true, + "cssModules": false, + "tailwindDirectives": true + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double" + } + }, + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": "on" + } + } + } +} diff --git a/components.json b/components.json index aeab3bb..ed01937 100644 --- a/components.json +++ b/components.json @@ -1,22 +1,22 @@ { - "$schema": "https://ui.shadcn.com/schema.json", - "style": "default", - "rsc": false, - "tsx": true, - "tailwind": { - "config": "", - "css": "src/App.css", - "baseColor": "neutral", - "cssVariables": true, - "prefix": "" - }, - "iconLibrary": "lucide", - "aliases": { - "components": "@/components", - "utils": "@/lib/utils", - "ui": "@/components/ui", - "lib": "@/lib", - "hooks": "@/hooks" - }, - "registries": {} + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/App.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "registries": {} } diff --git a/package.json b/package.json index 0e22297..addf0de 100644 --- a/package.json +++ b/package.json @@ -1,82 +1,83 @@ { - "name": "cc-mate", - "private": true, - "version": "0.1.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "tauri": "tauri" - }, - "dependencies": { - "@codemirror/fold": "^0.19.4", - "@codemirror/lang-json": "^6.0.2", - "@codemirror/lang-markdown": "^6.4.0", - "@codemirror/language": "^6.11.3", - "@codemirror/language-data": "^6.5.1", - "@hookform/resolvers": "^5.2.2", - "@lobehub/icons": "^2.38.0", - "@radix-ui/react-accordion": "^1.2.12", - "@radix-ui/react-alert-dialog": "^1.1.15", - "@radix-ui/react-dialog": "^1.1.15", - "@radix-ui/react-dropdown-menu": "^2.1.16", - "@radix-ui/react-label": "^2.1.7", - "@radix-ui/react-popover": "^1.1.15", - "@radix-ui/react-scroll-area": "^1.2.10", - "@radix-ui/react-select": "^2.2.6", - "@radix-ui/react-separator": "^1.1.7", - "@radix-ui/react-slot": "^1.2.3", - "@radix-ui/react-switch": "^1.2.6", - "@radix-ui/react-tabs": "^1.1.13", - "@radix-ui/react-tooltip": "^1.2.8", - "@remixicon/react": "^4.6.0", - "@tanstack/react-query": "^5.90.2", - "@tauri-apps/api": "^2", - "@tauri-apps/plugin-dialog": "^2.4.0", - "@tauri-apps/plugin-notification": "^2.3.1", - "@tauri-apps/plugin-opener": "^2.5.0", - "@tauri-apps/plugin-os": "^2.3.1", - "@tauri-apps/plugin-updater": "~2", - "@tremor/react": "^3.18.7", - "@uiw/codemirror-theme-vscode": "^4.25.2", - "@uiw/react-codemirror": "^4.25.2", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "cmdk": "^1.1.1", - "date-fns": "^4.1.0", - "i18next": "^25.5.3", - "i18next-browser-languagedetector": "^8.2.0", - "lodash-es": "^4.17.21", - "lucide-react": "^0.544.0", - "nanoid": "^5.1.6", - "next-themes": "^0.4.6", - "react": "^19.2.0", - "react-dom": "^19.2.0", - "react-error-boundary": "^6.0.0", - "react-hook-form": "^7.64.0", - "react-i18next": "^16.0.0", - "react-icons": "^5.5.0", - "react-router-dom": "^7.9.3", - "recharts": "^3.2.1", - "sonner": "^2.0.7", - "tailwind-merge": "^3.3.1", - "tailwind-variants": "^3.1.1", - "ts-pattern": "^5.8.0", - "zod": "^4.1.11" - }, - "devDependencies": { - "@tailwindcss/vite": "^4.1.14", - "@tauri-apps/cli": "^2", - "@types/lodash-es": "^4.17.12", - "@types/react": "^19.1.8", - "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.6.0", - "babel-plugin-react-compiler": "19.1.0-rc.3", - "shadcn": "^3.5.0", - "tailwindcss": "^4.1.14", - "tw-animate-css": "^1.4.0", - "typescript": "~5.8.3", - "vite": "^7.0.4" - } + "name": "cc-mate", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "tauri": "tauri" + }, + "dependencies": { + "@codemirror/fold": "^0.19.4", + "@codemirror/lang-json": "^6.0.2", + "@codemirror/lang-markdown": "^6.4.0", + "@codemirror/language": "^6.11.3", + "@codemirror/language-data": "^6.5.1", + "@hookform/resolvers": "^5.2.2", + "@lobehub/icons": "^2.38.0", + "@radix-ui/react-accordion": "^1.2.12", + "@radix-ui/react-alert-dialog": "^1.1.15", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-separator": "^1.1.7", + "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-switch": "^1.2.6", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-tooltip": "^1.2.8", + "@remixicon/react": "^4.6.0", + "@tanstack/react-query": "^5.90.2", + "@tauri-apps/api": "^2", + "@tauri-apps/plugin-dialog": "^2.4.0", + "@tauri-apps/plugin-notification": "^2.3.1", + "@tauri-apps/plugin-opener": "^2.5.0", + "@tauri-apps/plugin-os": "^2.3.1", + "@tauri-apps/plugin-updater": "~2", + "@tremor/react": "^3.18.7", + "@uiw/codemirror-theme-vscode": "^4.25.2", + "@uiw/react-codemirror": "^4.25.2", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "date-fns": "^4.1.0", + "i18next": "^25.5.3", + "i18next-browser-languagedetector": "^8.2.0", + "lodash-es": "^4.17.21", + "lucide-react": "^0.544.0", + "nanoid": "^5.1.6", + "next-themes": "^0.4.6", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-error-boundary": "^6.0.0", + "react-hook-form": "^7.64.0", + "react-i18next": "^16.0.0", + "react-icons": "^5.5.0", + "react-router-dom": "^7.9.3", + "recharts": "^3.2.1", + "sonner": "^2.0.7", + "tailwind-merge": "^3.3.1", + "tailwind-variants": "^3.1.1", + "ts-pattern": "^5.8.0", + "zod": "^4.1.11" + }, + "devDependencies": { + "@biomejs/biome": "2.3.1", + "@tailwindcss/vite": "^4.1.14", + "@tauri-apps/cli": "^2", + "@types/lodash-es": "^4.17.12", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "@vitejs/plugin-react": "^4.6.0", + "babel-plugin-react-compiler": "19.1.0-rc.3", + "shadcn": "^3.5.0", + "tailwindcss": "^4.1.14", + "tw-animate-css": "^1.4.0", + "typescript": "~5.8.3", + "vite": "^7.0.4" + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ec15fb2..b42a7e8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -171,6 +171,9 @@ importers: specifier: ^4.1.11 version: 4.1.11 devDependencies: + '@biomejs/biome': + specifier: 2.3.1 + version: 2.3.1 '@tailwindcss/vite': specifier: ^4.1.14 version: 4.1.14(vite@7.1.9(jiti@2.6.1)(lightningcss@1.30.1)) @@ -431,6 +434,59 @@ packages: resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} + '@biomejs/biome@2.3.1': + resolution: {integrity: sha512-A29evf1R72V5bo4o2EPxYMm5mtyGvzp2g+biZvRFx29nWebGyyeOSsDWGx3tuNNMFRepGwxmA9ZQ15mzfabK2w==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@2.3.1': + resolution: {integrity: sha512-ombSf3MnTUueiYGN1SeI9tBCsDUhpWzOwS63Dove42osNh0PfE1cUtHFx6eZ1+MYCCLwXzlFlYFdrJ+U7h6LcA==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@2.3.1': + resolution: {integrity: sha512-pcOfwyoQkrkbGvXxRvZNe5qgD797IowpJPovPX5biPk2FwMEV+INZqfCaz4G5bVq9hYnjwhRMamg11U4QsRXrQ==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@2.3.1': + resolution: {integrity: sha512-+DZYv8l7FlUtTrWs1Tdt1KcNCAmRO87PyOnxKGunbWm5HKg1oZBSbIIPkjrCtDZaeqSG1DiGx7qF+CPsquQRcg==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-arm64@2.3.1': + resolution: {integrity: sha512-td5O8pFIgLs8H1sAZsD6v+5quODihyEw4nv2R8z7swUfIK1FKk+15e4eiYVLcAE4jUqngvh4j3JCNgg0Y4o4IQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-x64-musl@2.3.1': + resolution: {integrity: sha512-Y3Ob4nqgv38Mh+6EGHltuN+Cq8aj/gyMTJYzkFZV2AEj+9XzoXB9VNljz9pjfFNHUxvLEV4b55VWyxozQTBaUQ==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-linux-x64@2.3.1': + resolution: {integrity: sha512-PYWgEO7up7XYwSAArOpzsVCiqxBCXy53gsReAb1kKYIyXaoAlhBaBMvxR/k2Rm9aTuZ662locXUmPk/Aj+Xu+Q==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-win32-arm64@2.3.1': + resolution: {integrity: sha512-RHIG/zgo+69idUqVvV3n8+j58dKYABRpMyDmfWu2TITC+jwGPiEaT0Q3RKD+kQHiS80mpBrST0iUGeEXT0bU9A==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@2.3.1': + resolution: {integrity: sha512-izl30JJ5Dp10mi90Eko47zhxE6pYyWPcnX1NQxKpL/yMhXxf95oLTzfpu4q+MDBh/gemNqyJEwjBpe0MT5iWPA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + '@braintree/sanitize-url@7.1.1': resolution: {integrity: sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==} @@ -5653,6 +5709,41 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@biomejs/biome@2.3.1': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 2.3.1 + '@biomejs/cli-darwin-x64': 2.3.1 + '@biomejs/cli-linux-arm64': 2.3.1 + '@biomejs/cli-linux-arm64-musl': 2.3.1 + '@biomejs/cli-linux-x64': 2.3.1 + '@biomejs/cli-linux-x64-musl': 2.3.1 + '@biomejs/cli-win32-arm64': 2.3.1 + '@biomejs/cli-win32-x64': 2.3.1 + + '@biomejs/cli-darwin-arm64@2.3.1': + optional: true + + '@biomejs/cli-darwin-x64@2.3.1': + optional: true + + '@biomejs/cli-linux-arm64-musl@2.3.1': + optional: true + + '@biomejs/cli-linux-arm64@2.3.1': + optional: true + + '@biomejs/cli-linux-x64-musl@2.3.1': + optional: true + + '@biomejs/cli-linux-x64@2.3.1': + optional: true + + '@biomejs/cli-win32-arm64@2.3.1': + optional: true + + '@biomejs/cli-win32-x64@2.3.1': + optional: true + '@braintree/sanitize-url@7.1.1': {} '@chevrotain/cst-dts-gen@11.0.3': diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index e32ba96..5d964e1 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -1,14 +1,14 @@ { - "$schema": "../gen/schemas/desktop-schema.json", - "identifier": "default", - "description": "Capability for the main window", - "windows": ["main"], - "permissions": [ - "core:default", - "opener:default", - "core:window:allow-start-dragging", - "dialog:allow-ask", - "dialog:allow-message", - "os:allow-platform" - ] + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Capability for the main window", + "windows": ["main"], + "permissions": [ + "core:default", + "opener:default", + "core:window:allow-start-dragging", + "dialog:allow-ask", + "dialog:allow-message", + "os:allow-platform" + ] } diff --git a/src-tauri/capabilities/desktop.json b/src-tauri/capabilities/desktop.json index ba6d080..48d3ecb 100644 --- a/src-tauri/capabilities/desktop.json +++ b/src-tauri/capabilities/desktop.json @@ -1,14 +1,6 @@ { - "identifier": "desktop-capability", - "platforms": [ - "macOS", - "windows", - "linux" - ], - "windows": [ - "main" - ], - "permissions": [ - "updater:default" - ] -} \ No newline at end of file + "identifier": "desktop-capability", + "platforms": ["macOS", "windows", "linux"], + "windows": ["main"], + "permissions": ["updater:default"] +} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 584bd57..de33bff 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,48 +1,45 @@ { - "$schema": "https://schema.tauri.app/config/2", - "productName": "CC Mate", - "version": "0.2.9", - "identifier": "org.randynamic.ccconfig", - "build": { - "beforeDevCommand": "pnpm dev", - "devUrl": "http://localhost:1420", - "beforeBuildCommand": "pnpm build", - "frontendDist": "../dist" - }, - "app": { - "macOSPrivateApi": true, - "windows": [ - { - "title": "CC Mate", - "width": 860, - "height": 660, - "minWidth": 860, - "minHeight": 660, - "decorations": true, - "titleBarStyle": "Overlay", - "hiddenTitle": true - } - ], - "security": { - "csp": null, - "capabilities": ["default"] - } - }, - "bundle": { - "createUpdaterArtifacts": true, - "active": true, - "targets": "all", - "icon": [ - "icons/icon.icns", - "icons/icon.ico" - ] - }, - "plugins": { - "updater": { - "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDE5NjU1MUZBRDYxQTY1ODcKUldTSFpSclcrbEZsR1ptZ3Z0a3hJNEx0RHpGQ29ZQVhJWDZZN3JOMnBBd29YdzA5ZmRsNzloRlEK", - "endpoints": [ - "https://github.com/djyde/ccmate-release/releases/latest/download/latest.json" - ] - } - } + "$schema": "https://schema.tauri.app/config/2", + "productName": "CC Mate", + "version": "0.2.9", + "identifier": "org.randynamic.ccconfig", + "build": { + "beforeDevCommand": "pnpm dev", + "devUrl": "http://localhost:1420", + "beforeBuildCommand": "pnpm build", + "frontendDist": "../dist" + }, + "app": { + "macOSPrivateApi": true, + "windows": [ + { + "title": "CC Mate", + "width": 860, + "height": 660, + "minWidth": 860, + "minHeight": 660, + "decorations": true, + "titleBarStyle": "Overlay", + "hiddenTitle": true + } + ], + "security": { + "csp": null, + "capabilities": ["default"] + } + }, + "bundle": { + "createUpdaterArtifacts": true, + "active": true, + "targets": "all", + "icon": ["icons/icon.icns", "icons/icon.ico"] + }, + "plugins": { + "updater": { + "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDE5NjU1MUZBRDYxQTY1ODcKUldTSFpSclcrbEZsR1ptZ3Z0a3hJNEx0RHpGQ29ZQVhJWDZZN3JOMnBBd29YdzA5ZmRsNzloRlEK", + "endpoints": [ + "https://github.com/djyde/ccmate-release/releases/latest/download/latest.json" + ] + } + } } diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx index 1c0240e..ec0e239 100644 --- a/src/components/ErrorBoundary.tsx +++ b/src/components/ErrorBoundary.tsx @@ -1,99 +1,98 @@ -import { Component, ErrorInfo, ReactNode } from "react"; -import { Button } from "./ui/button"; -import { Alert, AlertDescription, AlertTitle } from "./ui/alert"; import { AlertCircle } from "lucide-react"; +import { Component, type ErrorInfo, type ReactNode } from "react"; +import { Alert, AlertDescription, AlertTitle } from "./ui/alert"; +import { Button } from "./ui/button"; interface Props { - children: ReactNode; + children: ReactNode; } interface State { - hasError: boolean; - error: Error | null; - errorInfo: ErrorInfo | null; + hasError: boolean; + error: Error | null; + errorInfo: ErrorInfo | null; } export class ErrorBoundary extends Component { - constructor(props: Props) { - super(props); - this.state = { - hasError: false, - error: null, - errorInfo: null, - }; - } + constructor(props: Props) { + super(props); + this.state = { + hasError: false, + error: null, + errorInfo: null, + }; + } - static getDerivedStateFromError(error: Error): State { - return { - hasError: true, - error, - errorInfo: null, - }; - } + static getDerivedStateFromError(error: Error): State { + return { + hasError: true, + error, + errorInfo: null, + }; + } - componentDidCatch(error: Error, errorInfo: ErrorInfo) { - console.error("ErrorBoundary caught an error:", error, errorInfo); - this.setState({ - error, - errorInfo, - }); - } + componentDidCatch(error: Error, errorInfo: ErrorInfo) { + console.error("ErrorBoundary caught an error:", error, errorInfo); + this.setState({ + error, + errorInfo, + }); + } - handleReset = () => { - this.setState({ - hasError: false, - error: null, - errorInfo: null, - }); - window.location.href = "/"; - }; + handleReset = () => { + this.setState({ + hasError: false, + error: null, + errorInfo: null, + }); + window.location.href = "/"; + }; - handleReload = () => { - window.location.reload(); - }; + handleReload = () => { + window.location.reload(); + }; - render() { - if (this.state.hasError) { - return ( -
-
- - - Something went wrong - -
- {this.state.error?.message || "An unexpected error occurred"} -
-
- Please try reloading the page or return to the home page. -
-
-
+ render() { + if (this.state.hasError) { + return ( +
+
+ + + Something went wrong + +
+ {this.state.error?.message || "An unexpected error occurred"} +
+
+ Please try reloading the page or return to the home page. +
+
+
- {this.state.error && ( -
-

Stack Trace:

-
-                  {this.state.error.stack || this.state.error.toString()}
-                  {this.state.errorInfo?.componentStack}
-                
-
- )} + {this.state.error && ( +
+

Stack Trace:

+
+									{this.state.error.stack || this.state.error.toString()}
+									{this.state.errorInfo?.componentStack}
+								
+
+ )} -
- - -
-
-
- ); - } +
+ + +
+
+
+ ); + } - return this.props.children; - } + return this.props.children; + } } - diff --git a/src/components/GLMBanner.tsx b/src/components/GLMBanner.tsx index d726832..d9bf8b2 100644 --- a/src/components/GLMBanner.tsx +++ b/src/components/GLMBanner.tsx @@ -1,58 +1,77 @@ -import { useState } from "react" -import { Button } from "./ui/button" -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./ui/tooltip" -import { CircleQuestionMarkIcon } from "lucide-react" -import { cn } from "@/lib/utils" -import { useTranslation } from "react-i18next" -import { GLMDialog } from "./GLMDialog" +import { CircleQuestionMarkIcon } from "lucide-react"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { cn } from "@/lib/utils"; +import { GLMDialog } from "./GLMDialog"; +import { Button } from "./ui/button"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "./ui/tooltip"; -export { GLMDialog } from "./GLMDialog" +export { GLMDialog } from "./GLMDialog"; export function GLMBanner(props: { - className?: string, - hideCloseButton?: boolean + className?: string; + hideCloseButton?: boolean; }) { - const { t, i18n } = useTranslation() - const [isDismissed, setIsDismissed] = useState(localStorage.getItem('glm-banner-dismissed') === 'true') + const { t, i18n } = useTranslation(); + const [isDismissed, setIsDismissed] = useState( + localStorage.getItem("glm-banner-dismissed") === "true", + ); - const handleDismiss = () => { - localStorage.setItem('glm-banner-dismissed', 'true') - setIsDismissed(true) - } + const handleDismiss = () => { + localStorage.setItem("glm-banner-dismissed", "true"); + setIsDismissed(true); + }; - // Only show banner when locale is Chinese - if (i18n.language !== 'zh' || isDismissed) { - return null - } + // Only show banner when locale is Chinese + if (i18n.language !== "zh" || isDismissed) { + return null; + } - return ( -
-

{t('glm.title')} - - - - - - -

{t('glm.tooltip')}

-
-
-
-

-
- - {t('glm.startConfig')} - - } - onSuccess={handleDismiss} - /> - {!props.hideCloseButton && } -
-
- ) + return ( +
+

+ {t("glm.title")} + + + + + + +

{t("glm.tooltip")}

+
+
+
+

+
+ + {t("glm.startConfig")} + + } + onSuccess={handleDismiss} + /> + {!props.hideCloseButton && ( + + )} +
+
+ ); } - diff --git a/src/components/GLMDialog.tsx b/src/components/GLMDialog.tsx index d63e342..b6088d7 100644 --- a/src/components/GLMDialog.tsx +++ b/src/components/GLMDialog.tsx @@ -1,148 +1,182 @@ -import { useState } from "react" -import { openUrl } from "@tauri-apps/plugin-opener" -import { Button } from "./ui/button" -import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "./ui/dialog" -import { ExternalLinkIcon } from "lucide-react" -import { Input } from "./ui/input" -import { NativeSelect, NativeSelectOption } from "./ui/native-select" -import { useCreateConfig, useSetCurrentConfig } from "@/lib/query" -import { useTranslation } from "react-i18next" +import { openUrl } from "@tauri-apps/plugin-opener"; +import { ExternalLinkIcon } from "lucide-react"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useCreateConfig, useSetCurrentConfig } from "@/lib/query"; +import { Button } from "./ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "./ui/dialog"; +import { Input } from "./ui/input"; +import { NativeSelect, NativeSelectOption } from "./ui/native-select"; export function GLMDialog(props: { - trigger: React.ReactNode - onSuccess?: () => void + trigger: React.ReactNode; + onSuccess?: () => void; }) { - const { t } = useTranslation() - const [apiKey, setApiKey] = useState("") - const [selectedRegion, setSelectedRegion] = useState("china-mainland") - const [isOpen, setIsOpen] = useState(false) - const createConfigMutation = useCreateConfig() - const setCurrentConfigMutation = useSetCurrentConfig() + const { t } = useTranslation(); + const [apiKey, setApiKey] = useState(""); + const [selectedRegion, setSelectedRegion] = useState("china-mainland"); + const [isOpen, setIsOpen] = useState(false); + const createConfigMutation = useCreateConfig(); + const setCurrentConfigMutation = useSetCurrentConfig(); - const handleCreateConfig = async () => { - if (!apiKey.trim()) { - return - } + const handleCreateConfig = async () => { + if (!apiKey.trim()) { + return; + } - try { - const baseUrl = selectedRegion === "z-ai" - ? 'https://api.z.ai/api/anthropic' - : 'https://open.bigmodel.cn/api/anthropic' + try { + const baseUrl = + selectedRegion === "z-ai" + ? "https://api.z.ai/api/anthropic" + : "https://open.bigmodel.cn/api/anthropic"; - const store = await createConfigMutation.mutateAsync({ - title: selectedRegion === "z-ai" ? t('glm.zaiTitle') : t('glm.zhipuTitle'), - settings: { - env: { - ANTHROPIC_AUTH_TOKEN: apiKey.trim(), - ANTHROPIC_BASE_URL: baseUrl, - ANTHROPIC_MODEL: 'GLM-4.6', - ANTHROPIC_DEFAULT_OPUS_MODEL: 'GLM-4.6', - ANTHROPIC_DEFAULT_SONNET_MODEL: 'GLM-4.6', - ANTHROPIC_DEFAULT_HAIKU_MODEL: 'GLM-4.5-Air' - } - } - }) + const store = await createConfigMutation.mutateAsync({ + title: + selectedRegion === "z-ai" ? t("glm.zaiTitle") : t("glm.zhipuTitle"), + settings: { + env: { + ANTHROPIC_AUTH_TOKEN: apiKey.trim(), + ANTHROPIC_BASE_URL: baseUrl, + ANTHROPIC_MODEL: "GLM-4.6", + ANTHROPIC_DEFAULT_OPUS_MODEL: "GLM-4.6", + ANTHROPIC_DEFAULT_SONNET_MODEL: "GLM-4.6", + ANTHROPIC_DEFAULT_HAIKU_MODEL: "GLM-4.5-Air", + }, + }, + }); - // Set the newly created config as the current/active config - await setCurrentConfigMutation.mutateAsync(store.id) + // Set the newly created config as the current/active config + await setCurrentConfigMutation.mutateAsync(store.id); - setIsOpen(false) - setApiKey("") - setSelectedRegion("china-mainland") + setIsOpen(false); + setApiKey(""); + setSelectedRegion("china-mainland"); - // Call onSuccess callback to dismiss the banner - props.onSuccess?.() - } catch (error) { - console.error("Failed to create GLM config:", error) - } - } + // Call onSuccess callback to dismiss the banner + props.onSuccess?.(); + } catch (error) { + console.error("Failed to create GLM config:", error); + } + }; - return ( - - - {props.trigger} - - - - - {selectedRegion === "z-ai" ? t('glm.configZai') : t('glm.configZhipu')} - - - {t('glm.description', { provider: selectedRegion === "z-ai" ? "Z.ai" : t('glm.zhipu') })} - - -
-
-
-
- setSelectedRegion(e.target.value)} - className="w-full mt-1" - > - {t('glm.chinaMainland')} - {t('glm.international')} - -
-

- {t('glm.step1')} -

-
+ return ( + + {props.trigger} + + + + {selectedRegion === "z-ai" + ? t("glm.configZai") + : t("glm.configZhipu")} + + + {t("glm.description", { + provider: selectedRegion === "z-ai" ? "Z.ai" : t("glm.zhipu"), + })} + + +
+
+
+
+ setSelectedRegion(e.target.value)} + className="w-full mt-1" + > + + {t("glm.chinaMainland")} + + + {t("glm.international")} + + +
+

+ {t("glm.step1")} +

+
+ +

+ {t("glm.discount")} +

+
+
- -

{t('glm.discount')}

-
-
+
+

+ {t("glm.step2")} +

+
+ +
+
-
-

- {t('glm.step2')} -

-
- -
-
+
+

+ {t("glm.step3")} +

+
+ setApiKey(e.target.value)} + placeholder={ + selectedRegion === "z-ai" + ? t("glm.zaiApiKeyPlaceholder") + : t("glm.zhipuApiKeyPlaceholder") + } + /> +
+
-
-

- {t('glm.step3')} -

-
- setApiKey(e.target.value)} - placeholder={selectedRegion === "z-ai" ? t('glm.zaiApiKeyPlaceholder') : t('glm.zhipuApiKeyPlaceholder')} - /> -
-
- -
- -
-
-
- -
- ) -} \ No newline at end of file +
+ +
+ + + + + ); +} diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 08617d8..c13c216 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -1,122 +1,148 @@ -import React from "react"; -import { Outlet, NavLink, useLocation } from "react-router-dom"; +import { + ActivityIcon, + BellIcon, + BrainIcon, + CpuIcon, + FileJsonIcon, + FolderIcon, + SettingsIcon, + TerminalIcon, +} from "lucide-react"; +import type React from "react"; import { useTranslation } from "react-i18next"; +import { NavLink, Outlet, useLocation } from "react-router-dom"; import { cn, isMacOS } from "../lib/utils"; -import { FileJsonIcon, SettingsIcon, CpuIcon, ActivityIcon, BrainIcon, FolderIcon, BellIcon, TerminalIcon } from "lucide-react"; -import { ScrollArea } from "./ui/scroll-area"; import { UpdateButton } from "./UpdateButton"; +import { ScrollArea } from "./ui/scroll-area"; export function Layout() { - const { t } = useTranslation(); - const location = useLocation(); - const isProjectsRoute = location.pathname.startsWith('/projects'); + const { t } = useTranslation(); + const location = useLocation(); + const isProjectsRoute = location.pathname.startsWith("/projects"); - const navLinks = [ - { - to: "/", - icon: FileJsonIcon, - label: t("navigation.configurations") - }, - { - to: "/projects", - icon: FolderIcon, - label: t("navigation.projects") - }, - { - to: "/mcp", - icon: CpuIcon, - label: t("navigation.mcp") - }, - { - to: "/memory", - icon: BrainIcon, - label: t("navigation.memory") - }, - { - to: "/commands", - icon: TerminalIcon, - label: t("navigation.commands") - }, - { - to: "/notification", - icon: BellIcon, - label: t("navigation.notifications") - }, - { - to: "/usage", - icon: ActivityIcon, - label: t("navigation.usage") - }, - { - to: "/settings", - icon: SettingsIcon, - label: t("navigation.settings") - }, + const navLinks = [ + { + to: "/", + icon: FileJsonIcon, + label: t("navigation.configurations"), + }, + { + to: "/projects", + icon: FolderIcon, + label: t("navigation.projects"), + }, + { + to: "/mcp", + icon: CpuIcon, + label: t("navigation.mcp"), + }, + { + to: "/memory", + icon: BrainIcon, + label: t("navigation.memory"), + }, + { + to: "/commands", + icon: TerminalIcon, + label: t("navigation.commands"), + }, + { + to: "/notification", + icon: BellIcon, + label: t("navigation.notifications"), + }, + { + to: "/usage", + icon: ActivityIcon, + label: t("navigation.usage"), + }, + { + to: "/settings", + icon: SettingsIcon, + label: t("navigation.settings"), + }, + ]; - ] + return ( +
+ {/* Custom Title Bar - Draggable Region with traffic lights space (macOS only) */} + {isMacOS && ( +
+ )} - return ( -
- {/* Custom Title Bar - Draggable Region with traffic lights space (macOS only) */} - {isMacOS && ( -
-
- )} +
+
+ + {isProjectsRoute ? ( +
+ +
+ ) : ( + +
+ +
+
+ )} +
+
+ ); +} diff --git a/src/components/MiniMaxDialog.tsx b/src/components/MiniMaxDialog.tsx index 64f8a0b..a55161d 100644 --- a/src/components/MiniMaxDialog.tsx +++ b/src/components/MiniMaxDialog.tsx @@ -1,136 +1,164 @@ -import { useState } from "react" -import { openUrl } from "@tauri-apps/plugin-opener" -import { Button } from "./ui/button" -import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "./ui/dialog" -import { CircleQuestionMarkIcon, ExternalLinkIcon } from "lucide-react" -import { Input } from "./ui/input" -import { NativeSelect, NativeSelectOption } from "./ui/native-select" -import { useCreateConfig, useSetCurrentConfig } from "@/lib/query" -import { useTranslation } from "react-i18next" +import { openUrl } from "@tauri-apps/plugin-opener"; +import { ExternalLinkIcon } from "lucide-react"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useCreateConfig, useSetCurrentConfig } from "@/lib/query"; +import { Button } from "./ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "./ui/dialog"; +import { Input } from "./ui/input"; +import { NativeSelect, NativeSelectOption } from "./ui/native-select"; export function MiniMaxDialog(props: { - trigger: React.ReactNode - onSuccess?: () => void + trigger: React.ReactNode; + onSuccess?: () => void; }) { - const { t } = useTranslation() - const [apiKey, setApiKey] = useState("") - const [selectedRegion, setSelectedRegion] = useState("china-mainland") - const [isOpen, setIsOpen] = useState(false) - const createConfigMutation = useCreateConfig() - const setCurrentConfigMutation = useSetCurrentConfig() + const { t } = useTranslation(); + const [apiKey, setApiKey] = useState(""); + const [selectedRegion, setSelectedRegion] = useState("china-mainland"); + const [isOpen, setIsOpen] = useState(false); + const createConfigMutation = useCreateConfig(); + const setCurrentConfigMutation = useSetCurrentConfig(); - const handleCreateConfig = async () => { - if (!apiKey.trim()) { - return - } + const handleCreateConfig = async () => { + if (!apiKey.trim()) { + return; + } - try { - const baseUrl = selectedRegion === "international" - ? 'https://api.minimax.io/anthropic' - : 'https://api.minimaxi.com/anthropic' + try { + const baseUrl = + selectedRegion === "international" + ? "https://api.minimax.io/anthropic" + : "https://api.minimaxi.com/anthropic"; - const store = await createConfigMutation.mutateAsync({ - title: selectedRegion === "international" ? t('minimax.internationalTitle') : t('minimax.mainlandTitle'), - settings: { - env: { - ANTHROPIC_AUTH_TOKEN: apiKey.trim(), - ANTHROPIC_BASE_URL: baseUrl, - API_TIMEOUT_MS: "3000000", - CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: 1, - ANTHROPIC_MODEL: 'MiniMax-M2', - ANTHROPIC_SMALL_FAST_MODEL: 'MiniMax-M2', - ANTHROPIC_DEFAULT_SONNET_MODEL: 'MiniMax-M2', - ANTHROPIC_DEFAULT_OPUS_MODEL: 'MiniMax-M2', - ANTHROPIC_DEFAULT_HAIKU_MODEL: 'MiniMax-M2' - } - } - }) + const store = await createConfigMutation.mutateAsync({ + title: + selectedRegion === "international" + ? t("minimax.internationalTitle") + : t("minimax.mainlandTitle"), + settings: { + env: { + ANTHROPIC_AUTH_TOKEN: apiKey.trim(), + ANTHROPIC_BASE_URL: baseUrl, + API_TIMEOUT_MS: "3000000", + CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: 1, + ANTHROPIC_MODEL: "MiniMax-M2", + ANTHROPIC_SMALL_FAST_MODEL: "MiniMax-M2", + ANTHROPIC_DEFAULT_SONNET_MODEL: "MiniMax-M2", + ANTHROPIC_DEFAULT_OPUS_MODEL: "MiniMax-M2", + ANTHROPIC_DEFAULT_HAIKU_MODEL: "MiniMax-M2", + }, + }, + }); - // Set the newly created config as the current/active config - await setCurrentConfigMutation.mutateAsync(store.id) + // Set the newly created config as the current/active config + await setCurrentConfigMutation.mutateAsync(store.id); - setIsOpen(false) - setApiKey("") - setSelectedRegion("china-mainland") + setIsOpen(false); + setApiKey(""); + setSelectedRegion("china-mainland"); - // Call onSuccess callback to dismiss the banner - props.onSuccess?.() - } catch (error) { - console.error("Failed to create MiniMax config:", error) - } - } + // Call onSuccess callback to dismiss the banner + props.onSuccess?.(); + } catch (error) { + console.error("Failed to create MiniMax config:", error); + } + }; - return ( - - - {props.trigger} - - - - - {selectedRegion === "international" ? t('minimax.configInternational') : t('minimax.configMainland')} - - -
-

- {t('minimax.description', { provider: selectedRegion === "international" ? "MiniMax" : t('minimax.minimax') })} -

-
-
-
-
-
-
-
- setSelectedRegion(e.target.value)} - className="w-full" - > - {t('minimax.chinaMainland')} - {t('minimax.international')} - -
-

- {t('minimax.step1')} -

-
- -
-
+ return ( + + {props.trigger} + + + + {selectedRegion === "international" + ? t("minimax.configInternational") + : t("minimax.configMainland")} + + +
+

+ {t("minimax.description", { + provider: + selectedRegion === "international" + ? "MiniMax" + : t("minimax.minimax"), + })} +

+
+
+
+
+
+
+
+ setSelectedRegion(e.target.value)} + className="w-full" + > + + {t("minimax.chinaMainland")} + + + {t("minimax.international")} + + +
+

+ {t("minimax.step1")} +

+
+ +
+
-
-

- {t('minimax.step2')} -

-
- setApiKey(e.target.value)} - placeholder={t('minimax.apiKeyPlaceholder')} - /> -
-
+
+

+ {t("minimax.step2")} +

+
+ setApiKey(e.target.value)} + placeholder={t("minimax.apiKeyPlaceholder")} + /> +
+
-
- -
-
-
-
-
- ) -} \ No newline at end of file +
+ +
+
+
+
+
+ ); +} diff --git a/src/components/QueryErrorFallback.tsx b/src/components/QueryErrorFallback.tsx index cb34fe5..ed498d7 100644 --- a/src/components/QueryErrorFallback.tsx +++ b/src/components/QueryErrorFallback.tsx @@ -1,36 +1,42 @@ import { AlertCircle } from "lucide-react"; -import { Button } from "./ui/button"; import { Alert, AlertDescription, AlertTitle } from "./ui/alert"; +import { Button } from "./ui/button"; interface QueryErrorFallbackProps { - error: Error; - resetErrorBoundary?: () => void; + error: Error; + resetErrorBoundary?: () => void; } -export function QueryErrorFallback({ error, resetErrorBoundary }: QueryErrorFallbackProps) { - return ( -
-
- - - Failed to load data - - {error.message || "An unexpected error occurred while fetching data."} - - +export function QueryErrorFallback({ + error, + resetErrorBoundary, +}: QueryErrorFallbackProps) { + return ( +
+
+ + + Failed to load data + + {error.message || + "An unexpected error occurred while fetching data."} + + - {resetErrorBoundary && ( -
- - -
- )} -
-
- ); + {resetErrorBoundary && ( +
+ + +
+ )} +
+
+ ); } - diff --git a/src/components/RouteWrapper.tsx b/src/components/RouteWrapper.tsx index 9ba0204..5ada279 100644 --- a/src/components/RouteWrapper.tsx +++ b/src/components/RouteWrapper.tsx @@ -1,36 +1,36 @@ -import { Suspense, ReactNode } from "react"; import { QueryErrorResetBoundary } from "@tanstack/react-query"; +import { type ReactNode, Suspense } from "react"; import { ErrorBoundary, type FallbackProps } from "react-error-boundary"; import { QueryErrorFallback } from "./QueryErrorFallback"; function LoadingFallback() { - return ( -
-
Loading...
-
- ); + return ( +
+
Loading...
+
+ ); } interface RouteWrapperProps { - children: ReactNode; + children: ReactNode; } export function RouteWrapper({ children }: RouteWrapperProps) { - return ( - - {({ reset }) => ( - ( - - )} - > - }> - {children} - - - )} - - ); + return ( + + {({ reset }) => ( + ( + + )} + > + }>{children} + + )} + + ); } - diff --git a/src/components/TokenUsageChart.tsx b/src/components/TokenUsageChart.tsx index 4572c5a..c2506f5 100644 --- a/src/components/TokenUsageChart.tsx +++ b/src/components/TokenUsageChart.tsx @@ -1,356 +1,422 @@ -import { AreaChart } from "@/components/ui/area-chart"; -import { format, startOfDay, startOfWeek, startOfMonth, subHours, subDays } from "date-fns"; -import { ProjectUsageRecord } from "@/lib/query"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { useState, useMemo, useEffect } from "react"; -import { formatLargeNumber } from "@/lib/utils"; +import { + format, + startOfDay, + startOfMonth, + startOfWeek, + subDays, + subHours, +} from "date-fns"; +import { useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; +import { AreaChart } from "@/components/ui/area-chart"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import type { ProjectUsageRecord } from "@/lib/query"; +import { formatLargeNumber } from "@/lib/utils"; interface TokenUsageChartProps { - data: ProjectUsageRecord[]; - onFilteredDataChange?: (filteredData: ProjectUsageRecord[]) => void; + data: ProjectUsageRecord[]; + onFilteredDataChange?: (filteredData: ProjectUsageRecord[]) => void; } type TimeRange = "5h" | "today" | "7d" | "week" | "month" | "all"; -export function TokenUsageChart({ data, onFilteredDataChange }: TokenUsageChartProps) { - const { t } = useTranslation(); - const [selectedModel, setSelectedModel] = useState("all"); - const [timeRange, setTimeRange] = useState("5h"); - const [activeCategories, setActiveCategories] = useState(["Input Tokens", "Output Tokens"]); +export function TokenUsageChart({ + data, + onFilteredDataChange, +}: TokenUsageChartProps) { + const { t } = useTranslation(); + const [selectedModel, setSelectedModel] = useState("all"); + const [timeRange, setTimeRange] = useState("5h"); + const [activeCategories, setActiveCategories] = useState([ + "Input Tokens", + "Output Tokens", + ]); - // Get unique models from data - const availableModels = useMemo(() => { - const models = new Set(); - data.forEach((record) => { - if (record.model) { - models.add(record.model); - } - }); - return Array.from(models).sort(); - }, [data]); + // Get unique models from data + const availableModels = useMemo(() => { + const models = new Set(); + data.forEach((record) => { + if (record.model) { + models.add(record.model); + } + }); + return Array.from(models).sort(); + }, [data]); - // Filter data based on selected model and time range - const filteredData = useMemo(() => { - let filtered = data; + // Filter data based on selected model and time range + const filteredData = useMemo(() => { + let filtered = data; - // Filter by model - if (selectedModel !== "all") { - filtered = filtered.filter((record) => record.model === selectedModel); - } + // Filter by model + if (selectedModel !== "all") { + filtered = filtered.filter((record) => record.model === selectedModel); + } - // Filter by time range - const now = new Date(); - let startTime: Date; + // Filter by time range + const now = new Date(); + let startTime: Date; - switch (timeRange) { - case "5h": - startTime = subHours(now, 5); - break; - case "today": - startTime = startOfDay(now); - break; - case "7d": - startTime = subDays(now, 6); - break; - case "week": - startTime = startOfWeek(now); - break; - case "month": - startTime = startOfMonth(now); - break; - case "all": - // For "all time", find the earliest timestamp in the data - if (filtered.length > 0) { - const earliestTime = new Date( - Math.min(...filtered.map((record) => new Date(record.timestamp).getTime())) - ); - startTime = earliestTime; - } else { - startTime = new Date(0); - } - break; - default: - startTime = subHours(now, 5); - } + switch (timeRange) { + case "5h": + startTime = subHours(now, 5); + break; + case "today": + startTime = startOfDay(now); + break; + case "7d": + startTime = subDays(now, 6); + break; + case "week": + startTime = startOfWeek(now); + break; + case "month": + startTime = startOfMonth(now); + break; + case "all": + // For "all time", find the earliest timestamp in the data + if (filtered.length > 0) { + const earliestTime = new Date( + Math.min( + ...filtered.map((record) => new Date(record.timestamp).getTime()), + ), + ); + startTime = earliestTime; + } else { + startTime = new Date(0); + } + break; + default: + startTime = subHours(now, 5); + } - filtered = filtered.filter((record) => new Date(record.timestamp) >= startTime); + filtered = filtered.filter( + (record) => new Date(record.timestamp) >= startTime, + ); - return filtered; - }, [data, selectedModel, timeRange]); + return filtered; + }, [data, selectedModel, timeRange]); - // Notify parent component when filtered data changes - useEffect(() => { - if (onFilteredDataChange) { - onFilteredDataChange(filteredData); - } - }, [filteredData, onFilteredDataChange]); + // Notify parent component when filtered data changes + useEffect(() => { + if (onFilteredDataChange) { + onFilteredDataChange(filteredData); + } + }, [filteredData, onFilteredDataChange]); - // Handle category toggling - const handleCategoryToggle = (value: any) => { - if (value && value.categoryClicked) { - setActiveCategories(prev => { - if (prev.includes(value.categoryClicked)) { - return prev.filter(cat => cat !== value.categoryClicked); - } else { - return [...prev, value.categoryClicked]; - } - }); - } - }; + // Handle category toggling + const handleCategoryToggle = (value: any) => { + if (value && value.categoryClicked) { + setActiveCategories((prev) => { + if (prev.includes(value.categoryClicked)) { + return prev.filter((cat) => cat !== value.categoryClicked); + } else { + return [...prev, value.categoryClicked]; + } + }); + } + }; - // Toggle category function for direct use - const toggleCategory = (category: string) => { - handleCategoryToggle({ categoryClicked: category }); - }; - // Group data based on time range - const groupDataByInterval = (records: ProjectUsageRecord[]) => { - const intervals: { [key: string]: { input: number; output: number; cache: number } } = {}; - const now = new Date(); + // Toggle category function for direct use + const toggleCategory = (category: string) => { + handleCategoryToggle({ categoryClicked: category }); + }; + // Group data based on time range + const groupDataByInterval = (records: ProjectUsageRecord[]) => { + const intervals: { + [key: string]: { input: number; output: number; cache: number }; + } = {}; + const now = new Date(); - if (timeRange === "all") { - // For all time, group by week - const earliestTime = records.length > 0 - ? new Date(Math.min(...records.map((record) => new Date(record.timestamp).getTime()))) - : new Date(); + if (timeRange === "all") { + // For all time, group by week + const earliestTime = + records.length > 0 + ? new Date( + Math.min( + ...records.map((record) => + new Date(record.timestamp).getTime(), + ), + ), + ) + : new Date(); - // Get start of the week for earliest time - let currentWeekStart = startOfWeek(earliestTime); - const nowWeekStart = startOfWeek(now); + // Get start of the week for earliest time + let currentWeekStart = startOfWeek(earliestTime); + const nowWeekStart = startOfWeek(now); - // Generate weekly intervals from earliest week to current week - while (currentWeekStart <= nowWeekStart) { - intervals[currentWeekStart.getTime()] = { input: 0, output: 0, cache: 0 }; - // Move to next week (7 days) - currentWeekStart = new Date(currentWeekStart.getTime() + 7 * 24 * 60 * 60 * 1000); - } + // Generate weekly intervals from earliest week to current week + while (currentWeekStart <= nowWeekStart) { + intervals[currentWeekStart.getTime()] = { + input: 0, + output: 0, + cache: 0, + }; + // Move to next week (7 days) + currentWeekStart = new Date( + currentWeekStart.getTime() + 7 * 24 * 60 * 60 * 1000, + ); + } - // Group records into weekly intervals - records.forEach((record) => { - const recordTime = new Date(record.timestamp); - const weekStart = startOfWeek(recordTime); - const weekKey = weekStart.getTime(); + // Group records into weekly intervals + records.forEach((record) => { + const recordTime = new Date(record.timestamp); + const weekStart = startOfWeek(recordTime); + const weekKey = weekStart.getTime(); - if (intervals[weekKey]) { - intervals[weekKey].input += record.usage?.input_tokens || 0; - intervals[weekKey].output += record.usage?.output_tokens || 0; - intervals[weekKey].cache += record.usage?.cache_read_input_tokens || 0; - } - }); - } else if (timeRange === "5h") { - // Group by 30-minute intervals for 5h time range - const intervalMs = 30 * 60 * 1000; // 30 minutes in milliseconds + if (intervals[weekKey]) { + intervals[weekKey].input += record.usage?.input_tokens || 0; + intervals[weekKey].output += record.usage?.output_tokens || 0; + intervals[weekKey].cache += + record.usage?.cache_read_input_tokens || 0; + } + }); + } else if (timeRange === "5h") { + // Group by 30-minute intervals for 5h time range + const intervalMs = 30 * 60 * 1000; // 30 minutes in milliseconds - // Round current time down to nearest 30-minute boundary (epoch-based) - const currentIntervalKey = Math.floor(now.getTime() / intervalMs) * intervalMs; + // Round current time down to nearest 30-minute boundary (epoch-based) + const currentIntervalKey = + Math.floor(now.getTime() / intervalMs) * intervalMs; - // Generate intervals (10 intervals for 5 hours) - for (let i = 0; i < 10; i++) { - const intervalKey = currentIntervalKey - i * intervalMs; - intervals[intervalKey] = { input: 0, output: 0, cache: 0 }; - } + // Generate intervals (10 intervals for 5 hours) + for (let i = 0; i < 10; i++) { + const intervalKey = currentIntervalKey - i * intervalMs; + intervals[intervalKey] = { input: 0, output: 0, cache: 0 }; + } - // Group records into 30-minute intervals - records.forEach((record) => { - const recordTime = new Date(record.timestamp); - const recordIntervalKey = Math.floor(recordTime.getTime() / intervalMs) * intervalMs; + // Group records into 30-minute intervals + records.forEach((record) => { + const recordTime = new Date(record.timestamp); + const recordIntervalKey = + Math.floor(recordTime.getTime() / intervalMs) * intervalMs; - if (intervals[recordIntervalKey]) { - intervals[recordIntervalKey].input += record.usage?.input_tokens || 0; - intervals[recordIntervalKey].output += record.usage?.output_tokens || 0; - intervals[recordIntervalKey].cache += record.usage?.cache_read_input_tokens || 0; - } - }); - } else if (timeRange === "today") { - // Group by hour for today - const startOfToday = startOfDay(now); - const currentHour = now.getHours(); + if (intervals[recordIntervalKey]) { + intervals[recordIntervalKey].input += record.usage?.input_tokens || 0; + intervals[recordIntervalKey].output += + record.usage?.output_tokens || 0; + intervals[recordIntervalKey].cache += + record.usage?.cache_read_input_tokens || 0; + } + }); + } else if (timeRange === "today") { + // Group by hour for today + const startOfToday = startOfDay(now); + const currentHour = now.getHours(); - for (let i = 0; i <= currentHour; i++) { - const hourTime = new Date(startOfToday.getTime() + i * 60 * 60 * 1000); - intervals[hourTime.getTime()] = { input: 0, output: 0, cache: 0 }; - } + for (let i = 0; i <= currentHour; i++) { + const hourTime = new Date(startOfToday.getTime() + i * 60 * 60 * 1000); + intervals[hourTime.getTime()] = { input: 0, output: 0, cache: 0 }; + } - records.forEach((record) => { - const recordTime = new Date(record.timestamp); - const hourStart = new Date(recordTime); - hourStart.setMinutes(0, 0, 0); - const hourKey = hourStart.getTime(); + records.forEach((record) => { + const recordTime = new Date(record.timestamp); + const hourStart = new Date(recordTime); + hourStart.setMinutes(0, 0, 0); + const hourKey = hourStart.getTime(); - if (intervals[hourKey]) { - intervals[hourKey].input += record.usage?.input_tokens || 0; - intervals[hourKey].output += record.usage?.output_tokens || 0; - intervals[hourKey].cache += record.usage?.cache_read_input_tokens || 0; - } - }); - } else { - // Group by day for longer periods (7d, week, month) - let startDate: Date; - let days: number; + if (intervals[hourKey]) { + intervals[hourKey].input += record.usage?.input_tokens || 0; + intervals[hourKey].output += record.usage?.output_tokens || 0; + intervals[hourKey].cache += + record.usage?.cache_read_input_tokens || 0; + } + }); + } else { + // Group by day for longer periods (7d, week, month) + let startDate: Date; + let days: number; - if (timeRange === "week") { - startDate = startOfWeek(now); - // Calculate actual days in the current week so far (from start of week to today) - const todayStart = startOfDay(now); - days = Math.floor((todayStart.getTime() - startDate.getTime()) / (24 * 60 * 60 * 1000)) + 1; - } else if (timeRange === "month") { - startDate = startOfMonth(now); - // Calculate actual days in the current month so far (from start of month to today) - const todayStart = startOfDay(now); - days = Math.floor((todayStart.getTime() - startDate.getTime()) / (24 * 60 * 60 * 1000)) + 1; - } else { - // For 7d, start from (days-1) days ago to include today - days = 7; - startDate = startOfDay(subDays(now, days - 1)); - } + if (timeRange === "week") { + startDate = startOfWeek(now); + // Calculate actual days in the current week so far (from start of week to today) + const todayStart = startOfDay(now); + days = + Math.floor( + (todayStart.getTime() - startDate.getTime()) / + (24 * 60 * 60 * 1000), + ) + 1; + } else if (timeRange === "month") { + startDate = startOfMonth(now); + // Calculate actual days in the current month so far (from start of month to today) + const todayStart = startOfDay(now); + days = + Math.floor( + (todayStart.getTime() - startDate.getTime()) / + (24 * 60 * 60 * 1000), + ) + 1; + } else { + // For 7d, start from (days-1) days ago to include today + days = 7; + startDate = startOfDay(subDays(now, days - 1)); + } - for (let i = 0; i < days; i++) { - const dayTime = startOfDay(new Date(startDate.getTime() + i * 24 * 60 * 60 * 1000)); - intervals[dayTime.getTime()] = { input: 0, output: 0, cache: 0 }; - } + for (let i = 0; i < days; i++) { + const dayTime = startOfDay( + new Date(startDate.getTime() + i * 24 * 60 * 60 * 1000), + ); + intervals[dayTime.getTime()] = { input: 0, output: 0, cache: 0 }; + } - records.forEach((record) => { - const recordTime = new Date(record.timestamp); - const dayStart = new Date(recordTime); - dayStart.setHours(0, 0, 0, 0); - const dayKey = dayStart.getTime(); + records.forEach((record) => { + const recordTime = new Date(record.timestamp); + const dayStart = new Date(recordTime); + dayStart.setHours(0, 0, 0, 0); + const dayKey = dayStart.getTime(); - if (intervals[dayKey]) { - intervals[dayKey].input += record.usage?.input_tokens || 0; - intervals[dayKey].output += record.usage?.output_tokens || 0; - intervals[dayKey].cache += record.usage?.cache_read_input_tokens || 0; - } - }); - } + if (intervals[dayKey]) { + intervals[dayKey].input += record.usage?.input_tokens || 0; + intervals[dayKey].output += record.usage?.output_tokens || 0; + intervals[dayKey].cache += record.usage?.cache_read_input_tokens || 0; + } + }); + } - return intervals; - }; + return intervals; + }; - const groupedData = groupDataByInterval(filteredData); + const groupedData = groupDataByInterval(filteredData); - // Prepare chart data for Recharts - const chartData = Object.keys(groupedData) - .map(Number) - .sort((a, b) => a - b) - .map((timestamp) => { - const date = new Date(timestamp); - let label: string; - if (timeRange === "all") { - label = format(date, "MMM dd, yyyy"); - } else if (timeRange === "today") { - label = format(date, "HH:mm"); - } else if (timeRange === "5h") { - label = format(date, "HH:mm"); - } else { - label = format(date, "MMM dd"); - } + // Prepare chart data for Recharts + const chartData = Object.keys(groupedData) + .map(Number) + .sort((a, b) => a - b) + .map((timestamp) => { + const date = new Date(timestamp); + let label: string; + if (timeRange === "all") { + label = format(date, "MMM dd, yyyy"); + } else if (timeRange === "today") { + label = format(date, "HH:mm"); + } else if (timeRange === "5h") { + label = format(date, "HH:mm"); + } else { + label = format(date, "MMM dd"); + } - return { - time: label, - timestamp, - "Input Tokens": groupedData[timestamp].input, - "Output Tokens": groupedData[timestamp].output, - "Cache Read Tokens": groupedData[timestamp].cache, - }; - }); + return { + time: label, + timestamp, + "Input Tokens": groupedData[timestamp].input, + "Output Tokens": groupedData[timestamp].output, + "Cache Read Tokens": groupedData[timestamp].cache, + }; + }); - if (!data || data.length === 0) { - return ( -
-

{t("usageChart.noData")}

-
- ); - } + if (!data || data.length === 0) { + return ( +
+

{t("usageChart.noData")}

+
+ ); + } - return ( -
- {/* Filter Controls */} -
-
- - -
+ return ( +
+ {/* Filter Controls */} +
+
+ + +
-
- - -
-
+
+ + +
+
- {/* Custom Legend */} -
- {[ - { key: "Input Tokens", label: t("usage.inputTokens") }, - { key: "Output Tokens", label: t("usage.outputTokens") }, - { key: "Cache Read Tokens", label: t("usage.cacheReadTokens") } - ].map(({ key, label }) => { - const isActive = activeCategories.includes(key); - const color = key === "Input Tokens" ? "bg-blue-500" : - key === "Output Tokens" ? "bg-emerald-500" : - "bg-amber-500"; - return ( - - ); - })} -
+ {/* Custom Legend */} +
+ {[ + { key: "Input Tokens", label: t("usage.inputTokens") }, + { key: "Output Tokens", label: t("usage.outputTokens") }, + { key: "Cache Read Tokens", label: t("usage.cacheReadTokens") }, + ].map(({ key, label }) => { + const isActive = activeCategories.includes(key); + const color = + key === "Input Tokens" + ? "bg-blue-500" + : key === "Output Tokens" + ? "bg-emerald-500" + : "bg-amber-500"; + return ( + + ); + })} +
- {/* Chart */} -
- { - if (cat === "Input Tokens") return "blue"; - if (cat === "Output Tokens") return "emerald"; - if (cat === "Cache Read Tokens") return "amber"; - return "blue"; - })} - valueFormatter={formatLargeNumber} - fill="gradient" - className="h-full" - showLegend={false} - /> -
-
- ); -} \ No newline at end of file + {/* Chart */} +
+ { + if (cat === "Input Tokens") return "blue"; + if (cat === "Output Tokens") return "emerald"; + if (cat === "Cache Read Tokens") return "amber"; + return "blue"; + })} + valueFormatter={formatLargeNumber} + fill="gradient" + className="h-full" + showLegend={false} + /> +
+
+ ); +} diff --git a/src/components/UpdateButton.tsx b/src/components/UpdateButton.tsx index c33f16b..cf433ee 100644 --- a/src/components/UpdateButton.tsx +++ b/src/components/UpdateButton.tsx @@ -1,46 +1,47 @@ -import { useCheckForUpdates, useInstallAndRestart } from "../lib/query"; -import { RotateCwIcon, RefreshCcwIcon } from "lucide-react"; -import { cn } from "../lib/utils"; +import { RefreshCcwIcon, RotateCwIcon } from "lucide-react"; import { useTranslation } from "react-i18next"; +import { useCheckForUpdates, useInstallAndRestart } from "../lib/query"; +import { cn } from "../lib/utils"; export function UpdateButton() { - const { t } = useTranslation(); - const { data: updateInfo, isLoading, error } = useCheckForUpdates(); - const installAndRestart = useInstallAndRestart(); + const { t } = useTranslation(); + const { data: updateInfo, isLoading, error } = useCheckForUpdates(); + const installAndRestart = useInstallAndRestart(); - if (isLoading || error) { - return null; - } + if (isLoading || error) { + return null; + } - if (!updateInfo || !updateInfo.available) { - return null; - } + if (!updateInfo || !updateInfo.available) { + return null; + } - return ( -
-
- -
-
- ); -} \ No newline at end of file + return ( +
+
+ +
+
+ ); +} diff --git a/src/components/theme-provider.tsx b/src/components/theme-provider.tsx index 4cdf4fe..ba395ab 100644 --- a/src/components/theme-provider.tsx +++ b/src/components/theme-provider.tsx @@ -1,10 +1,10 @@ -"use client" +"use client"; -import * as React from "react" -import { ThemeProvider as NextThemesProvider } from "next-themes" +import { ThemeProvider as NextThemesProvider } from "next-themes"; +import type * as React from "react"; -type ThemeProviderProps = React.ComponentProps +type ThemeProviderProps = React.ComponentProps; export function ThemeProvider({ children, ...props }: ThemeProviderProps) { - return {children} -} \ No newline at end of file + return {children}; +} diff --git a/src/components/ui/accordion.tsx b/src/components/ui/accordion.tsx index e6a723d..45a3181 100644 --- a/src/components/ui/accordion.tsx +++ b/src/components/ui/accordion.tsx @@ -1,56 +1,56 @@ -import * as React from "react" -import * as AccordionPrimitive from "@radix-ui/react-accordion" -import { ChevronDown } from "lucide-react" +import * as AccordionPrimitive from "@radix-ui/react-accordion"; +import { ChevronDown } from "lucide-react"; +import * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; -const Accordion = AccordionPrimitive.Root +const Accordion = AccordionPrimitive.Root; const AccordionItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - -)) -AccordionItem.displayName = "AccordionItem" + +)); +AccordionItem.displayName = "AccordionItem"; const AccordionTrigger = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => ( - - svg]:rotate-180", - className - )} - {...props} - > - {children} - - - -)) -AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName + + svg]:rotate-180", + className, + )} + {...props} + > + {children} + + + +)); +AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName; const AccordionContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => ( - -
{children}
-
-)) + +
{children}
+
+)); -AccordionContent.displayName = AccordionPrimitive.Content.displayName +AccordionContent.displayName = AccordionPrimitive.Content.displayName; -export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; diff --git a/src/components/ui/alert-dialog.tsx b/src/components/ui/alert-dialog.tsx index 8722561..552909b 100644 --- a/src/components/ui/alert-dialog.tsx +++ b/src/components/ui/alert-dialog.tsx @@ -1,139 +1,138 @@ -import * as React from "react" -import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" +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"; -import { cn } from "@/lib/utils" -import { buttonVariants } from "@/components/ui/button" +const AlertDialog = AlertDialogPrimitive.Root; -const AlertDialog = AlertDialogPrimitive.Root +const AlertDialogTrigger = AlertDialogPrimitive.Trigger; -const AlertDialogTrigger = AlertDialogPrimitive.Trigger - -const AlertDialogPortal = AlertDialogPrimitive.Portal +const AlertDialogPortal = AlertDialogPrimitive.Portal; const AlertDialogOverlay = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - -)) -AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName + +)); +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName; const AlertDialogContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - - - - -)) -AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName + + + + +)); +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName; const AlertDialogHeader = ({ - className, - ...props + className, + ...props }: React.HTMLAttributes) => ( -
-) -AlertDialogHeader.displayName = "AlertDialogHeader" +
+); +AlertDialogHeader.displayName = "AlertDialogHeader"; const AlertDialogFooter = ({ - className, - ...props + className, + ...props }: React.HTMLAttributes) => ( -
-) -AlertDialogFooter.displayName = "AlertDialogFooter" +
+); +AlertDialogFooter.displayName = "AlertDialogFooter"; const AlertDialogTitle = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - -)) -AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName + +)); +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName; const AlertDialogDescription = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - -)) + +)); AlertDialogDescription.displayName = - AlertDialogPrimitive.Description.displayName + AlertDialogPrimitive.Description.displayName; const AlertDialogAction = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - -)) -AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName + +)); +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName; const AlertDialogCancel = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - -)) -AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName + +)); +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName; export { - AlertDialog, - AlertDialogPortal, - AlertDialogOverlay, - AlertDialogTrigger, - AlertDialogContent, - AlertDialogHeader, - AlertDialogFooter, - AlertDialogTitle, - AlertDialogDescription, - AlertDialogAction, - AlertDialogCancel, -} + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +}; diff --git a/src/components/ui/alert.tsx b/src/components/ui/alert.tsx index 41fa7e0..30fb7e0 100644 --- a/src/components/ui/alert.tsx +++ b/src/components/ui/alert.tsx @@ -1,59 +1,59 @@ -import * as React from "react" -import { cva, type VariantProps } from "class-variance-authority" +import { cva, type VariantProps } from "class-variance-authority"; +import * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const alertVariants = cva( - "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", - { - variants: { - variant: { - default: "bg-background text-foreground", - destructive: - "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", - }, - }, - defaultVariants: { - variant: "default", - }, - } -) + "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", + { + variants: { + variant: { + default: "bg-background text-foreground", + destructive: + "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); const Alert = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes & VariantProps + HTMLDivElement, + React.HTMLAttributes & VariantProps >(({ className, variant, ...props }, ref) => ( -
-)) -Alert.displayName = "Alert" +
+)); +Alert.displayName = "Alert"; const AlertTitle = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes + HTMLParagraphElement, + React.HTMLAttributes >(({ className, ...props }, ref) => ( -
-)) -AlertTitle.displayName = "AlertTitle" +
+)); +AlertTitle.displayName = "AlertTitle"; const AlertDescription = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes + HTMLParagraphElement, + React.HTMLAttributes >(({ className, ...props }, ref) => ( -
-)) -AlertDescription.displayName = "AlertDescription" +
+)); +AlertDescription.displayName = "AlertDescription"; -export { Alert, AlertTitle, AlertDescription } +export { Alert, AlertTitle, AlertDescription }; diff --git a/src/components/ui/area-chart.tsx b/src/components/ui/area-chart.tsx index 230e672..3332aff 100644 --- a/src/components/ui/area-chart.tsx +++ b/src/components/ui/area-chart.tsx @@ -1,1003 +1,1007 @@ // Tremor AreaChart [v1.0.0] /* eslint-disable @typescript-eslint/no-explicit-any */ -"use client" +"use client"; -import React from "react" -import { RiArrowLeftSLine, RiArrowRightSLine } from "@remixicon/react" +import { RiArrowLeftSLine, RiArrowRightSLine } from "@remixicon/react"; +import React from "react"; import { - Area, - CartesianGrid, - Dot, - Label, - Line, - AreaChart as RechartsAreaChart, - Legend as RechartsLegend, - ResponsiveContainer, - Tooltip, - XAxis, - YAxis, -} from "recharts" -import type { AxisDomain } from "recharts/types/util/types" + Area, + CartesianGrid, + Dot, + Label, + Line, + AreaChart as RechartsAreaChart, + Legend as RechartsLegend, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from "recharts"; +import type { AxisDomain } from "recharts/types/util/types"; import { - AvailableChartColors, - constructCategoryColors, - getColorClassName, - getYAxisDomain, - hasOnlyOneValueForKey, - type AvailableChartColorsKeys, -} from "@/lib/chartUtils" -import { useOnWindowResize } from "@/lib/useOnWindowResize" -import { cx } from "@/lib/utils" + AvailableChartColors, + type AvailableChartColorsKeys, + constructCategoryColors, + getColorClassName, + getYAxisDomain, + hasOnlyOneValueForKey, +} from "@/lib/chartUtils"; +import { useOnWindowResize } from "@/lib/useOnWindowResize"; +import { cx } from "@/lib/utils"; //#region Legend interface LegendItemProps { - name: string - color: AvailableChartColorsKeys - onClick?: (name: string, color: AvailableChartColorsKeys) => void - activeLegend?: string + name: string; + color: AvailableChartColorsKeys; + onClick?: (name: string, color: AvailableChartColorsKeys) => void; + activeLegend?: string; } const LegendItem = ({ - name, - color, - onClick, - activeLegend, + name, + color, + onClick, + activeLegend, }: LegendItemProps) => { - const hasOnValueChange = !!onClick - return ( -
  • { - e.stopPropagation() - onClick?.(name, color) - }} - > - -

    - {name} -

    -
  • - ) -} + const hasOnValueChange = !!onClick; + return ( +
  • { + e.stopPropagation(); + onClick?.(name, color); + }} + > + +

    + {name} +

    +
  • + ); +}; interface ScrollButtonProps { - icon: React.ElementType - onClick?: () => void - disabled?: boolean + icon: React.ElementType; + onClick?: () => void; + disabled?: boolean; } const ScrollButton = ({ icon, onClick, disabled }: ScrollButtonProps) => { - const Icon = icon - const [isPressed, setIsPressed] = React.useState(false) - const intervalRef = React.useRef(null) + const Icon = icon; + const [isPressed, setIsPressed] = React.useState(false); + const intervalRef = React.useRef(null); - React.useEffect(() => { - if (isPressed) { - intervalRef.current = window.setInterval(() => { - onClick?.() - }, 300) - } else { - if (intervalRef.current !== null) { - clearInterval(intervalRef.current) - } - } - return () => { - if (intervalRef.current !== null) { - clearInterval(intervalRef.current) - } - } - }, [isPressed, onClick]) + React.useEffect(() => { + if (isPressed) { + intervalRef.current = window.setInterval(() => { + onClick?.(); + }, 300); + } else { + if (intervalRef.current !== null) { + clearInterval(intervalRef.current); + } + } + return () => { + if (intervalRef.current !== null) { + clearInterval(intervalRef.current); + } + }; + }, [isPressed, onClick]); - React.useEffect(() => { - if (disabled) { - if (intervalRef.current !== null) { - clearInterval(intervalRef.current) - } - setIsPressed(false) - } - }, [disabled]) + React.useEffect(() => { + if (disabled) { + if (intervalRef.current !== null) { + clearInterval(intervalRef.current); + } + setIsPressed(false); + } + }, [disabled]); - return ( - - ) -} + return ( + + ); +}; interface LegendProps extends React.OlHTMLAttributes { - categories: string[] - colors?: AvailableChartColorsKeys[] - onClickLegendItem?: (category: string, color: string) => void - activeLegend?: string - enableLegendSlider?: boolean + categories: string[]; + colors?: AvailableChartColorsKeys[]; + onClickLegendItem?: (category: string, color: string) => void; + activeLegend?: string; + enableLegendSlider?: boolean; } type HasScrollProps = { - left: boolean - right: boolean -} + left: boolean; + right: boolean; +}; const Legend = React.forwardRef((props, ref) => { - const { - categories, - colors = AvailableChartColors, - className, - onClickLegendItem, - activeLegend, - enableLegendSlider = false, - ...other - } = props - const scrollableRef = React.useRef(null) - const scrollButtonsRef = React.useRef(null) - const [hasScroll, setHasScroll] = React.useState(null) - const [isKeyDowned, setIsKeyDowned] = React.useState(null) - const intervalRef = React.useRef(null) + const { + categories, + colors = AvailableChartColors, + className, + onClickLegendItem, + activeLegend, + enableLegendSlider = false, + ...other + } = props; + const scrollableRef = React.useRef(null); + const scrollButtonsRef = React.useRef(null); + const [hasScroll, setHasScroll] = React.useState(null); + const [isKeyDowned, setIsKeyDowned] = React.useState(null); + const intervalRef = React.useRef(null); - const checkScroll = React.useCallback(() => { - const scrollable = scrollableRef?.current - if (!scrollable) return + const checkScroll = React.useCallback(() => { + const scrollable = scrollableRef?.current; + if (!scrollable) return; - const hasLeftScroll = scrollable.scrollLeft > 0 - const hasRightScroll = - scrollable.scrollWidth - scrollable.clientWidth > scrollable.scrollLeft + const hasLeftScroll = scrollable.scrollLeft > 0; + const hasRightScroll = + scrollable.scrollWidth - scrollable.clientWidth > scrollable.scrollLeft; - setHasScroll({ left: hasLeftScroll, right: hasRightScroll }) - }, [setHasScroll]) + setHasScroll({ left: hasLeftScroll, right: hasRightScroll }); + }, [setHasScroll]); - const scrollToTest = React.useCallback( - (direction: "left" | "right") => { - const element = scrollableRef?.current - const scrollButtons = scrollButtonsRef?.current - const scrollButtonsWith = scrollButtons?.clientWidth ?? 0 - const width = element?.clientWidth ?? 0 + const scrollToTest = React.useCallback( + (direction: "left" | "right") => { + const element = scrollableRef?.current; + const scrollButtons = scrollButtonsRef?.current; + const scrollButtonsWith = scrollButtons?.clientWidth ?? 0; + const width = element?.clientWidth ?? 0; - if (element && enableLegendSlider) { - element.scrollTo({ - left: - direction === "left" - ? element.scrollLeft - width + scrollButtonsWith - : element.scrollLeft + width - scrollButtonsWith, - behavior: "smooth", - }) - window.setTimeout(() => { - checkScroll() - }, 400) - } - }, - [enableLegendSlider, checkScroll], - ) + if (element && enableLegendSlider) { + element.scrollTo({ + left: + direction === "left" + ? element.scrollLeft - width + scrollButtonsWith + : element.scrollLeft + width - scrollButtonsWith, + behavior: "smooth", + }); + window.setTimeout(() => { + checkScroll(); + }, 400); + } + }, + [enableLegendSlider, checkScroll], + ); - React.useEffect(() => { - const keyDownHandler = (key: string) => { - if (key === "ArrowLeft") { - scrollToTest("left") - } else if (key === "ArrowRight") { - scrollToTest("right") - } - } - if (isKeyDowned) { - keyDownHandler(isKeyDowned) - intervalRef.current = window.setInterval(() => { - keyDownHandler(isKeyDowned) - }, 300) - } else { - if (intervalRef.current !== null) { - clearInterval(intervalRef.current) - } - } - return () => { - if (intervalRef.current !== null) { - clearInterval(intervalRef.current) - } - } - }, [isKeyDowned, scrollToTest]) + React.useEffect(() => { + const keyDownHandler = (key: string) => { + if (key === "ArrowLeft") { + scrollToTest("left"); + } else if (key === "ArrowRight") { + scrollToTest("right"); + } + }; + if (isKeyDowned) { + keyDownHandler(isKeyDowned); + intervalRef.current = window.setInterval(() => { + keyDownHandler(isKeyDowned); + }, 300); + } else { + if (intervalRef.current !== null) { + clearInterval(intervalRef.current); + } + } + return () => { + if (intervalRef.current !== null) { + clearInterval(intervalRef.current); + } + }; + }, [isKeyDowned, scrollToTest]); - const keyDown = (e: KeyboardEvent) => { - e.stopPropagation() - if (e.key === "ArrowLeft" || e.key === "ArrowRight") { - e.preventDefault() - setIsKeyDowned(e.key) - } - } - const keyUp = (e: KeyboardEvent) => { - e.stopPropagation() - setIsKeyDowned(null) - } + const keyDown = (e: KeyboardEvent) => { + e.stopPropagation(); + if (e.key === "ArrowLeft" || e.key === "ArrowRight") { + e.preventDefault(); + setIsKeyDowned(e.key); + } + }; + const keyUp = (e: KeyboardEvent) => { + e.stopPropagation(); + setIsKeyDowned(null); + }; - React.useEffect(() => { - const scrollable = scrollableRef?.current - if (enableLegendSlider) { - checkScroll() - scrollable?.addEventListener("keydown", keyDown) - scrollable?.addEventListener("keyup", keyUp) - } + React.useEffect(() => { + const scrollable = scrollableRef?.current; + if (enableLegendSlider) { + checkScroll(); + scrollable?.addEventListener("keydown", keyDown); + scrollable?.addEventListener("keyup", keyUp); + } - return () => { - scrollable?.removeEventListener("keydown", keyDown) - scrollable?.removeEventListener("keyup", keyUp) - } - }, [checkScroll, enableLegendSlider]) + return () => { + scrollable?.removeEventListener("keydown", keyDown); + scrollable?.removeEventListener("keyup", keyUp); + }; + }, [checkScroll, enableLegendSlider]); - return ( -
      -
      - {categories.map((category, index) => ( - - ))} -
      - {enableLegendSlider && (hasScroll?.right || hasScroll?.left) ? ( - <> -
      - { - setIsKeyDowned(null) - scrollToTest("left") - }} - disabled={!hasScroll?.left} - /> - { - setIsKeyDowned(null) - scrollToTest("right") - }} - disabled={!hasScroll?.right} - /> -
      - - ) : null} -
    - ) -}) + return ( +
      +
      + {categories.map((category, index) => ( + + ))} +
      + {enableLegendSlider && (hasScroll?.right || hasScroll?.left) ? ( + <> +
      + { + setIsKeyDowned(null); + scrollToTest("left"); + }} + disabled={!hasScroll?.left} + /> + { + setIsKeyDowned(null); + scrollToTest("right"); + }} + disabled={!hasScroll?.right} + /> +
      + + ) : null} +
    + ); +}); -Legend.displayName = "Legend" +Legend.displayName = "Legend"; const ChartLegend = ( - { payload }: any, - categoryColors: Map, - setLegendHeight: React.Dispatch>, - activeLegend: string | undefined, - onClick?: (category: string, color: string) => void, - enableLegendSlider?: boolean, - legendPosition?: "left" | "center" | "right", - yAxisWidth?: number, + { payload }: any, + categoryColors: Map, + setLegendHeight: React.Dispatch>, + activeLegend: string | undefined, + onClick?: (category: string, color: string) => void, + enableLegendSlider?: boolean, + legendPosition?: "left" | "center" | "right", + yAxisWidth?: number, ) => { - const legendRef = React.useRef(null) + const legendRef = React.useRef(null); - useOnWindowResize(() => { - const calculateHeight = (height: number | undefined) => - height ? Number(height) + 15 : 60 - setLegendHeight(calculateHeight(legendRef.current?.clientHeight)) - }) + useOnWindowResize(() => { + const calculateHeight = (height: number | undefined) => + height ? Number(height) + 15 : 60; + setLegendHeight(calculateHeight(legendRef.current?.clientHeight)); + }); - const legendPayload = payload.filter((item: any) => item.type !== "none") + const legendPayload = payload.filter((item: any) => item.type !== "none"); - const paddingLeft = - legendPosition === "left" && yAxisWidth ? yAxisWidth - 8 : 0 + const paddingLeft = + legendPosition === "left" && yAxisWidth ? yAxisWidth - 8 : 0; - return ( -
    - entry.value)} - colors={legendPayload.map((entry: any) => - categoryColors.get(entry.value), - )} - onClickLegendItem={onClick} - activeLegend={activeLegend} - enableLegendSlider={enableLegendSlider} - /> -
    - ) -} + return ( +
    + entry.value)} + colors={legendPayload.map((entry: any) => + categoryColors.get(entry.value), + )} + onClickLegendItem={onClick} + activeLegend={activeLegend} + enableLegendSlider={enableLegendSlider} + /> +
    + ); +}; //#region Tooltip -type TooltipProps = Pick +type TooltipProps = Pick; type PayloadItem = { - category: string - value: number - index: string - color: AvailableChartColorsKeys - type?: string - payload: any -} + category: string; + value: number; + index: string; + color: AvailableChartColorsKeys; + type?: string; + payload: any; +}; interface ChartTooltipProps { - active: boolean | undefined - payload: PayloadItem[] - label: string - valueFormatter: (value: number) => string + active: boolean | undefined; + payload: PayloadItem[]; + label: string; + valueFormatter: (value: number) => string; } const ChartTooltip = ({ - active, - payload, - label, - valueFormatter, + active, + payload, + label, + valueFormatter, }: ChartTooltipProps) => { - if (active && payload && payload.length) { - return ( -
    -
    -

    - {label} -

    -
    -
    - {payload.map(({ value, category, color }, index) => ( -
    -
    -
    -

    - {valueFormatter(value)} -

    -
    - ))} -
    -
    - ) - } - return null -} + if (active && payload && payload.length) { + return ( +
    +
    +

    + {label} +

    +
    +
    + {payload.map(({ value, category, color }, index) => ( +
    +
    +
    +

    + {valueFormatter(value)} +

    +
    + ))} +
    +
    + ); + } + return null; +}; //#region AreaChart interface ActiveDot { - index?: number - dataKey?: string + index?: number; + dataKey?: string; } type BaseEventProps = { - eventType: "dot" | "category" - categoryClicked: string - [key: string]: number | string -} + eventType: "dot" | "category"; + categoryClicked: string; + [key: string]: number | string; +}; -type AreaChartEventProps = BaseEventProps | null | undefined +type AreaChartEventProps = BaseEventProps | null | undefined; interface AreaChartProps extends React.HTMLAttributes { - data: Record[] - index: string - categories: string[] - colors?: AvailableChartColorsKeys[] - valueFormatter?: (value: number) => string - startEndOnly?: boolean - showXAxis?: boolean - showYAxis?: boolean - showGridLines?: boolean - yAxisWidth?: number - intervalType?: "preserveStartEnd" | "equidistantPreserveStart" - showTooltip?: boolean - showLegend?: boolean - autoMinValue?: boolean - minValue?: number - maxValue?: number - allowDecimals?: boolean - onValueChange?: (value: AreaChartEventProps) => void - enableLegendSlider?: boolean - tickGap?: number - connectNulls?: boolean - xAxisLabel?: string - yAxisLabel?: string - type?: "default" | "stacked" | "percent" - legendPosition?: "left" | "center" | "right" - fill?: "gradient" | "solid" | "none" - tooltipCallback?: (tooltipCallbackContent: TooltipProps) => void - customTooltip?: React.ComponentType + data: Record[]; + index: string; + categories: string[]; + colors?: AvailableChartColorsKeys[]; + valueFormatter?: (value: number) => string; + startEndOnly?: boolean; + showXAxis?: boolean; + showYAxis?: boolean; + showGridLines?: boolean; + yAxisWidth?: number; + intervalType?: "preserveStartEnd" | "equidistantPreserveStart"; + showTooltip?: boolean; + showLegend?: boolean; + autoMinValue?: boolean; + minValue?: number; + maxValue?: number; + allowDecimals?: boolean; + onValueChange?: (value: AreaChartEventProps) => void; + enableLegendSlider?: boolean; + tickGap?: number; + connectNulls?: boolean; + xAxisLabel?: string; + yAxisLabel?: string; + type?: "default" | "stacked" | "percent"; + legendPosition?: "left" | "center" | "right"; + fill?: "gradient" | "solid" | "none"; + tooltipCallback?: (tooltipCallbackContent: TooltipProps) => void; + customTooltip?: React.ComponentType; } const AreaChart = React.forwardRef( - (props, ref) => { - const { - data = [], - categories = [], - index, - colors = AvailableChartColors, - valueFormatter = (value: number) => value.toString(), - startEndOnly = false, - showXAxis = true, - showYAxis = true, - showGridLines = true, - yAxisWidth = 56, - intervalType = "equidistantPreserveStart", - showTooltip = true, - showLegend = true, - autoMinValue = false, - minValue, - maxValue, - allowDecimals = true, - connectNulls = false, - className, - onValueChange, - enableLegendSlider = false, - tickGap = 5, - xAxisLabel, - yAxisLabel, - type = "default", - legendPosition = "right", - fill = "gradient", - tooltipCallback, - customTooltip, - ...other - } = props - const CustomTooltip = customTooltip - const paddingValue = - (!showXAxis && !showYAxis) || (startEndOnly && !showYAxis) ? 0 : 20 - const [legendHeight, setLegendHeight] = React.useState(60) - const [activeDot, setActiveDot] = React.useState( - undefined, - ) - const [activeLegend, setActiveLegend] = React.useState( - undefined, - ) - const categoryColors = constructCategoryColors(categories, colors) + (props, ref) => { + const { + data = [], + categories = [], + index, + colors = AvailableChartColors, + valueFormatter = (value: number) => value.toString(), + startEndOnly = false, + showXAxis = true, + showYAxis = true, + showGridLines = true, + yAxisWidth = 56, + intervalType = "equidistantPreserveStart", + showTooltip = true, + showLegend = true, + autoMinValue = false, + minValue, + maxValue, + allowDecimals = true, + connectNulls = false, + className, + onValueChange, + enableLegendSlider = false, + tickGap = 5, + xAxisLabel, + yAxisLabel, + type = "default", + legendPosition = "right", + fill = "gradient", + tooltipCallback, + customTooltip, + ...other + } = props; + const CustomTooltip = customTooltip; + const paddingValue = + (!showXAxis && !showYAxis) || (startEndOnly && !showYAxis) ? 0 : 20; + const [legendHeight, setLegendHeight] = React.useState(60); + const [activeDot, setActiveDot] = React.useState( + undefined, + ); + const [activeLegend, setActiveLegend] = React.useState( + undefined, + ); + const categoryColors = constructCategoryColors(categories, colors); - const yAxisDomain = getYAxisDomain(autoMinValue, minValue, maxValue) - const hasOnValueChange = !!onValueChange - const stacked = type === "stacked" || type === "percent" - const areaId = React.useId() + const yAxisDomain = getYAxisDomain(autoMinValue, minValue, maxValue); + const hasOnValueChange = !!onValueChange; + const stacked = type === "stacked" || type === "percent"; + const areaId = React.useId(); - const prevActiveRef = React.useRef(undefined) - const prevLabelRef = React.useRef(undefined) + const prevActiveRef = React.useRef(undefined); + const prevLabelRef = React.useRef(undefined); - const getFillContent = ({ - fillType, - activeDot, - activeLegend, - category, - }: { - fillType: AreaChartProps["fill"] - activeDot: ActiveDot | undefined - activeLegend: string | undefined - category: string - }) => { - const stopOpacity = - activeDot || (activeLegend && activeLegend !== category) ? 0.1 : 0.3 + const getFillContent = ({ + fillType, + activeDot, + activeLegend, + category, + }: { + fillType: AreaChartProps["fill"]; + activeDot: ActiveDot | undefined; + activeLegend: string | undefined; + category: string; + }) => { + const stopOpacity = + activeDot || (activeLegend && activeLegend !== category) ? 0.1 : 0.3; - switch (fillType) { - case "none": - return - case "gradient": - return ( - <> - - - - ) - case "solid": - default: - return - } - } + switch (fillType) { + case "none": + return ; + case "gradient": + return ( + <> + + + + ); + case "solid": + default: + return ; + } + }; - function valueToPercent(value: number) { - return `${(value * 100).toFixed(0)}%` - } + function valueToPercent(value: number) { + return `${(value * 100).toFixed(0)}%`; + } - function onDotClick(itemData: any, event: React.MouseEvent) { - event.stopPropagation() + function onDotClick(itemData: any, event: React.MouseEvent) { + event.stopPropagation(); - if (!hasOnValueChange) return - if ( - (itemData.index === activeDot?.index && - itemData.dataKey === activeDot?.dataKey) || - (hasOnlyOneValueForKey(data, itemData.dataKey) && - activeLegend && - activeLegend === itemData.dataKey) - ) { - setActiveLegend(undefined) - setActiveDot(undefined) - onValueChange?.(null) - } else { - setActiveLegend(itemData.dataKey) - setActiveDot({ - index: itemData.index, - dataKey: itemData.dataKey, - }) - onValueChange?.({ - eventType: "dot", - categoryClicked: itemData.dataKey, - ...itemData.payload, - }) - } - } + if (!hasOnValueChange) return; + if ( + (itemData.index === activeDot?.index && + itemData.dataKey === activeDot?.dataKey) || + (hasOnlyOneValueForKey(data, itemData.dataKey) && + activeLegend && + activeLegend === itemData.dataKey) + ) { + setActiveLegend(undefined); + setActiveDot(undefined); + onValueChange?.(null); + } else { + setActiveLegend(itemData.dataKey); + setActiveDot({ + index: itemData.index, + dataKey: itemData.dataKey, + }); + onValueChange?.({ + eventType: "dot", + categoryClicked: itemData.dataKey, + ...itemData.payload, + }); + } + } - function onCategoryClick(dataKey: string) { - if (!hasOnValueChange) return - if ( - (dataKey === activeLegend && !activeDot) || - (hasOnlyOneValueForKey(data, dataKey) && - activeDot && - activeDot.dataKey === dataKey) - ) { - setActiveLegend(undefined) - onValueChange?.(null) - } else { - setActiveLegend(dataKey) - onValueChange?.({ - eventType: "category", - categoryClicked: dataKey, - }) - } - setActiveDot(undefined) - } + function onCategoryClick(dataKey: string) { + if (!hasOnValueChange) return; + if ( + (dataKey === activeLegend && !activeDot) || + (hasOnlyOneValueForKey(data, dataKey) && + activeDot && + activeDot.dataKey === dataKey) + ) { + setActiveLegend(undefined); + onValueChange?.(null); + } else { + setActiveLegend(dataKey); + onValueChange?.({ + eventType: "category", + categoryClicked: dataKey, + }); + } + setActiveDot(undefined); + } - return ( -
    - - { - setActiveDot(undefined) - setActiveLegend(undefined) - onValueChange?.(null) - } - : undefined - } - margin={{ - bottom: xAxisLabel ? 30 : undefined, - left: yAxisLabel ? 20 : undefined, - right: yAxisLabel ? 5 : undefined, - top: 5, - }} - stackOffset={type === "percent" ? "expand" : undefined} - > - {showGridLines ? ( - - ) : null} - - {xAxisLabel && ( - - )} - - - {yAxisLabel && ( - - )} - - { - const cleanPayload: TooltipProps["payload"] = payload - ? payload.map((item: any) => ({ - category: item.dataKey, - value: item.value, - index: String(item.payload[index]), - color: categoryColors.get( - item.dataKey, - ) as AvailableChartColorsKeys, - type: item.type, - payload: item.payload, - })) - : [] + return ( +
    + + { + setActiveDot(undefined); + setActiveLegend(undefined); + onValueChange?.(null); + } + : undefined + } + margin={{ + bottom: xAxisLabel ? 30 : undefined, + left: yAxisLabel ? 20 : undefined, + right: yAxisLabel ? 5 : undefined, + top: 5, + }} + stackOffset={type === "percent" ? "expand" : undefined} + > + {showGridLines ? ( + + ) : null} + + {xAxisLabel && ( + + )} + + + {yAxisLabel && ( + + )} + + { + const cleanPayload: TooltipProps["payload"] = payload + ? payload.map((item: any) => ({ + category: item.dataKey, + value: item.value, + index: String(item.payload[index]), + color: categoryColors.get( + item.dataKey, + ) as AvailableChartColorsKeys, + type: item.type, + payload: item.payload, + })) + : []; - if ( - tooltipCallback && - (active !== prevActiveRef.current || - label !== prevLabelRef.current) - ) { - tooltipCallback({ active, payload: cleanPayload, label: String(label) }) - prevActiveRef.current = active - prevLabelRef.current = String(label) - } + if ( + tooltipCallback && + (active !== prevActiveRef.current || + label !== prevLabelRef.current) + ) { + tooltipCallback({ + active, + payload: cleanPayload, + label: String(label), + }); + prevActiveRef.current = active; + prevLabelRef.current = String(label); + } - return showTooltip && active ? ( - CustomTooltip ? ( - - ) : ( - - ) - ) : null - }} - /> + return showTooltip && active ? ( + CustomTooltip ? ( + + ) : ( + + ) + ) : null; + }} + /> - {showLegend ? ( - - ChartLegend( - { payload }, - categoryColors, - setLegendHeight, - activeLegend, - hasOnValueChange - ? (clickedLegendItem: string) => - onCategoryClick(clickedLegendItem) - : undefined, - enableLegendSlider, - legendPosition, - yAxisWidth, - ) - } - /> - ) : null} - {categories.map((category) => { - const categoryId = `${areaId}-${category.replace(/[^a-zA-Z0-9]/g, "")}` - return ( - - - - {getFillContent({ - fillType: fill, - activeDot: activeDot, - activeLegend: activeLegend, - category: category, - })} - - - { - const { - cx: cxCoord, - cy: cyCoord, - stroke, - strokeLinecap, - strokeLinejoin, - strokeWidth, - dataKey, - } = props - return ( - onDotClick(props, event)} - /> - ) - }} - dot={(props: any) => { - const { - stroke, - strokeLinecap, - strokeLinejoin, - strokeWidth, - cx: cxCoord, - cy: cyCoord, - dataKey, - index, - } = props + {showLegend ? ( + + ChartLegend( + { payload }, + categoryColors, + setLegendHeight, + activeLegend, + hasOnValueChange + ? (clickedLegendItem: string) => + onCategoryClick(clickedLegendItem) + : undefined, + enableLegendSlider, + legendPosition, + yAxisWidth, + ) + } + /> + ) : null} + {categories.map((category) => { + const categoryId = `${areaId}-${category.replace(/[^a-zA-Z0-9]/g, "")}`; + return ( + + + + {getFillContent({ + fillType: fill, + activeDot: activeDot, + activeLegend: activeLegend, + category: category, + })} + + + { + const { + cx: cxCoord, + cy: cyCoord, + stroke, + strokeLinecap, + strokeLinejoin, + strokeWidth, + dataKey, + } = props; + return ( + onDotClick(props, event)} + /> + ); + }} + dot={(props: any) => { + const { + stroke, + strokeLinecap, + strokeLinejoin, + strokeWidth, + cx: cxCoord, + cy: cyCoord, + dataKey, + index, + } = props; - if ( - (hasOnlyOneValueForKey(data, category) && - !( - activeDot || - (activeLegend && activeLegend !== category) - )) || - (activeDot?.index === index && - activeDot?.dataKey === category) - ) { - return ( - - ) - } - return - }} - key={category} - name={category} - type="monotone" - dataKey={category} - stroke="" - strokeWidth={2} - strokeLinejoin="round" - strokeLinecap="round" - isAnimationActive={false} - connectNulls={connectNulls} - stackId={stacked ? "stack" : undefined} - fill={`url(#${categoryId})`} - /> - - ) - })} - {/* hidden lines to increase clickable target area */} - {onValueChange - ? categories.map((category) => ( - { - event.stopPropagation() - const { name } = props - onCategoryClick(name) - }} - /> - )) - : null} - - -
    - ) - }, -) + if ( + (hasOnlyOneValueForKey(data, category) && + !( + activeDot || + (activeLegend && activeLegend !== category) + )) || + (activeDot?.index === index && + activeDot?.dataKey === category) + ) { + return ( + + ); + } + return ; + }} + key={category} + name={category} + type="monotone" + dataKey={category} + stroke="" + strokeWidth={2} + strokeLinejoin="round" + strokeLinecap="round" + isAnimationActive={false} + connectNulls={connectNulls} + stackId={stacked ? "stack" : undefined} + fill={`url(#${categoryId})`} + /> + + ); + })} + {/* hidden lines to increase clickable target area */} + {onValueChange + ? categories.map((category) => ( + { + event.stopPropagation(); + const { name } = props; + onCategoryClick(name); + }} + /> + )) + : null} +
    +
    +
    + ); + }, +); -AreaChart.displayName = "AreaChart" +AreaChart.displayName = "AreaChart"; -export { AreaChart, type AreaChartEventProps, type TooltipProps } \ No newline at end of file +export { AreaChart, type AreaChartEventProps, type TooltipProps }; diff --git a/src/components/ui/button-group.tsx b/src/components/ui/button-group.tsx index d6e7801..3b59e08 100644 --- a/src/components/ui/button-group.tsx +++ b/src/components/ui/button-group.tsx @@ -1,83 +1,82 @@ -import { Slot } from "@radix-ui/react-slot" -import { cva, type VariantProps } from "class-variance-authority" - -import { cn } from "@/lib/utils" -import { Separator } from "@/components/ui/separator" +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; +import { Separator } from "@/components/ui/separator"; +import { cn } from "@/lib/utils"; const buttonGroupVariants = cva( - "flex w-fit items-stretch has-[>[data-slot=button-group]]:gap-2 [&>*]:focus-visible:relative [&>*]:focus-visible:z-10 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1", - { - variants: { - orientation: { - horizontal: - "[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none", - vertical: - "flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none", - }, - }, - defaultVariants: { - orientation: "horizontal", - }, - } -) + "flex w-fit items-stretch has-[>[data-slot=button-group]]:gap-2 [&>*]:focus-visible:relative [&>*]:focus-visible:z-10 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1", + { + variants: { + orientation: { + horizontal: + "[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none", + vertical: + "flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none", + }, + }, + defaultVariants: { + orientation: "horizontal", + }, + }, +); function ButtonGroup({ - className, - orientation, - ...props + className, + orientation, + ...props }: React.ComponentProps<"div"> & VariantProps) { - return ( -
    - ) + return ( +
    + ); } function ButtonGroupText({ - className, - asChild = false, - ...props + className, + asChild = false, + ...props }: React.ComponentProps<"div"> & { - asChild?: boolean + asChild?: boolean; }) { - const Comp = asChild ? Slot : "div" + const Comp = asChild ? Slot : "div"; - return ( - - ) + return ( + + ); } function ButtonGroupSeparator({ - className, - orientation = "vertical", - ...props + className, + orientation = "vertical", + ...props }: React.ComponentProps) { - return ( - - ) + return ( + + ); } export { - ButtonGroup, - ButtonGroupSeparator, - ButtonGroupText, - buttonGroupVariants, -} + ButtonGroup, + ButtonGroupSeparator, + ButtonGroupText, + buttonGroupVariants, +}; diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index 0306e7a..deaa0fb 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -1,56 +1,56 @@ -import * as React from "react" -import { Slot } from "@radix-ui/react-slot" -import { cva, type VariantProps } from "class-variance-authority" +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; +import * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", - { - variants: { - variant: { - default: "bg-primary text-primary-foreground hover:bg-primary/90", - destructive: - "bg-destructive text-destructive-foreground hover:bg-destructive/90", - outline: - "border border-input bg-background hover:bg-accent hover:text-accent-foreground", - secondary: - "bg-secondary text-secondary-foreground hover:bg-secondary/80", - ghost: "hover:bg-accent hover:text-accent-foreground", - link: "text-primary underline-offset-4 hover:underline", - }, - size: { - default: "h-10 px-4 py-2", - sm: "h-8 rounded-md px-2 text-sm", - lg: "h-11 rounded-md px-8", - icon: "h-10 w-10", - }, - }, - defaultVariants: { - variant: "default", - size: "default", - }, - } -) + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-8 rounded-md px-2 text-sm", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, +); export interface ButtonProps - extends React.ButtonHTMLAttributes, - VariantProps { - asChild?: boolean + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; } const Button = React.forwardRef( - ({ className, variant, size, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : "button" - return ( - - ) - } -) -Button.displayName = "Button" + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + return ( + + ); + }, +); +Button.displayName = "Button"; -export { Button, buttonVariants } +export { Button, buttonVariants }; diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx index 19be04c..039eae1 100644 --- a/src/components/ui/card.tsx +++ b/src/components/ui/card.tsx @@ -2,69 +2,84 @@ import * as React from "react"; import { cn } from "@/lib/utils"; const Card = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes + HTMLDivElement, + React.HTMLAttributes >(({ className, ...props }, ref) => ( -
    +
    )); Card.displayName = "Card"; const CardHeader = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes + HTMLDivElement, + React.HTMLAttributes >(({ className, ...props }, ref) => ( -
    +
    )); CardHeader.displayName = "CardHeader"; const CardTitle = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes + HTMLParagraphElement, + React.HTMLAttributes >(({ className, ...props }, ref) => ( -

    +

    )); CardTitle.displayName = "CardTitle"; const CardDescription = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes + HTMLParagraphElement, + React.HTMLAttributes >(({ className, ...props }, ref) => ( -

    +

    )); CardDescription.displayName = "CardDescription"; const CardContent = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes + HTMLDivElement, + React.HTMLAttributes >(({ className, ...props }, ref) => ( -

    +
    )); CardContent.displayName = "CardContent"; const CardFooter = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes + HTMLDivElement, + React.HTMLAttributes >(({ className, ...props }, ref) => ( -
    +
    )); CardFooter.displayName = "CardFooter"; -export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }; \ No newline at end of file +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardDescription, + CardContent, +}; diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx index 59a2645..d840653 100644 --- a/src/components/ui/command.tsx +++ b/src/components/ui/command.tsx @@ -1,153 +1,152 @@ -"use client" +"use client"; -import * as React from "react" -import { type DialogProps } from "@radix-ui/react-dialog" -import { Command as CommandPrimitive } from "cmdk" -import { Search } from "lucide-react" - -import { cn } from "@/lib/utils" -import { Dialog, DialogContent } from "@/components/ui/dialog" +import type { DialogProps } from "@radix-ui/react-dialog"; +import { Command as CommandPrimitive } from "cmdk"; +import { Search } from "lucide-react"; +import * as React from "react"; +import { Dialog, DialogContent } from "@/components/ui/dialog"; +import { cn } from "@/lib/utils"; const Command = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - -)) -Command.displayName = CommandPrimitive.displayName + +)); +Command.displayName = CommandPrimitive.displayName; const CommandDialog = ({ children, ...props }: DialogProps) => { - return ( - - - - {children} - - - - ) -} + return ( + + + + {children} + + + + ); +}; const CommandInput = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( -
    - - -
    -)) +
    + + +
    +)); -CommandInput.displayName = CommandPrimitive.Input.displayName +CommandInput.displayName = CommandPrimitive.Input.displayName; const CommandList = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - -)) + +)); -CommandList.displayName = CommandPrimitive.List.displayName +CommandList.displayName = CommandPrimitive.List.displayName; const CommandEmpty = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >((props, ref) => ( - -)) + +)); -CommandEmpty.displayName = CommandPrimitive.Empty.displayName +CommandEmpty.displayName = CommandPrimitive.Empty.displayName; const CommandGroup = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - -)) + +)); -CommandGroup.displayName = CommandPrimitive.Group.displayName +CommandGroup.displayName = CommandPrimitive.Group.displayName; const CommandSeparator = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - -)) -CommandSeparator.displayName = CommandPrimitive.Separator.displayName + +)); +CommandSeparator.displayName = CommandPrimitive.Separator.displayName; const CommandItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - -)) + +)); -CommandItem.displayName = CommandPrimitive.Item.displayName +CommandItem.displayName = CommandPrimitive.Item.displayName; const CommandShortcut = ({ - className, - ...props + className, + ...props }: React.HTMLAttributes) => { - return ( - - ) -} -CommandShortcut.displayName = "CommandShortcut" + return ( + + ); +}; +CommandShortcut.displayName = "CommandShortcut"; export { - Command, - CommandDialog, - CommandInput, - CommandList, - CommandEmpty, - CommandGroup, - CommandItem, - CommandShortcut, - CommandSeparator, -} + Command, + CommandDialog, + CommandInput, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, + CommandShortcut, + CommandSeparator, +}; diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx index dd10669..81291bd 100644 --- a/src/components/ui/dialog.tsx +++ b/src/components/ui/dialog.tsx @@ -1,124 +1,124 @@ -import * as React from "react" -import * as DialogPrimitive from "@radix-ui/react-dialog" -import { X } from "lucide-react" +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { X } from "lucide-react"; +import * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; -const Dialog = DialogPrimitive.Root +const Dialog = DialogPrimitive.Root; -const DialogTrigger = DialogPrimitive.Trigger +const DialogTrigger = DialogPrimitive.Trigger; -const DialogPortal = DialogPrimitive.Portal +const DialogPortal = DialogPrimitive.Portal; -const DialogClose = DialogPrimitive.Close +const DialogClose = DialogPrimitive.Close; const DialogOverlay = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - -)) -DialogOverlay.displayName = DialogPrimitive.Overlay.displayName + +)); +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; const DialogContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => ( - - - { - event.preventDefault() - }} - {...props} - > - {children} - - - Close - - - -)) -DialogContent.displayName = DialogPrimitive.Content.displayName + + + { + event.preventDefault(); + }} + {...props} + > + {children} + + + Close + + + +)); +DialogContent.displayName = DialogPrimitive.Content.displayName; const DialogHeader = ({ - className, - ...props + className, + ...props }: React.HTMLAttributes) => ( -
    -) -DialogHeader.displayName = "DialogHeader" +
    +); +DialogHeader.displayName = "DialogHeader"; const DialogFooter = ({ - className, - ...props + className, + ...props }: React.HTMLAttributes) => ( -
    -) -DialogFooter.displayName = "DialogFooter" +
    +); +DialogFooter.displayName = "DialogFooter"; const DialogTitle = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - -)) -DialogTitle.displayName = DialogPrimitive.Title.displayName + +)); +DialogTitle.displayName = DialogPrimitive.Title.displayName; const DialogDescription = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - -)) -DialogDescription.displayName = DialogPrimitive.Description.displayName + +)); +DialogDescription.displayName = DialogPrimitive.Description.displayName; export { - Dialog, - DialogPortal, - DialogOverlay, - DialogClose, - DialogTrigger, - DialogContent, - DialogHeader, - DialogFooter, - DialogTitle, - DialogDescription, -} + Dialog, + DialogPortal, + DialogOverlay, + DialogClose, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +}; diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx index fc790a8..67fcc06 100644 --- a/src/components/ui/dropdown-menu.tsx +++ b/src/components/ui/dropdown-menu.tsx @@ -1,198 +1,198 @@ -import * as React from "react" -import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" -import { Check, ChevronRight, Circle } from "lucide-react" +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; +import { Check, ChevronRight, Circle } from "lucide-react"; +import * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; -const DropdownMenu = DropdownMenuPrimitive.Root +const DropdownMenu = DropdownMenuPrimitive.Root; -const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; -const DropdownMenuGroup = DropdownMenuPrimitive.Group +const DropdownMenuGroup = DropdownMenuPrimitive.Group; -const DropdownMenuPortal = DropdownMenuPrimitive.Portal +const DropdownMenuPortal = DropdownMenuPrimitive.Portal; -const DropdownMenuSub = DropdownMenuPrimitive.Sub +const DropdownMenuSub = DropdownMenuPrimitive.Sub; -const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; const DropdownMenuSubTrigger = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - inset?: boolean - } + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } >(({ className, inset, children, ...props }, ref) => ( - - {children} - - -)) + + {children} + + +)); DropdownMenuSubTrigger.displayName = - DropdownMenuPrimitive.SubTrigger.displayName + DropdownMenuPrimitive.SubTrigger.displayName; const DropdownMenuSubContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - -)) + +)); DropdownMenuSubContent.displayName = - DropdownMenuPrimitive.SubContent.displayName + DropdownMenuPrimitive.SubContent.displayName; const DropdownMenuContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, sideOffset = 4, ...props }, ref) => ( - - - -)) -DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName + + + +)); +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; const DropdownMenuItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - inset?: boolean - } + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } >(({ className, inset, ...props }, ref) => ( - -)) -DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName + +)); +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; const DropdownMenuCheckboxItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, children, checked, ...props }, ref) => ( - - - - - - - {children} - -)) + + + + + + + {children} + +)); DropdownMenuCheckboxItem.displayName = - DropdownMenuPrimitive.CheckboxItem.displayName + DropdownMenuPrimitive.CheckboxItem.displayName; const DropdownMenuRadioItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => ( - - - - - - - {children} - -)) -DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName + + + + + + + {children} + +)); +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName; const DropdownMenuLabel = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - inset?: boolean - } + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } >(({ className, inset, ...props }, ref) => ( - -)) -DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName + +)); +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; const DropdownMenuSeparator = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - -)) -DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName + +)); +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; const DropdownMenuShortcut = ({ - className, - ...props + className, + ...props }: React.HTMLAttributes) => { - return ( - - ) -} -DropdownMenuShortcut.displayName = "DropdownMenuShortcut" + return ( + + ); +}; +DropdownMenuShortcut.displayName = "DropdownMenuShortcut"; export { - DropdownMenu, - DropdownMenuTrigger, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuCheckboxItem, - DropdownMenuRadioItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuShortcut, - DropdownMenuGroup, - DropdownMenuPortal, - DropdownMenuSub, - DropdownMenuSubContent, - DropdownMenuSubTrigger, - DropdownMenuRadioGroup, -} + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuRadioGroup, +}; diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx index 7265bd1..37350a3 100644 --- a/src/components/ui/input.tsx +++ b/src/components/ui/input.tsx @@ -1,22 +1,22 @@ -import * as React from "react" +import * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const Input = React.forwardRef>( - ({ className, type, ...props }, ref) => { - return ( - - ) - } -) -Input.displayName = "Input" + ({ className, type, ...props }, ref) => { + return ( + + ); + }, +); +Input.displayName = "Input"; -export { Input } +export { Input }; diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx index 683faa7..8944179 100644 --- a/src/components/ui/label.tsx +++ b/src/components/ui/label.tsx @@ -1,24 +1,24 @@ -import * as React from "react" -import * as LabelPrimitive from "@radix-ui/react-label" -import { cva, type VariantProps } from "class-variance-authority" +import * as LabelPrimitive from "@radix-ui/react-label"; +import { cva, type VariantProps } from "class-variance-authority"; +import * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const labelVariants = cva( - "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" -) + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", +); const Label = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & - VariantProps + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps >(({ className, ...props }, ref) => ( - -)) -Label.displayName = LabelPrimitive.Root.displayName + +)); +Label.displayName = LabelPrimitive.Root.displayName; -export { Label } +export { Label }; diff --git a/src/components/ui/native-select.tsx b/src/components/ui/native-select.tsx index a770e3c..c7fb0d0 100644 --- a/src/components/ui/native-select.tsx +++ b/src/components/ui/native-select.tsx @@ -1,48 +1,48 @@ -import * as React from "react" -import { ChevronDownIcon } from "lucide-react" +import { ChevronDownIcon } from "lucide-react"; +import type * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; function NativeSelect({ className, ...props }: React.ComponentProps<"select">) { - return ( -
    - +
    + ); } function NativeSelectOption({ ...props }: React.ComponentProps<"option">) { - return - ) + return ( + + ); } -export { NativeSelect, NativeSelectOptGroup, NativeSelectOption } +export { NativeSelect, NativeSelectOptGroup, NativeSelectOption }; diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx index 821a32f..411a6ed 100644 --- a/src/components/ui/popover.tsx +++ b/src/components/ui/popover.tsx @@ -1,29 +1,29 @@ -import * as React from "react" -import * as PopoverPrimitive from "@radix-ui/react-popover" +import * as PopoverPrimitive from "@radix-ui/react-popover"; +import * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; -const Popover = PopoverPrimitive.Root +const Popover = PopoverPrimitive.Root; -const PopoverTrigger = PopoverPrimitive.Trigger +const PopoverTrigger = PopoverPrimitive.Trigger; const PopoverContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( - - - -)) -PopoverContent.displayName = PopoverPrimitive.Content.displayName + + + +)); +PopoverContent.displayName = PopoverPrimitive.Content.displayName; -export { Popover, PopoverTrigger, PopoverContent } +export { Popover, PopoverTrigger, PopoverContent }; diff --git a/src/components/ui/scroll-area.tsx b/src/components/ui/scroll-area.tsx index cf253cf..7efdc68 100644 --- a/src/components/ui/scroll-area.tsx +++ b/src/components/ui/scroll-area.tsx @@ -1,46 +1,46 @@ -import * as React from "react" -import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" +import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"; +import * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const ScrollArea = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => ( - - - {children} - - - - -)) -ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName + + + {children} + + + + +)); +ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName; const ScrollBar = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, orientation = "vertical", ...props }, ref) => ( - - - -)) -ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName + + + +)); +ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName; -export { ScrollArea, ScrollBar } +export { ScrollArea, ScrollBar }; diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx index 0b84971..4dedebf 100644 --- a/src/components/ui/select.tsx +++ b/src/components/ui/select.tsx @@ -1,158 +1,158 @@ -import * as React from "react" -import * as SelectPrimitive from "@radix-ui/react-select" -import { Check, ChevronDown, ChevronUp } from "lucide-react" +import * as SelectPrimitive from "@radix-ui/react-select"; +import { Check, ChevronDown, ChevronUp } from "lucide-react"; +import * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; -const Select = SelectPrimitive.Root +const Select = SelectPrimitive.Root; -const SelectGroup = SelectPrimitive.Group +const SelectGroup = SelectPrimitive.Group; -const SelectValue = SelectPrimitive.Value +const SelectValue = SelectPrimitive.Value; const SelectTrigger = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => ( - span]:line-clamp-1", - className - )} - {...props} - > - {children} - - - - -)) -SelectTrigger.displayName = SelectPrimitive.Trigger.displayName + span]:line-clamp-1", + className, + )} + {...props} + > + {children} + + + + +)); +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; const SelectScrollUpButton = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - - - -)) -SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName + + + +)); +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName; const SelectScrollDownButton = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - - - -)) + + + +)); SelectScrollDownButton.displayName = - SelectPrimitive.ScrollDownButton.displayName + SelectPrimitive.ScrollDownButton.displayName; const SelectContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, children, position = "popper", ...props }, ref) => ( - - - - - {children} - - - - -)) -SelectContent.displayName = SelectPrimitive.Content.displayName + + + + + {children} + + + + +)); +SelectContent.displayName = SelectPrimitive.Content.displayName; const SelectLabel = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - -)) -SelectLabel.displayName = SelectPrimitive.Label.displayName + +)); +SelectLabel.displayName = SelectPrimitive.Label.displayName; const SelectItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => ( - - - - - - + + + + + + - {children} - -)) -SelectItem.displayName = SelectPrimitive.Item.displayName + {children} + +)); +SelectItem.displayName = SelectPrimitive.Item.displayName; const SelectSeparator = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - -)) -SelectSeparator.displayName = SelectPrimitive.Separator.displayName + +)); +SelectSeparator.displayName = SelectPrimitive.Separator.displayName; export { - Select, - SelectGroup, - SelectValue, - SelectTrigger, - SelectContent, - SelectLabel, - SelectItem, - SelectSeparator, - SelectScrollUpButton, - SelectScrollDownButton, -} + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, + SelectSeparator, + SelectScrollUpButton, + SelectScrollDownButton, +}; diff --git a/src/components/ui/separator.tsx b/src/components/ui/separator.tsx index bb3ad74..a384c99 100644 --- a/src/components/ui/separator.tsx +++ b/src/components/ui/separator.tsx @@ -1,26 +1,26 @@ -import * as React from "react" -import * as SeparatorPrimitive from "@radix-ui/react-separator" +import * as SeparatorPrimitive from "@radix-ui/react-separator"; +import type * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; function Separator({ - className, - orientation = "horizontal", - decorative = true, - ...props + className, + orientation = "horizontal", + decorative = true, + ...props }: React.ComponentProps) { - return ( - - ) + return ( + + ); } -export { Separator } +export { Separator }; diff --git a/src/components/ui/skeleton.tsx b/src/components/ui/skeleton.tsx index 01b8b6d..925b4da 100644 --- a/src/components/ui/skeleton.tsx +++ b/src/components/ui/skeleton.tsx @@ -1,15 +1,15 @@ -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; function Skeleton({ - className, - ...props + className, + ...props }: React.HTMLAttributes) { - return ( -
    - ) + return ( +
    + ); } -export { Skeleton } +export { Skeleton }; diff --git a/src/components/ui/sonner.tsx b/src/components/ui/sonner.tsx index 5399822..adddffc 100644 --- a/src/components/ui/sonner.tsx +++ b/src/components/ui/sonner.tsx @@ -1,26 +1,26 @@ -import { Toaster as Sonner } from "sonner" +import { Toaster as Sonner } from "sonner"; -type ToasterProps = React.ComponentProps +type ToasterProps = React.ComponentProps; const Toaster = ({ ...props }: ToasterProps) => { - return ( - - ) -} + return ( + + ); +}; -export { Toaster } +export { Toaster }; diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx index aa58baa..9680902 100644 --- a/src/components/ui/switch.tsx +++ b/src/components/ui/switch.tsx @@ -1,27 +1,27 @@ -import * as React from "react" -import * as SwitchPrimitives from "@radix-ui/react-switch" +import * as SwitchPrimitives from "@radix-ui/react-switch"; +import * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const Switch = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - - - -)) -Switch.displayName = SwitchPrimitives.Root.displayName + + + +)); +Switch.displayName = SwitchPrimitives.Root.displayName; -export { Switch } +export { Switch }; diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx index f57fffd..e54c215 100644 --- a/src/components/ui/tabs.tsx +++ b/src/components/ui/tabs.tsx @@ -1,53 +1,53 @@ -import * as React from "react" -import * as TabsPrimitive from "@radix-ui/react-tabs" +import * as TabsPrimitive from "@radix-ui/react-tabs"; +import * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; -const Tabs = TabsPrimitive.Root +const Tabs = TabsPrimitive.Root; const TabsList = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - -)) -TabsList.displayName = TabsPrimitive.List.displayName + +)); +TabsList.displayName = TabsPrimitive.List.displayName; const TabsTrigger = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - -)) -TabsTrigger.displayName = TabsPrimitive.Trigger.displayName + +)); +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName; const TabsContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - -)) -TabsContent.displayName = TabsPrimitive.Content.displayName + +)); +TabsContent.displayName = TabsPrimitive.Content.displayName; -export { Tabs, TabsList, TabsTrigger, TabsContent } +export { Tabs, TabsList, TabsTrigger, TabsContent }; diff --git a/src/components/ui/textarea.tsx b/src/components/ui/textarea.tsx index 4d858bb..9611cb0 100644 --- a/src/components/ui/textarea.tsx +++ b/src/components/ui/textarea.tsx @@ -1,22 +1,22 @@ -import * as React from "react" +import * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const Textarea = React.forwardRef< - HTMLTextAreaElement, - React.ComponentProps<"textarea"> + HTMLTextAreaElement, + React.ComponentProps<"textarea"> >(({ className, ...props }, ref) => { - return ( -