From 19c81550b6186232ddad3172f187c002a4e8bbe9 Mon Sep 17 00:00:00 2001 From: Hemanand Date: Fri, 8 Aug 2025 08:46:22 +0530 Subject: [PATCH 1/5] some update in theme --- .claude/settings.local.json | 13 +- CLAUDE.md | 27 +- ThingConnect.Pulse.Server/Program.cs | 34 +- .../Properties/launchSettings.json | 9 +- package-lock.json | 1913 +++++++++++++++++ package.json | 5 + thingconnect.pulse.client/index.html | 6 +- .../public/thingconnect-icon.svg | 9 + thingconnect.pulse.client/src/App.tsx | 95 +- .../src/assets/ThingConnect Icon.png | Bin 0 -> 77912 bytes .../src/assets/ThingConnect Icon.svg | 9 + .../src/assets/ThingConnect Logo.png | Bin 0 -> 128665 bytes .../src/assets/ThingConnect Logo.svg | 12 + .../src/components/auth/LoginForm.tsx | 53 +- .../src/components/auth/RegisterForm.tsx | 47 +- .../src/components/layout/AppLayout.tsx | 41 + .../src/components/ui/logo.tsx | 31 + .../src/components/ui/provider.tsx | 5 +- .../src/components/ui/theme-toggle.tsx | 154 ++ thingconnect.pulse.client/src/main.tsx | 1 + thingconnect.pulse.client/src/theme/README.md | 171 ++ .../src/theme/global.css | 45 + thingconnect.pulse.client/src/theme/index.ts | 630 ++++++ 23 files changed, 3261 insertions(+), 49 deletions(-) create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 thingconnect.pulse.client/public/thingconnect-icon.svg create mode 100644 thingconnect.pulse.client/src/assets/ThingConnect Icon.png create mode 100644 thingconnect.pulse.client/src/assets/ThingConnect Icon.svg create mode 100644 thingconnect.pulse.client/src/assets/ThingConnect Logo.png create mode 100644 thingconnect.pulse.client/src/assets/ThingConnect Logo.svg create mode 100644 thingconnect.pulse.client/src/components/layout/AppLayout.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/logo.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/theme-toggle.tsx create mode 100644 thingconnect.pulse.client/src/theme/README.md create mode 100644 thingconnect.pulse.client/src/theme/global.css create mode 100644 thingconnect.pulse.client/src/theme/index.ts diff --git a/.claude/settings.local.json b/.claude/settings.local.json index e23884f..aef1b40 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -3,7 +3,18 @@ "allow": [ "Bash(npm run build:*)", "WebFetch(domain:chakra-ui.com)", - "Bash(npm run lint)" + "Bash(npm run lint)", + "Bash(git branch:*)", + "Bash(git checkout:*)", + "mcp__chakra-ui__get_theme", + "mcp__chakra-ui__customize_theme", + "Bash(npx @chakra-ui/cli typegen:*)", + "Bash(npm run dev:*)", + "Bash(dotnet run:*)", + "Bash(cp:*)", + "WebFetch(domain:atlassian.design)", + "WebFetch(domain:atlaskit.atlassian.com)", + "mcp__chakra-ui__get_component_props" ], "deny": [] } diff --git a/CLAUDE.md b/CLAUDE.md index 48ad95f..e35bbdb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,24 +21,37 @@ ThingConnect.Pulse is a full-stack web application with: ## Development Commands -### Frontend (run from `thingconnect.pulse.client/`) +**IMPORTANT**: For development, run backend and frontend servers separately for optimal development experience. + +### Frontend Development (run from `thingconnect.pulse.client/`) ```bash -npm run dev # Start development server (Vite) +npm run dev # Start Vite development server (https://localhost:49812 or similar) npm run build # Build for production (TypeScript compile + Vite build) npm run lint # Run ESLint npm run format # Format code with Prettier npm run preview # Preview production build ``` -### Backend (run from `ThingConnect.Pulse.Server/`) +### Backend Development (run from `ThingConnect.Pulse.Server/`) ```bash -dotnet run # Start development server -dotnet build # Build the project +dotnet run # Start ASP.NET Core API server (https://localhost:7286) +dotnet build # Build the backend project +dotnet watch # Start with hot reload for development ``` -### Full Stack (run from solution root) +### Development Workflow +1. **Terminal 1**: Start backend server from `ThingConnect.Pulse.Server/` + ```bash + dotnet run + ``` +2. **Terminal 2**: Start frontend server from `thingconnect.pulse.client/` + ```bash + npm run dev + ``` + +### Production Build (run from solution root) ```bash -dotnet run --project ThingConnect.Pulse.Server # Starts both backend and frontend via SPA proxy +dotnet run --project ThingConnect.Pulse.Server # Serves built frontend via SPA proxy ``` ## Development Setup diff --git a/ThingConnect.Pulse.Server/Program.cs b/ThingConnect.Pulse.Server/Program.cs index 66a60e2..a3e5f79 100644 --- a/ThingConnect.Pulse.Server/Program.cs +++ b/ThingConnect.Pulse.Server/Program.cs @@ -42,6 +42,18 @@ public static void Main(string[] args) }; }); + // Add CORS for development + builder.Services.AddCors(options => + { + options.AddPolicy("DevelopmentCors", policy => + { + policy.WithOrigins("https://localhost:49812", "http://localhost:49812", "https://localhost:5173", "http://localhost:5173") + .AllowAnyHeader() + .AllowAnyMethod() + .AllowCredentials(); + }); + }); + builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); @@ -49,25 +61,37 @@ public static void Main(string[] args) var app = builder.Build(); - app.UseDefaultFiles(); - app.UseStaticFiles(); - // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } + else + { + // Only serve static files in production + app.UseDefaultFiles(); + app.UseStaticFiles(); + } app.UseHttpsRedirection(); + // Use CORS in development + if (app.Environment.IsDevelopment()) + { + app.UseCors("DevelopmentCors"); + } + app.UseAuthentication(); app.UseAuthorization(); - app.MapControllers(); - app.MapFallbackToFile("/index.html"); + // Only serve SPA fallback in production + if (!app.Environment.IsDevelopment()) + { + app.MapFallbackToFile("/index.html"); + } app.Run(); } diff --git a/ThingConnect.Pulse.Server/Properties/launchSettings.json b/ThingConnect.Pulse.Server/Properties/launchSettings.json index 4b8b570..6ebb27a 100644 --- a/ThingConnect.Pulse.Server/Properties/launchSettings.json +++ b/ThingConnect.Pulse.Server/Properties/launchSettings.json @@ -5,8 +5,7 @@ "launchBrowser": true, "launchUrl": "swagger", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy" + "ASPNETCORE_ENVIRONMENT": "Development" }, "dotnetRunMessages": true, "applicationUrl": "http://localhost:5099" @@ -16,8 +15,7 @@ "launchBrowser": true, "launchUrl": "swagger", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy" + "ASPNETCORE_ENVIRONMENT": "Development" }, "dotnetRunMessages": true, "applicationUrl": "https://localhost:7286;http://localhost:5099" @@ -27,8 +25,7 @@ "launchBrowser": true, "launchUrl": "swagger", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy" + "ASPNETCORE_ENVIRONMENT": "Development" } }, "Container (Dockerfile)": { diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..22a494f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1913 @@ +{ + "name": "ThingConnect.Pulse", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "devDependencies": { + "puppeteer": "^24.15.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@puppeteer/browsers": { + "version": "2.10.6", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.6.tgz", + "integrity": "sha512-pHUn6ZRt39bP3698HFQlu2ZHCkS/lPcpv7fVQcGBSzNNygw171UXAKrCUhy+TEMw4lEttOKDgNpb04hwUAJeiQ==", + "dev": true, + "dependencies": { + "debug": "^4.4.1", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.5.0", + "semver": "^7.7.2", + "tar-fs": "^3.1.0", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.0.tgz", + "integrity": "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==", + "dev": true, + "optional": true, + "dependencies": { + "undici-types": "~7.10.0" + } + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "dev": true + }, + "node_modules/bare-events": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.6.0.tgz", + "integrity": "sha512-EKZ5BTXYExaNqi3I3f9RtEsaI/xBSGjE0XZCZilPzFAV/goswFHuPd9jEZlPIZ/iNZJwDSao9qRiScySz7MbQg==", + "dev": true, + "optional": true + }, + "node_modules/bare-fs": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.1.6.tgz", + "integrity": "sha512-25RsLF33BqooOEFNdMcEhMpJy8EoR88zSMrnOQOaM3USnOK2VmaJ1uaQEwPA6AQjrv1lXChScosN6CzbwbO9OQ==", + "dev": true, + "optional": true, + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.1.tgz", + "integrity": "sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==", + "dev": true, + "optional": true, + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "dev": true, + "optional": true, + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.6.5.tgz", + "integrity": "sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==", + "dev": true, + "optional": true, + "dependencies": { + "streamx": "^2.21.0" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chromium-bidi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-7.2.0.tgz", + "integrity": "sha512-gREyhyBstermK+0RbcJLbFhcQctg92AGgDe/h/taMJEOLRdtSswBAO9KmvltFSQWgM2LrwWu5SIuEUbdm3JsyQ==", + "dev": true, + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1464554", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1464554.tgz", + "integrity": "sha512-CAoP3lYfwAGQTaAXYvA6JZR0fjGUb7qec1qf4mToyoH2TZgUFeIqYcjh6f9jNuhHfuZiEdH+PONHYrLhRQX6aw==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-uri": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "dev": true, + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "dev": true, + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/puppeteer": { + "version": "24.15.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.15.0.tgz", + "integrity": "sha512-HPSOTw+DFsU/5s2TUUWEum9WjFbyjmvFDuGHtj2X4YUz2AzOzvKMkT3+A3FR+E+ZefiX/h3kyLyXzWJWx/eMLQ==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@puppeteer/browsers": "2.10.6", + "chromium-bidi": "7.2.0", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1464554", + "puppeteer-core": "24.15.0", + "typed-query-selector": "^2.12.0" + }, + "bin": { + "puppeteer": "lib/cjs/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core": { + "version": "24.15.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.15.0.tgz", + "integrity": "sha512-2iy0iBeWbNyhgiCGd/wvGrDSo73emNFjSxYOcyAqYiagkYt5q4cPfVXaVDKBsukgc2fIIfLAalBZlaxldxdDYg==", + "dev": true, + "dependencies": { + "@puppeteer/browsers": "2.10.6", + "chromium-bidi": "7.2.0", + "debug": "^4.4.1", + "devtools-protocol": "0.0.1464554", + "typed-query-selector": "^2.12.0", + "ws": "^8.18.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.6.tgz", + "integrity": "sha512-pe4Y2yzru68lXCb38aAqRf5gvN8YdjP1lok5o0J7BOHljkyCGKVz7H3vpVIXKD27rj2giOJ7DwVyk/GWrPHDWA==", + "dev": true, + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true + }, + "node_modules/streamx": { + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.1.tgz", + "integrity": "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==", + "dev": true, + "dependencies": { + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar-fs": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.0.tgz", + "integrity": "sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==", + "dev": true, + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "dev": true, + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + }, + "node_modules/typed-query-selector": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", + "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", + "dev": true + }, + "node_modules/undici-types": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", + "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", + "dev": true, + "optional": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true + }, + "@puppeteer/browsers": { + "version": "2.10.6", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.6.tgz", + "integrity": "sha512-pHUn6ZRt39bP3698HFQlu2ZHCkS/lPcpv7fVQcGBSzNNygw171UXAKrCUhy+TEMw4lEttOKDgNpb04hwUAJeiQ==", + "dev": true, + "requires": { + "debug": "^4.4.1", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.5.0", + "semver": "^7.7.2", + "tar-fs": "^3.1.0", + "yargs": "^17.7.2" + } + }, + "@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true + }, + "@types/node": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.0.tgz", + "integrity": "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==", + "dev": true, + "optional": true, + "requires": { + "undici-types": "~7.10.0" + } + }, + "@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "optional": true, + "requires": { + "@types/node": "*" + } + }, + "agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "requires": { + "tslib": "^2.0.1" + } + }, + "b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "dev": true + }, + "bare-events": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.6.0.tgz", + "integrity": "sha512-EKZ5BTXYExaNqi3I3f9RtEsaI/xBSGjE0XZCZilPzFAV/goswFHuPd9jEZlPIZ/iNZJwDSao9qRiScySz7MbQg==", + "dev": true, + "optional": true + }, + "bare-fs": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.1.6.tgz", + "integrity": "sha512-25RsLF33BqooOEFNdMcEhMpJy8EoR88zSMrnOQOaM3USnOK2VmaJ1uaQEwPA6AQjrv1lXChScosN6CzbwbO9OQ==", + "dev": true, + "optional": true, + "requires": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4" + } + }, + "bare-os": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.1.tgz", + "integrity": "sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==", + "dev": true, + "optional": true + }, + "bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "dev": true, + "optional": true, + "requires": { + "bare-os": "^3.0.1" + } + }, + "bare-stream": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.6.5.tgz", + "integrity": "sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==", + "dev": true, + "optional": true, + "requires": { + "streamx": "^2.21.0" + } + }, + "basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "dev": true + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "chromium-bidi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-7.2.0.tgz", + "integrity": "sha512-gREyhyBstermK+0RbcJLbFhcQctg92AGgDe/h/taMJEOLRdtSswBAO9KmvltFSQWgM2LrwWu5SIuEUbdm3JsyQ==", + "dev": true, + "requires": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + } + }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "requires": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + } + }, + "data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true + }, + "debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "requires": { + "ms": "^2.1.3" + } + }, + "degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "requires": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + } + }, + "devtools-protocol": { + "version": "0.0.1464554", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1464554.tgz", + "integrity": "sha512-CAoP3lYfwAGQTaAXYvA6JZR0fjGUb7qec1qf4mToyoH2TZgUFeIqYcjh6f9jNuhHfuZiEdH+PONHYrLhRQX6aw==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true + }, + "escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "source-map": "~0.6.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "requires": { + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + } + }, + "fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true + }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "requires": { + "pend": "~1.2.0" + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "get-uri": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "dev": true, + "requires": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + } + }, + "http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "requires": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + } + }, + "https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "requires": { + "agent-base": "^7.1.2", + "debug": "4" + } + }, + "import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, + "requires": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true + }, + "mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "dev": true, + "requires": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + } + }, + "pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "requires": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true + }, + "picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "dev": true, + "requires": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + } + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, + "pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "puppeteer": { + "version": "24.15.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.15.0.tgz", + "integrity": "sha512-HPSOTw+DFsU/5s2TUUWEum9WjFbyjmvFDuGHtj2X4YUz2AzOzvKMkT3+A3FR+E+ZefiX/h3kyLyXzWJWx/eMLQ==", + "dev": true, + "requires": { + "@puppeteer/browsers": "2.10.6", + "chromium-bidi": "7.2.0", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1464554", + "puppeteer-core": "24.15.0", + "typed-query-selector": "^2.12.0" + } + }, + "puppeteer-core": { + "version": "24.15.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.15.0.tgz", + "integrity": "sha512-2iy0iBeWbNyhgiCGd/wvGrDSo73emNFjSxYOcyAqYiagkYt5q4cPfVXaVDKBsukgc2fIIfLAalBZlaxldxdDYg==", + "dev": true, + "requires": { + "@puppeteer/browsers": "2.10.6", + "chromium-bidi": "7.2.0", + "debug": "^4.4.1", + "devtools-protocol": "0.0.1464554", + "typed-query-selector": "^2.12.0", + "ws": "^8.18.3" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true + }, + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true + }, + "socks": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.6.tgz", + "integrity": "sha512-pe4Y2yzru68lXCb38aAqRf5gvN8YdjP1lok5o0J7BOHljkyCGKVz7H3vpVIXKD27rj2giOJ7DwVyk/GWrPHDWA==", + "dev": true, + "requires": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + } + }, + "socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "requires": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + }, + "sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true + }, + "streamx": { + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.1.tgz", + "integrity": "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==", + "dev": true, + "requires": { + "bare-events": "^2.2.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "tar-fs": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.0.tgz", + "integrity": "sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==", + "dev": true, + "requires": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + } + }, + "tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "requires": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "dev": true, + "requires": { + "b4a": "^1.6.4" + } + }, + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + }, + "typed-query-selector": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", + "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", + "dev": true + }, + "undici-types": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", + "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", + "dev": true, + "optional": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "requires": {} + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true + }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..cbfa284 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "puppeteer": "^24.15.0" + } +} diff --git a/thingconnect.pulse.client/index.html b/thingconnect.pulse.client/index.html index 7fcdf32..fc481dd 100644 --- a/thingconnect.pulse.client/index.html +++ b/thingconnect.pulse.client/index.html @@ -2,9 +2,11 @@ - + - ThingConnect - Pulse + ThingConnect Pulse + +
diff --git a/thingconnect.pulse.client/public/thingconnect-icon.svg b/thingconnect.pulse.client/public/thingconnect-icon.svg new file mode 100644 index 0000000..18a3c6c --- /dev/null +++ b/thingconnect.pulse.client/public/thingconnect-icon.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/thingconnect.pulse.client/src/App.tsx b/thingconnect.pulse.client/src/App.tsx index 1271f8d..fa657f3 100644 --- a/thingconnect.pulse.client/src/App.tsx +++ b/thingconnect.pulse.client/src/App.tsx @@ -1,5 +1,98 @@ +import { useState, useEffect } from 'react' +import { AuthScreen } from './components/auth/AuthScreen' +import { AppLayout } from './components/layout/AppLayout' +import { authService } from './services/authService' +import { Box, Button, Heading, Text, VStack, HStack } from '@chakra-ui/react' +import { Logo } from './components/ui/logo' + function App() { - return <> + const [isAuthenticated, setIsAuthenticated] = useState(false) + const [isLoading, setIsLoading] = useState(true) + + useEffect(() => { + // Check if user is already authenticated on app start + const checkAuth = () => { + try { + const isLoggedIn = authService.isAuthenticated() + setIsAuthenticated(isLoggedIn) + } catch (error) { + console.error('Auth check failed:', error) + setIsAuthenticated(false) + } finally { + setIsLoading(false) + } + } + + void checkAuth() + }, []) + + const handleAuthSuccess = () => { + setIsAuthenticated(true) + } + + const handleLogout = () => { + authService.logout() + setIsAuthenticated(false) + } + + if (isLoading) { + return ( + + + Loading... + + + ) + } + + if (!isAuthenticated) { + return ( + + + + ) + } + + // Main authenticated app content + return ( + + + + + + + + Welcome to Pulse + + + + You are successfully logged in! This is where your main application content will go. + + + + + + Enterprise Dashboard + + + Your authenticated dashboard content goes here. The theme automatically adapts to light and dark modes using Atlassian Design System principles. + + + + + + + + + + ) } export default App diff --git a/thingconnect.pulse.client/src/assets/ThingConnect Icon.png b/thingconnect.pulse.client/src/assets/ThingConnect Icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f60cbf1478cdf14c1bab4e10b93a2c4ac2f2ffdf GIT binary patch literal 77912 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 + + + + + + + + \ No newline at end of file diff --git a/thingconnect.pulse.client/src/assets/ThingConnect Logo.png b/thingconnect.pulse.client/src/assets/ThingConnect Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..e03cb1a503a3a231b4a96307a92f7f4edccf9e46 GIT binary patch literal 128665 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 literal 0 HcmV?d00001 diff --git a/thingconnect.pulse.client/src/assets/ThingConnect Logo.svg b/thingconnect.pulse.client/src/assets/ThingConnect Logo.svg new file mode 100644 index 0000000..c825f31 --- /dev/null +++ b/thingconnect.pulse.client/src/assets/ThingConnect Logo.svg @@ -0,0 +1,12 @@ + + + ThingConnect + + + + + + + + + \ No newline at end of file diff --git a/thingconnect.pulse.client/src/components/auth/LoginForm.tsx b/thingconnect.pulse.client/src/components/auth/LoginForm.tsx index d32ff1b..8b7b45e 100644 --- a/thingconnect.pulse.client/src/components/auth/LoginForm.tsx +++ b/thingconnect.pulse.client/src/components/auth/LoginForm.tsx @@ -5,7 +5,9 @@ import { Input, Stack, Text, + VStack, } from '@chakra-ui/react'; +import { Logo } from '../ui/logo'; interface LoginFormProps { onLogin: (username: string, password: string) => Promise; @@ -24,55 +26,82 @@ export const LoginForm = ({ onLogin, onSwitchToRegister, isLoading, error }: Log }; return ( - - - Login + + + + + Login to Pulse + + + {error && ( - - {error} + + {error} )} - Username * + Username * setUsername(e.target.value)} placeholder="Enter your username" required + bg="color.background.default" + borderColor="border" + borderRadius="base" + p="100" + _focus={{ borderColor: "brand.solid", boxShadow: "0 0 0 2px var(--chakra-colors-brand-200)" }} /> - Password * + Password * setPassword(e.target.value)} placeholder="Enter your password" required + bg="color.background.default" + borderColor="border" + borderRadius="base" + p="100" + _focus={{ borderColor: "brand.solid", boxShadow: "0 0 0 2px var(--chakra-colors-brand-200)" }} /> - + Don't have an account?{' '} - - + + ); }; \ No newline at end of file diff --git a/thingconnect.pulse.client/src/components/auth/RegisterForm.tsx b/thingconnect.pulse.client/src/components/auth/RegisterForm.tsx index cadedd1..73c3efa 100644 --- a/thingconnect.pulse.client/src/components/auth/RegisterForm.tsx +++ b/thingconnect.pulse.client/src/components/auth/RegisterForm.tsx @@ -5,7 +5,9 @@ import { Input, Stack, Text, + VStack, } from '@chakra-ui/react'; +import { Logo } from '../ui/logo'; interface RegisterFormProps { onRegister: (username: string, email: string, password: string) => Promise; @@ -33,60 +35,77 @@ export const RegisterForm = ({ onRegister, onSwitchToLogin, isLoading, error }: const passwordMismatch = password !== confirmPassword && confirmPassword !== ''; return ( - - - Register + + + + + Join Pulse + + + {error && ( - + {error} )} - Username * + Username * setUsername(e.target.value)} placeholder="Choose a username" required + bg="bg.panel" + borderColor="border" + _focus={{ borderColor: "brand.solid", boxShadow: "0 0 0 1px var(--chakra-colors-brand-solid)" }} /> - Email * + Email * setEmail(e.target.value)} placeholder="Enter your email" required + bg="bg.panel" + borderColor="border" + _focus={{ borderColor: "brand.solid", boxShadow: "0 0 0 1px var(--chakra-colors-brand-solid)" }} /> - Password * + Password * setPassword(e.target.value)} placeholder="Create a password" required + bg="bg.panel" + borderColor="border" + _focus={{ borderColor: "brand.solid", boxShadow: "0 0 0 1px var(--chakra-colors-brand-solid)" }} /> - Confirm Password * + Confirm Password * setConfirmPassword(e.target.value)} placeholder="Confirm your password" required + bg="bg.panel" + borderColor="border" + _focus={{ borderColor: "brand.solid", boxShadow: "0 0 0 1px var(--chakra-colors-brand-solid)" }} /> {passwordMismatch && ( - + Passwords do not match )} @@ -94,22 +113,24 @@ export const RegisterForm = ({ onRegister, onSwitchToLogin, isLoading, error }: - + Already have an account?{' '} - - + + ); }; \ No newline at end of file diff --git a/thingconnect.pulse.client/src/components/layout/AppLayout.tsx b/thingconnect.pulse.client/src/components/layout/AppLayout.tsx new file mode 100644 index 0000000..ac68d49 --- /dev/null +++ b/thingconnect.pulse.client/src/components/layout/AppLayout.tsx @@ -0,0 +1,41 @@ +import { Box, Flex, HStack } from "@chakra-ui/react" +import { ColorModeButton } from "../ui/color-mode" +import { Logo } from "../ui/logo" +import type { ReactNode } from "react" + +interface AppLayoutProps { + children: ReactNode + showHeader?: boolean +} + +export function AppLayout({ children, showHeader = true }: AppLayoutProps) { + return ( + + {showHeader && ( + + + + {/* Show full logo on larger screens, icon on mobile */} + + + + + + + + .Pulse + + + + + + + + )} + + + {children} + + + ) +} \ No newline at end of file diff --git a/thingconnect.pulse.client/src/components/ui/logo.tsx b/thingconnect.pulse.client/src/components/ui/logo.tsx new file mode 100644 index 0000000..75b220c --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/logo.tsx @@ -0,0 +1,31 @@ +import { Image, type ImageProps } from "@chakra-ui/react" +import ThingConnectLogo from "../../assets/ThingConnect Logo.svg" +import ThingConnectIcon from "../../assets/ThingConnect Icon.svg" + +interface LogoProps extends Omit { + variant?: 'full' | 'icon' + size?: 'sm' | 'md' | 'lg' | 'xl' +} + +const sizeMap = { + sm: { full: "30px", icon: "24px" }, + md: { full: "40px", icon: "32px" }, + lg: { full: "50px", icon: "40px" }, + xl: { full: "60px", icon: "48px" }, +} + +export function Logo({ variant = 'full', size = 'md', ...props }: LogoProps) { + const logoSrc = variant === 'full' ? ThingConnectLogo : ThingConnectIcon + const logoAlt = variant === 'full' ? 'ThingConnect Logo' : 'ThingConnect' + const logoHeight = sizeMap[size][variant] + + return ( + {logoAlt} + ) +} \ No newline at end of file diff --git a/thingconnect.pulse.client/src/components/ui/provider.tsx b/thingconnect.pulse.client/src/components/ui/provider.tsx index fd0331b..afd7f9b 100644 --- a/thingconnect.pulse.client/src/components/ui/provider.tsx +++ b/thingconnect.pulse.client/src/components/ui/provider.tsx @@ -1,14 +1,15 @@ "use client" -import { ChakraProvider, defaultSystem } from "@chakra-ui/react" +import { ChakraProvider } from "@chakra-ui/react" import { ColorModeProvider, type ColorModeProviderProps, } from "./color-mode" +import { system } from "../../theme" export function Provider(props: ColorModeProviderProps) { return ( - + ) diff --git a/thingconnect.pulse.client/src/components/ui/theme-toggle.tsx b/thingconnect.pulse.client/src/components/ui/theme-toggle.tsx new file mode 100644 index 0000000..b515617 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/theme-toggle.tsx @@ -0,0 +1,154 @@ +"use client" + +import { + Button, + HStack, + VStack, + Text, + Box, + Heading, + Stack +} from "@chakra-ui/react" +import { ColorModeButton } from "./color-mode" +import { LuPalette, LuSun, LuMoon } from "react-icons/lu" +import { useColorMode } from "../../lib/color-mode" + +export function ThemeToggle() { + const { colorMode, toggleColorMode } = useColorMode() + + return ( + + + + + Theme Settings + + + + Switch between light and dark themes for the best viewing experience + + + + + + + + + + + Current theme: + + + {colorMode === "light" ? "Light" : "Dark"} + + + + + + ) +} + +export function EnterpriseThemeShowcase() { + return ( + + + + ThingConnect.Pulse + + + Enterprise Theme System with Light & Dark Mode Support + + + + + + + {/* Brand Colors Card */} + + + Brand Colors + + + Primary + + + Success + + + Warning + + + Danger + + + + + + {/* Typography Card */} + + + Typography + + Heading Large + Body Medium Text + Caption Text + Overline Text + + + + + + {/* Layout Examples */} + + + Section Style + + This uses the enterprise.section layer style for content areas. + + + + + Header Style + + This uses the enterprise.header layer style for navigation areas. + + + + + + + 🎨 Professional enterprise theme with semantic color tokens that automatically adapt to light and dark modes + + + + ) +} \ No newline at end of file diff --git a/thingconnect.pulse.client/src/main.tsx b/thingconnect.pulse.client/src/main.tsx index a797eeb..94bef7b 100644 --- a/thingconnect.pulse.client/src/main.tsx +++ b/thingconnect.pulse.client/src/main.tsx @@ -2,6 +2,7 @@ import { StrictMode } from 'react' import { Provider } from "@/components/ui/provider" import { createRoot } from 'react-dom/client' import App from './App.tsx' +import './theme/global.css' createRoot(document.getElementById('root')!).render( diff --git a/thingconnect.pulse.client/src/theme/README.md b/thingconnect.pulse.client/src/theme/README.md new file mode 100644 index 0000000..8efcea3 --- /dev/null +++ b/thingconnect.pulse.client/src/theme/README.md @@ -0,0 +1,171 @@ +# ThingConnect.Pulse Theme System + +This directory contains the enterprise-focused theme configuration for ThingConnect.Pulse, built with Chakra UI v3, incorporating authentic ThingConnect brand colors, and following Atlassian Design System principles. + +## Design System Foundation + +### 🏗️ Atlassian Design System Integration +- **Design Tokens**: Follows Atlassian's token naming conventions and structure +- **4px Grid System**: Consistent spacing based on Atlassian's rhythm +- **Surface System**: Semantic background colors (default, sunken, raised, overlay) +- **Elevation**: Shadow system with light/dark mode variations +- **Typography**: 2025 typography refresh with bolder fonts and improved readability + +### 🎨 ThingConnect Brand Colors +- **Primary Brand**: `#076bb3` - The signature ThingConnect blue extracted from logo +- **Secondary Accent**: `#16a5a4` - The distinctive ThingConnect teal from logo +- **Supporting Colors**: Dark variants `#124771` and `#097a7d` for depth and contrast +- **Semantic Colors**: Automatic light/dark mode support with semantic tokens +- **Status Colors**: Success, warning, danger, and neutral color schemes +- **Accessibility**: WCAG compliant color contrasts + +### 🌙 Dark/Light Mode Support +- Seamless theme switching with `next-themes` +- Automatic system preference detection +- Smooth transitions between modes +- Enterprise-optimized color schemes for both modes + +### 📐 Typography System +- Inter font family for modern, readable text +- Comprehensive text styles (display, heading, body, caption, overline) +- Consistent spacing and line heights +- Professional letter spacing + +### 🧱 Layout Styles +Pre-built layer styles for common enterprise UI patterns: +- `enterprise.card` - Card containers with proper shadows and borders +- `enterprise.section` - Content sections with subtle backgrounds +- `enterprise.header` - Navigation and header areas +- `enterprise.sidebar` - Sidebar layouts with borders + +## Usage + +### Atlassian-Style Usage +```tsx +import { Box, Text, Heading } from "@chakra-ui/react" + +function MyComponent() { + return ( + + Enterprise Title + + Professional content with Atlassian design principles + + + ) +} +``` + +### Legacy Enterprise Usage (Still Supported) +```tsx +function LegacyComponent() { + return ( + + Legacy Title + + Backward compatible with previous theme + + + ) +} +``` + +### Color Palette Usage +```tsx +// Use semantic tokens that automatically adapt to light/dark mode + + + + Themed container + +``` + +### Theme Toggle +```tsx +import { ColorModeButton } from "@/components/ui/color-mode" +import { ThemeToggle } from "@/components/ui/theme-toggle" + +// Simple toggle button + + +// Full theme selection UI + +``` + +## File Structure + +``` +src/theme/ +├── index.ts # Main theme configuration +├── global.css # Global CSS styles +└── README.md # This documentation +``` + +## Customization + +To modify the theme: + +1. Edit `src/theme/index.ts` to adjust colors, typography, or layout styles +2. Run the typegen command to update TypeScript types: + ```bash + npx @chakra-ui/cli typegen src/theme/index.ts + ``` +3. Restart your development server + +## Color Tokens + +### ThingConnect Brand Colors +- `brand.50` through `brand.950` - Full ThingConnect blue palette based on `#076bb3` +- `brand.solid` - Primary ThingConnect blue (`#076bb3`) +- `brand.fg` - Text color that adapts to light/dark mode +- `brand.muted` - Subtle background color +- `brand.emphasized` - Highlighted background color + +### ThingConnect Accent Colors +- `accent.50` through `accent.950` - Full ThingConnect teal palette based on `#16a5a4` +- `accent.solid` - Secondary ThingConnect teal (`#16a5a4`) +- `accent.fg` - Accent text color that adapts to light/dark mode +- `accent.muted` - Subtle accent background color +- `accent.emphasized` - Highlighted accent background color + +### Semantic Colors +- `bg` - Main background color +- `bg.panel` - Panel/card background +- `bg.muted` - Muted background +- `fg` - Primary text color +- `fg.muted` - Secondary text color +- `border` - Default border color +- `border.subtle` - Subtle border color + +## Typography Styles + +### Display Text +- `display.large` - 7xl, bold, tight spacing +- `display.medium` - 6xl, bold, tight spacing +- `display.small` - 5xl, bold, comfortable spacing + +### Headings +- `heading.large` - 4xl, semibold +- `heading.medium` - 3xl, semibold +- `heading.small` - 2xl, semibold + +### Body Text +- `body.large` - lg, normal weight +- `body.medium` - md, normal weight +- `body.small` - sm, normal weight + +### Utility Text +- `caption` - xs, medium weight, tracked +- `overline` - 2xs, bold, uppercase, widely tracked + +## Development + +The theme system includes automatic TypeScript type generation. After modifying the theme, run: + +```bash +npx @chakra-ui/cli typegen src/theme/index.ts +``` + +This ensures full type safety and autocompletion for all custom tokens and styles. \ No newline at end of file diff --git a/thingconnect.pulse.client/src/theme/global.css b/thingconnect.pulse.client/src/theme/global.css new file mode 100644 index 0000000..979be20 --- /dev/null +++ b/thingconnect.pulse.client/src/theme/global.css @@ -0,0 +1,45 @@ +/* Enterprise Theme Global Styles */ + +/* Font smoothing for better text rendering */ +body { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* Improve focus visibility for accessibility */ +*:focus-visible { + outline: 2px solid var(--chakra-colors-brand-solid); + outline-offset: 2px; +} + +/* Smooth transitions for theme switching */ +* { + transition: background-color 0.2s ease-in-out, + border-color 0.2s ease-in-out, + color 0.2s ease-in-out; +} + +/* Improve scrollbar styling in webkit browsers */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: var(--chakra-colors-bg-muted); +} + +::-webkit-scrollbar-thumb { + background: var(--chakra-colors-border-subtle); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--chakra-colors-border); +} + +/* Better selection colors */ +::selection { + background: var(--chakra-colors-brand-emphasized); + color: var(--chakra-colors-brand-contrast); +} \ No newline at end of file diff --git a/thingconnect.pulse.client/src/theme/index.ts b/thingconnect.pulse.client/src/theme/index.ts new file mode 100644 index 0000000..620bc9b --- /dev/null +++ b/thingconnect.pulse.client/src/theme/index.ts @@ -0,0 +1,630 @@ +import { createSystem, defaultConfig, defineConfig } from "@chakra-ui/react" + +const config = defineConfig({ + theme: { + tokens: { + colors: { + // Atlassian brand colors - primary blue palette (B400 = #0052CC) + brand: { + 50: { value: "#e6f2ff" }, + 100: { value: "#cce6ff" }, + 200: { value: "#99ccff" }, + 300: { value: "#66b3ff" }, + 400: { value: "#0052cc" }, // Atlassian primary blue (B400) + 500: { value: "#0052cc" }, // Atlassian primary blue (B400) + 600: { value: "#0046b3" }, + 700: { value: "#003a99" }, + 800: { value: "#002e80" }, + 900: { value: "#002266" }, + 950: { value: "#001a4d" }, + }, + // Atlassian neutral surfaces - clean whites and dark backgrounds + surfaceDefault: { + 50: { value: "#ffffff" }, // Atlassian white (N0) + 900: { value: "#091e42" } // Atlassian dark blue (N900) + }, + surfaceSunken: { + 50: { value: "#f4f5f7" }, // Atlassian light gray (N20) + 900: { value: "#0c2140" } // Atlassian darker blue + }, + surfaceRaised: { + 50: { value: "#ffffff" }, // Atlassian white (N0) + 900: { value: "#172b4d" } // Atlassian neutral text (N800) + }, + surfaceOverlay: { + 50: { value: "#ffffff" }, // Atlassian white (N0) + 900: { value: "#253858" } // Atlassian modal overlay + }, + // Atlassian teal/secondary colors (T300 = #00B8D9) + accent: { + 50: { value: "#e6fcff" }, + 100: { value: "#ccf9ff" }, + 200: { value: "#99f2ff" }, + 300: { value: "#66ebff" }, + 400: { value: "#33e4ff" }, + 500: { value: "#00b8d9" }, // Atlassian teal (T300) + 600: { value: "#00a3c7" }, + 700: { value: "#008fb5" }, + 800: { value: "#007ba3" }, + 900: { value: "#006691" }, + 950: { value: "#00527f" }, + }, + // Atlassian neutral grays (N800 = #172B4D for text) + neutral: { + 50: { value: "#fafbfc" }, // Atlassian light (N10) + 100: { value: "#f4f5f7" }, // Atlassian light gray (N20) + 200: { value: "#ebecf0" }, // Atlassian border (N40) + 300: { value: "#dfe1e6" }, // Atlassian subtle (N60) + 400: { value: "#97a0af" }, // Atlassian muted (N200) + 500: { value: "#6b778c" }, // Atlassian secondary (N300) + 600: { value: "#505f79" }, // Atlassian primary text (N500) + 700: { value: "#42526e" }, // Atlassian darker text (N600) + 800: { value: "#172b4d" }, // Atlassian text dark (N800) + 900: { value: "#091e42" }, // Atlassian darkest (N900) + 950: { value: "#061b35" }, // Atlassian deep dark + }, + // Atlassian success colors (G300 = #36B37E) + success: { + 50: { value: "#e3fcef" }, + 100: { value: "#c7f9df" }, + 200: { value: "#8ff2bf" }, + 300: { value: "#57eb9f" }, + 400: { value: "#36b37e" }, // Atlassian green (G300) + 500: { value: "#36b37e" }, // Atlassian green (G300) + 600: { value: "#2e9f6b" }, + 700: { value: "#268b58" }, + 800: { value: "#1e7745" }, + 900: { value: "#166332" }, + 950: { value: "#0e4f1f" }, + }, + // Atlassian warning colors (Y300 = #FFAB00) + warning: { + 50: { value: "#fff8e6" }, + 100: { value: "#fff0cc" }, + 200: { value: "#ffe199" }, + 300: { value: "#ffd266" }, + 400: { value: "#ffc333" }, + 500: { value: "#ffab00" }, // Atlassian yellow (Y300) + 600: { value: "#e6970e" }, + 700: { value: "#cc831c" }, + 800: { value: "#b36f2a" }, + 900: { value: "#995b38" }, + 950: { value: "#804746" }, + }, + // Atlassian error/danger colors (R300 = #FF5630) + danger: { + 50: { value: "#ffebe6" }, + 100: { value: "#ffd6cc" }, + 200: { value: "#ffad99" }, + 300: { value: "#ff8566" }, + 400: { value: "#ff5c33" }, + 500: { value: "#ff5630" }, // Atlassian red (R300) + 600: { value: "#e64a2b" }, + 700: { value: "#cc3e26" }, + 800: { value: "#b33221" }, + 900: { value: "#99261c" }, + 950: { value: "#801a17" }, + }, + }, + fonts: { + heading: { value: "'Atlassian Sans', ui-sans-serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" }, + body: { value: "'Atlassian Sans', ui-sans-serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" }, + mono: { value: "SF Mono, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace" }, + }, + fontSizes: { + "2xs": { value: "0.625rem" }, // 10px + xs: { value: "0.75rem" }, // 12px + sm: { value: "0.875rem" }, // 14px + md: { value: "1rem" }, // 16px + lg: { value: "1.125rem" }, // 18px + xl: { value: "1.25rem" }, // 20px + "2xl": { value: "1.5rem" }, // 24px + "3xl": { value: "1.875rem" }, // 30px + "4xl": { value: "2.25rem" }, // 36px + "5xl": { value: "3rem" }, // 48px + "6xl": { value: "3.75rem" }, // 60px + "7xl": { value: "4.5rem" }, // 72px + }, + spacing: { + // Atlassian Design System 4px grid spacing tokens + "025": { value: "0.125rem" }, // 2px - minimal spacing + "050": { value: "0.25rem" }, // 4px - space.050 + "075": { value: "0.375rem" }, // 6px - space.075 + "100": { value: "0.5rem" }, // 8px - space.100 + "150": { value: "0.75rem" }, // 12px - space.150 + "200": { value: "1rem" }, // 16px - space.200 + "250": { value: "1.25rem" }, // 20px - space.250 + "300": { value: "1.5rem" }, // 24px - space.300 + "400": { value: "2rem" }, // 32px - space.400 + "500": { value: "2.5rem" }, // 40px - space.500 + "600": { value: "3rem" }, // 48px - space.600 + "800": { value: "4rem" }, // 64px - space.800 + "1000": { value: "5rem" }, // 80px - space.1000 + }, + radii: { + none: { value: "0" }, + sm: { value: "0.125rem" }, // 2px + base: { value: "0.1875rem" }, // 3px - Atlassian button border radius + md: { value: "0.25rem" }, // 4px + lg: { value: "0.375rem" }, // 6px + xl: { value: "0.5rem" }, // 8px + "2xl": { value: "0.75rem" }, // 12px + "3xl": { value: "1rem" }, // 16px + full: { value: "9999px" }, + }, + shadows: { + // Atlassian-inspired shadow system - subtle and clean + raised: { value: "0 1px 1px rgba(9, 30, 66, 0.25), 0 0 1px rgba(9, 30, 66, 0.31)" }, + overlay: { value: "0 8px 16px rgba(9, 30, 66, 0.25), 0 0 1px rgba(9, 30, 66, 0.31)" }, + "raised.dark": { value: "0 1px 1px rgba(0, 0, 0, 0.25), 0 0 1px rgba(0, 0, 0, 0.31)" }, + "overlay.dark": { value: "0 8px 16px rgba(0, 0, 0, 0.36), 0 0 1px rgba(0, 0, 0, 0.31)" }, + // Atlassian elevation shadows + xs: { value: "0 1px 1px rgba(9, 30, 66, 0.25)" }, + sm: { value: "0 1px 1px rgba(9, 30, 66, 0.25), 0 0 1px rgba(9, 30, 66, 0.31)" }, + base: { value: "0 4px 8px rgba(9, 30, 66, 0.25), 0 0 1px rgba(9, 30, 66, 0.31)" }, + md: { value: "0 8px 16px rgba(9, 30, 66, 0.25), 0 0 1px rgba(9, 30, 66, 0.31)" }, + lg: { value: "0 16px 24px rgba(9, 30, 66, 0.25), 0 0 1px rgba(9, 30, 66, 0.31)" }, + }, + gradients: { + // Atlassian subtle gradients for overlays and backgrounds + "brand.subtle": { value: "linear-gradient(135deg, #0052cc 0%, #0065ff 100%)" }, + "accent.subtle": { value: "linear-gradient(135deg, #00b8d9 0%, #36b37e 100%)" }, + "surface.overlay": { value: "linear-gradient(rgba(9, 30, 66, 0), rgba(9, 30, 66, 0.6))" }, + "surface.overlay.light": { value: "linear-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.9))" }, + }, + }, + semanticTokens: { + colors: { + // ThingConnect brand semantic tokens + brand: { + solid: { value: "{colors.brand.500}" }, + contrast: { value: "white" }, + fg: { + value: { + _light: "{colors.brand.600}", + _dark: "{colors.brand.400}" + } + }, + muted: { + value: { + _light: "{colors.brand.50}", + _dark: "{colors.brand.950}" + } + }, + subtle: { + value: { + _light: "{colors.brand.100}", + _dark: "{colors.brand.900}" + } + }, + emphasized: { + value: { + _light: "{colors.brand.200}", + _dark: "{colors.brand.800}" + } + }, + focusRing: { value: "{colors.brand.500}" }, + }, + // ThingConnect accent/secondary semantic tokens + accent: { + solid: { value: "{colors.accent.500}" }, + contrast: { value: "white" }, + fg: { + value: { + _light: "{colors.accent.600}", + _dark: "{colors.accent.400}" + } + }, + muted: { + value: { + _light: "{colors.accent.50}", + _dark: "{colors.accent.950}" + } + }, + subtle: { + value: { + _light: "{colors.accent.100}", + _dark: "{colors.accent.900}" + } + }, + emphasized: { + value: { + _light: "{colors.accent.200}", + _dark: "{colors.accent.800}" + } + }, + focusRing: { value: "{colors.accent.500}" }, + }, + // Success semantic tokens + success: { + solid: { value: "{colors.success.500}" }, + contrast: { value: "white" }, + fg: { + value: { + _light: "{colors.success.600}", + _dark: "{colors.success.400}" + } + }, + muted: { + value: { + _light: "{colors.success.50}", + _dark: "{colors.success.950}" + } + }, + subtle: { + value: { + _light: "{colors.success.100}", + _dark: "{colors.success.900}" + } + }, + emphasized: { + value: { + _light: "{colors.success.200}", + _dark: "{colors.success.800}" + } + }, + focusRing: { value: "{colors.success.500}" }, + }, + // Warning semantic tokens + warning: { + solid: { value: "{colors.warning.500}" }, + contrast: { value: "white" }, + fg: { + value: { + _light: "{colors.warning.600}", + _dark: "{colors.warning.400}" + } + }, + muted: { + value: { + _light: "{colors.warning.50}", + _dark: "{colors.warning.950}" + } + }, + subtle: { + value: { + _light: "{colors.warning.100}", + _dark: "{colors.warning.900}" + } + }, + emphasized: { + value: { + _light: "{colors.warning.200}", + _dark: "{colors.warning.800}" + } + }, + focusRing: { value: "{colors.warning.500}" }, + }, + // Danger semantic tokens + danger: { + solid: { value: "{colors.danger.500}" }, + contrast: { value: "white" }, + fg: { + value: { + _light: "{colors.danger.600}", + _dark: "{colors.danger.400}" + } + }, + muted: { + value: { + _light: "{colors.danger.50}", + _dark: "{colors.danger.950}" + } + }, + subtle: { + value: { + _light: "{colors.danger.100}", + _dark: "{colors.danger.900}" + } + }, + emphasized: { + value: { + _light: "{colors.danger.200}", + _dark: "{colors.danger.800}" + } + }, + focusRing: { value: "{colors.danger.500}" }, + }, + // Neutral semantic tokens + neutral: { + solid: { value: "{colors.neutral.500}" }, + contrast: { value: "white" }, + fg: { + value: { + _light: "{colors.neutral.600}", + _dark: "{colors.neutral.400}" + } + }, + muted: { + value: { + _light: "{colors.neutral.50}", + _dark: "{colors.neutral.950}" + } + }, + subtle: { + value: { + _light: "{colors.neutral.100}", + _dark: "{colors.neutral.900}" + } + }, + emphasized: { + value: { + _light: "{colors.neutral.200}", + _dark: "{colors.neutral.800}" + } + }, + focusRing: { value: "{colors.neutral.500}" }, + }, + // Atlassian-style surface semantic tokens + "color.background.default": { + value: { + _light: "{colors.surfaceDefault.50}", + _dark: "{colors.surfaceDefault.900}" + } + }, + "color.background.sunken": { + value: { + _light: "{colors.surfaceSunken.50}", + _dark: "{colors.surfaceSunken.900}" + } + }, + "color.background.raised": { + value: { + _light: "{colors.surfaceRaised.50}", + _dark: "{colors.surfaceRaised.900}" + } + }, + "color.background.overlay": { + value: { + _light: "{colors.surfaceOverlay.50}", + _dark: "{colors.surfaceOverlay.900}" + } + }, + // Legacy background tokens for compatibility + bg: { + value: { + _light: "{colors.surfaceSunken.50}", + _dark: "{colors.surfaceSunken.900}" + } + }, + "bg.panel": { + value: { + _light: "{colors.surfaceDefault.50}", + _dark: "{colors.surfaceDefault.900}" + } + }, + fg: { + value: { + _light: "{colors.neutral.900}", + _dark: "{colors.neutral.100}" + } + }, + "fg.muted": { + value: { + _light: "{colors.neutral.600}", + _dark: "{colors.neutral.400}" + } + }, + "fg.subtle": { + value: { + _light: "{colors.neutral.500}", + _dark: "{colors.neutral.500}" + } + }, + border: { + value: { + _light: "{colors.neutral.200}", + _dark: "{colors.neutral.700}" + } + }, + "border.muted": { + value: { + _light: "{colors.neutral.100}", + _dark: "{colors.neutral.800}" + } + }, + "border.subtle": { + value: { + _light: "{colors.neutral.300}", + _dark: "{colors.neutral.600}" + } + }, + }, + }, + textStyles: { + // Atlassian typography scale - clean, accessible typography + "ui.heading.xxlarge": { + value: { + fontSize: "7xl", // 72px + fontWeight: "600", // Atlassian medium weight + lineHeight: "1.125", + letterSpacing: "-0.01em", + }, + }, + "ui.heading.xlarge": { + value: { + fontSize: "6xl", // 60px + fontWeight: "600", + lineHeight: "1.133", + letterSpacing: "-0.008em", + }, + }, + "ui.heading.large": { + value: { + fontSize: "5xl", // 48px + fontWeight: "600", + lineHeight: "1.167", + letterSpacing: "-0.006em", + }, + }, + "ui.heading.medium": { + value: { + fontSize: "4xl", // 36px + fontWeight: "500", // Atlassian medium + lineHeight: "1.222", + letterSpacing: "-0.004em", + }, + }, + "ui.heading.small": { + value: { + fontSize: "3xl", // 30px + fontWeight: "500", + lineHeight: "1.267", + letterSpacing: "-0.002em", + }, + }, + "ui.heading.xsmall": { + value: { + fontSize: "2xl", // 24px + fontWeight: "500", + lineHeight: "1.333", + }, + }, + "ui.heading.xxsmall": { + value: { + fontSize: "xl", // 20px + fontWeight: "500", + lineHeight: "1.4", + }, + }, + // Atlassian body text styles - optimized for readability + "ui.body.large": { + value: { + fontSize: "lg", // 18px + fontWeight: "400", + lineHeight: "1.556", // ~28px line height + }, + }, + "ui.body.medium": { + value: { + fontSize: "md", // 16px + fontWeight: "400", + lineHeight: "1.5", // 24px line height + }, + }, + "ui.body.small": { + value: { + fontSize: "sm", // 14px + fontWeight: "400", + lineHeight: "1.429", // ~20px line height + }, + }, + // Helper text styles + "ui.helper": { + value: { + fontSize: "xs", // 12px + fontWeight: "400", + lineHeight: "1.333", // 16px line height + }, + }, + // Code text style + "ui.code": { + value: { + fontSize: "sm", // 14px + fontWeight: "400", + lineHeight: "1.571", // ~22px + fontFamily: "mono", + }, + }, + }, + layerStyles: { + // Atlassian surface styles - clean and accessible + "atlassian.card": { + value: { + bg: "color.background.raised", + borderRadius: "base", // 3px - Atlassian border radius + border: "1px solid", + borderColor: "border.muted", + boxShadow: "raised", + p: "200", // 16px - consistent spacing + }, + }, + "atlassian.panel": { + value: { + bg: "color.background.sunken", + borderRadius: "base", + border: "1px solid", + borderColor: "border.subtle", + p: "300", // 24px + }, + }, + "atlassian.header": { + value: { + bg: "color.background.default", + borderBottom: "1px solid", + borderColor: "border.muted", + px: "200", // 16px + py: "150", // 12px + minHeight: "44px", // Atlassian touch target + }, + }, + "atlassian.modal": { + value: { + bg: "color.background.overlay", + borderRadius: "lg", // Larger radius for modals + boxShadow: "overlay", + p: "300", // 24px + }, + }, + "atlassian.button.primary": { + value: { + bg: "brand.500", + color: "white", + borderRadius: "base", // 3px + px: "200", // 16px + py: "100", // 8px + minHeight: "32px", // Atlassian button height + }, + }, + "atlassian.button.secondary": { + value: { + bg: "transparent", + color: "brand.500", + border: "1px solid", + borderColor: "brand.500", + borderRadius: "base", // 3px + px: "200", // 16px + py: "100", // 8px + minHeight: "32px", // Atlassian button height + }, + }, + // Legacy styles for backward compatibility + "enterprise.card": { + value: { + bg: "color.background.raised", + borderRadius: "base", + boxShadow: "raised", + p: "300", + }, + }, + "enterprise.header": { + value: { + bg: "color.background.default", + borderBottom: "1px solid", + borderColor: "border", + px: "300", + py: "200", + }, + }, + }, + }, + globalCss: { + "html": { + colorScheme: "light dark", + }, + "body": { + bg: "bg", + color: "fg", + fontFamily: "body", + lineHeight: "1.6", + }, + "*": { + borderColor: "border", + }, + "*::placeholder": { + color: "fg.muted", + }, + }, +}) + +export const system = createSystem(defaultConfig, config) \ No newline at end of file From c8b29df69f0ef9be8c649f95aa873369f960de70 Mon Sep 17 00:00:00 2001 From: Hemanand Date: Fri, 8 Aug 2025 08:56:52 +0530 Subject: [PATCH 2/5] Add React Router for authentication and routing - Installed react-router-dom - Configured BrowserRouter in main.tsx - Implemented protected routes with authentication - Created DashboardPage and ProtectedRoute components - Added routing logic to App component --- thingconnect.pulse.client/src/App.tsx | 86 +++++++------------ .../src/components/routing/ProtectedRoute.tsx | 22 +++++ thingconnect.pulse.client/src/main.tsx | 5 +- .../src/pages/DashboardPage.tsx | 50 +++++++++++ 4 files changed, 108 insertions(+), 55 deletions(-) create mode 100644 thingconnect.pulse.client/src/components/routing/ProtectedRoute.tsx create mode 100644 thingconnect.pulse.client/src/pages/DashboardPage.tsx diff --git a/thingconnect.pulse.client/src/App.tsx b/thingconnect.pulse.client/src/App.tsx index fa657f3..9b5ef64 100644 --- a/thingconnect.pulse.client/src/App.tsx +++ b/thingconnect.pulse.client/src/App.tsx @@ -1,16 +1,17 @@ import { useState, useEffect } from 'react' +import { Routes, Route, Navigate } from 'react-router-dom' import { AuthScreen } from './components/auth/AuthScreen' import { AppLayout } from './components/layout/AppLayout' import { authService } from './services/authService' -import { Box, Button, Heading, Text, VStack, HStack } from '@chakra-ui/react' -import { Logo } from './components/ui/logo' +import { Box, Text } from '@chakra-ui/react' +import DashboardPage from './pages/DashboardPage' +import ProtectedRoute from './components/routing/ProtectedRoute' function App() { const [isAuthenticated, setIsAuthenticated] = useState(false) const [isLoading, setIsLoading] = useState(true) useEffect(() => { - // Check if user is already authenticated on app start const checkAuth = () => { try { const isLoggedIn = authService.isAuthenticated() @@ -30,11 +31,6 @@ function App() { setIsAuthenticated(true) } - const handleLogout = () => { - authService.logout() - setIsAuthenticated(false) - } - if (isLoading) { return ( @@ -45,53 +41,35 @@ function App() { ) } - if (!isAuthenticated) { - return ( - - - - ) - } - - // Main authenticated app content return ( - - - - - - - - Welcome to Pulse - - - - You are successfully logged in! This is where your main application content will go. - - - - - - Enterprise Dashboard - - - Your authenticated dashboard content goes here. The theme automatically adapts to light and dark modes using Atlassian Design System principles. - - - - - - - - - + + + + + ) : ( + + ) + } + /> + + + + } + /> + : + } + /> + ) } diff --git a/thingconnect.pulse.client/src/components/routing/ProtectedRoute.tsx b/thingconnect.pulse.client/src/components/routing/ProtectedRoute.tsx new file mode 100644 index 0000000..87396bd --- /dev/null +++ b/thingconnect.pulse.client/src/components/routing/ProtectedRoute.tsx @@ -0,0 +1,22 @@ +import { ReactNode } from 'react' +import { Navigate } from 'react-router-dom' +import { AppLayout } from '@/components/layout/AppLayout' + +interface ProtectedRouteProps { + isAuthenticated: boolean + children: ReactNode +} + +function ProtectedRoute({ isAuthenticated, children }: ProtectedRouteProps) { + if (!isAuthenticated) { + return + } + + return ( + + {children} + + ) +} + +export default ProtectedRoute \ No newline at end of file diff --git a/thingconnect.pulse.client/src/main.tsx b/thingconnect.pulse.client/src/main.tsx index 94bef7b..090d449 100644 --- a/thingconnect.pulse.client/src/main.tsx +++ b/thingconnect.pulse.client/src/main.tsx @@ -1,13 +1,16 @@ import { StrictMode } from 'react' import { Provider } from "@/components/ui/provider" import { createRoot } from 'react-dom/client' +import { BrowserRouter } from 'react-router-dom' import App from './App.tsx' import './theme/global.css' createRoot(document.getElementById('root')!).render( - + + + ) diff --git a/thingconnect.pulse.client/src/pages/DashboardPage.tsx b/thingconnect.pulse.client/src/pages/DashboardPage.tsx new file mode 100644 index 0000000..3021455 --- /dev/null +++ b/thingconnect.pulse.client/src/pages/DashboardPage.tsx @@ -0,0 +1,50 @@ +import { Box, Button, Heading, Text, VStack, HStack } from '@chakra-ui/react' +import { Logo } from '@/components/ui/logo' +import { authService } from '@/services/authService' + +function DashboardPage() { + const handleLogout = () => { + authService.logout() + window.location.href = '/login' + } + + return ( + + + + + + + Welcome to Pulse + + + + You are successfully logged in! This is where your main application content will go. + + + + + + Enterprise Dashboard + + + Your authenticated dashboard content goes here. The theme automatically adapts to light and dark modes using Atlassian Design System principles. + + + + + + + + + ) +} + +export default DashboardPage \ No newline at end of file From a317a56c9cbcecf57648941643d17fd7cf5fd70e Mon Sep 17 00:00:00 2001 From: Hemanand Date: Fri, 8 Aug 2025 08:58:51 +0530 Subject: [PATCH 3/5] Initialize Chakra UI base components and theme customization - Created base UI components (Button, Input, Card) - Extended theme with Atlassian-inspired design system - Added semantic tokens and component variants - Created UI components index file --- .../src/components/ui/Button.tsx | 35 ++++++++++++++++ .../src/components/ui/Card.tsx | 42 +++++++++++++++++++ .../src/components/ui/Input.tsx | 28 +++++++++++++ .../src/components/ui/index.ts | 3 ++ thingconnect.pulse.client/src/theme/index.ts | 40 ++++++++++++++++++ 5 files changed, 148 insertions(+) create mode 100644 thingconnect.pulse.client/src/components/ui/Button.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/Card.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/Input.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/index.ts diff --git a/thingconnect.pulse.client/src/components/ui/Button.tsx b/thingconnect.pulse.client/src/components/ui/Button.tsx new file mode 100644 index 0000000..6f97e94 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/Button.tsx @@ -0,0 +1,35 @@ +import { Button as ChakraButton, ButtonProps as ChakraButtonProps } from "@chakra-ui/react" + +export interface ButtonProps extends ChakraButtonProps { + variant?: 'primary' | 'secondary' | 'danger' +} + +export function Button({ + variant = 'primary', + children, + ...props +}: ButtonProps) { + const variantProps = { + primary: { + colorPalette: 'blue', + variant: 'solid' + }, + secondary: { + colorPalette: 'gray', + variant: 'outline' + }, + danger: { + colorPalette: 'red', + variant: 'solid' + } + }[variant] + + return ( + + {children} + + ) +} \ No newline at end of file diff --git a/thingconnect.pulse.client/src/components/ui/Card.tsx b/thingconnect.pulse.client/src/components/ui/Card.tsx new file mode 100644 index 0000000..00a1a39 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/Card.tsx @@ -0,0 +1,42 @@ +import { Box, BoxProps } from "@chakra-ui/react" +import { ReactNode } from "react" + +export interface CardProps extends BoxProps { + children: ReactNode + variant?: 'elevated' | 'outline' | 'ghost' +} + +export function Card({ + children, + variant = 'elevated', + ...props +}: CardProps) { + const variantStyles = { + elevated: { + boxShadow: 'md', + borderWidth: '1px', + borderColor: 'transparent' + }, + outline: { + boxShadow: 'none', + borderWidth: '1px', + borderColor: 'border.default' + }, + ghost: { + boxShadow: 'none', + borderWidth: '0', + bg: 'transparent' + } + }[variant] + + return ( + + {children} + + ) +} \ No newline at end of file diff --git a/thingconnect.pulse.client/src/components/ui/Input.tsx b/thingconnect.pulse.client/src/components/ui/Input.tsx new file mode 100644 index 0000000..03159a5 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/Input.tsx @@ -0,0 +1,28 @@ +import { + Input as ChakraInput, + InputProps as ChakraInputProps, + FormControl, + FormLabel, + FormErrorMessage +} from "@chakra-ui/react" +import { forwardRef } from "react" + +export interface InputProps extends ChakraInputProps { + label?: string + error?: string +} + +export const Input = forwardRef( + ({ label, error, ...props }, ref) => { + return ( + + {label && {label}} + + {error && {error}} + + ) + } +) \ No newline at end of file diff --git a/thingconnect.pulse.client/src/components/ui/index.ts b/thingconnect.pulse.client/src/components/ui/index.ts new file mode 100644 index 0000000..ad81259 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/index.ts @@ -0,0 +1,3 @@ +export { Button } from './Button' +export { Input } from './Input' +export { Card } from './Card' \ No newline at end of file diff --git a/thingconnect.pulse.client/src/theme/index.ts b/thingconnect.pulse.client/src/theme/index.ts index 620bc9b..adbf42e 100644 --- a/thingconnect.pulse.client/src/theme/index.ts +++ b/thingconnect.pulse.client/src/theme/index.ts @@ -627,4 +627,44 @@ const config = defineConfig({ }, }) +config.theme.components = { + ...config.theme.components, + Button: { + baseStyle: { + fontWeight: 'semibold', + borderRadius: 'base', + }, + variants: { + primary: { + bg: 'brand.500', + color: 'white', + _hover: { bg: 'brand.600' } + }, + secondary: { + bg: 'transparent', + color: 'brand.500', + border: '1px solid', + borderColor: 'brand.500', + _hover: { bg: 'brand.50' } + }, + danger: { + bg: 'danger.500', + color: 'white', + _hover: { bg: 'danger.600' } + } + } + }, + Input: { + baseStyle: { + field: { + borderColor: 'border.default', + _focus: { + borderColor: 'brand.500', + boxShadow: '0 0 0 1px brand.500' + } + } + } + } +} + export const system = createSystem(defaultConfig, config) \ No newline at end of file From f8e6f602dd3c769c59718250dfab8e66396282d0 Mon Sep 17 00:00:00 2001 From: Hemanand Date: Sat, 9 Aug 2025 19:18:48 +0530 Subject: [PATCH 4/5] Update theme system with proper Atlassian color naming and comprehensive theme documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix blue and red color palettes to use correct shade naming (100-900 for blue, 100-1000 for red) - Add comprehensive Chakra UI component library with 50+ components - Create ThemeShowcasePage with compact color matrix display matching design system - Add detailed theme documentation (CLAUDE.md) covering colors, typography, spacing, and usage guidelines - Update color tokens to match Atlassian Design System specifications - Format and lint all code for consistency 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../.claude/settings.local.json | 17 + thingconnect.pulse.client/eslint.config.js | 42 +- thingconnect.pulse.client/package-lock.json | 103 ++- thingconnect.pulse.client/package.json | 5 +- thingconnect.pulse.client/src/App.tsx | 27 +- .../src/assets/ThingConnect Logo.svg | 42 +- .../src/components/auth/AuthScreen.tsx | 64 +- .../src/components/auth/LoginForm.tsx | 179 ++--- .../src/components/auth/RegisterForm.tsx | 244 ++++--- .../src/components/layout/AppLayout.tsx | 16 +- .../src/components/routing/ProtectedRoute.tsx | 8 +- .../src/components/ui/Button.tsx | 23 +- .../src/components/ui/CLAUDE.md | 196 +++++ .../src/components/ui/Card.tsx | 27 +- .../src/components/ui/Input.tsx | 39 +- .../src/components/ui/accordion.tsx | 45 ++ .../src/components/ui/action-bar.tsx | 39 + .../src/components/ui/alert.tsx | 27 + .../src/components/ui/avatar.tsx | 26 + .../src/components/ui/blockquote.tsx | 29 + .../src/components/ui/breadcrumb.tsx | 35 + .../src/components/ui/checkbox-card.tsx | 53 ++ .../src/components/ui/checkbox.tsx | 21 + .../src/components/ui/clipboard.tsx | 99 +++ .../src/components/ui/close-button.tsx | 16 + .../src/components/ui/color-mode.tsx | 129 ++-- .../src/components/ui/color-picker.tsx | 201 ++++++ .../src/components/ui/data-list.tsx | 28 + .../src/components/ui/dialog.tsx | 49 ++ .../src/components/ui/drawer.tsx | 45 ++ .../src/components/ui/empty-state.tsx | 30 + .../src/components/ui/field.tsx | 26 + .../src/components/ui/file-upload.tsx | 153 ++++ .../src/components/ui/hover-card.tsx | 34 + .../src/components/ui/index.ts | 2 +- .../src/components/ui/input-group.tsx | 52 ++ .../src/components/ui/link-button.tsx | 11 + .../src/components/ui/logo.tsx | 18 +- .../src/components/ui/menu.tsx | 103 +++ .../src/components/ui/native-select.tsx | 53 ++ .../src/components/ui/number-input.tsx | 23 + .../src/components/ui/pagination.tsx | 188 +++++ .../src/components/ui/password-input.tsx | 146 ++++ .../src/components/ui/pin-input.tsx | 27 + .../src/components/ui/popover.tsx | 57 ++ .../src/components/ui/progress-circle.tsx | 33 + .../src/components/ui/progress.tsx | 32 + .../src/components/ui/prose.tsx | 275 +++++++ .../src/components/ui/provider.tsx | 11 +- .../src/components/ui/qr-code.tsx | 20 + .../src/components/ui/radio-card.tsx | 53 ++ .../src/components/ui/radio.tsx | 20 + .../src/components/ui/rating.tsx | 25 + .../src/components/ui/segmented-control.tsx | 42 ++ .../src/components/ui/select.tsx | 134 ++++ .../src/components/ui/skeleton.tsx | 37 + .../src/components/ui/slider.tsx | 78 ++ .../src/components/ui/stat.tsx | 61 ++ .../src/components/ui/status.tsx | 27 + .../src/components/ui/stepper-input.tsx | 47 ++ .../src/components/ui/steps.tsx | 77 ++ .../src/components/ui/switch.tsx | 32 + .../src/components/ui/tag.tsx | 26 + .../src/components/ui/theme-toggle.tsx | 113 +-- .../src/components/ui/theme/CLAUDE.md | 335 +++++++++ .../src/{ => components/ui}/theme/README.md | 23 +- .../src/components/ui/theme/colors.ts | 560 +++++++++++++++ .../src/{ => components/ui}/theme/global.css | 9 +- .../src/components/ui/theme/index.ts | 569 +++++++++++++++ .../src/components/ui/timeline.tsx | 20 + .../src/components/ui/toaster.tsx | 26 +- .../src/components/ui/toggle-tip.tsx | 54 ++ .../src/components/ui/toggle.tsx | 44 ++ .../src/components/ui/tooltip.tsx | 67 +- .../src/lib/color-mode.ts | 26 +- thingconnect.pulse.client/src/lib/toaster.ts | 6 +- thingconnect.pulse.client/src/main.tsx | 4 +- .../src/pages/DashboardPage.tsx | 26 +- .../src/pages/ThemeShowcasePage.tsx | 50 ++ .../src/services/authService.ts | 84 +-- thingconnect.pulse.client/src/theme/index.ts | 670 ------------------ thingconnect.pulse.client/vite.config.ts | 5 + 82 files changed, 5271 insertions(+), 1247 deletions(-) create mode 100644 thingconnect.pulse.client/.claude/settings.local.json create mode 100644 thingconnect.pulse.client/src/components/ui/CLAUDE.md create mode 100644 thingconnect.pulse.client/src/components/ui/accordion.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/action-bar.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/alert.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/avatar.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/blockquote.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/breadcrumb.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/checkbox-card.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/checkbox.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/clipboard.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/close-button.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/color-picker.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/data-list.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/dialog.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/drawer.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/empty-state.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/field.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/file-upload.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/hover-card.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/input-group.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/link-button.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/menu.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/native-select.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/number-input.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/pagination.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/password-input.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/pin-input.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/popover.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/progress-circle.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/progress.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/prose.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/qr-code.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/radio-card.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/radio.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/rating.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/segmented-control.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/select.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/skeleton.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/slider.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/stat.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/status.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/stepper-input.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/steps.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/switch.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/tag.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/theme/CLAUDE.md rename thingconnect.pulse.client/src/{ => components/ui}/theme/README.md (97%) create mode 100644 thingconnect.pulse.client/src/components/ui/theme/colors.ts rename thingconnect.pulse.client/src/{ => components/ui}/theme/global.css (86%) create mode 100644 thingconnect.pulse.client/src/components/ui/theme/index.ts create mode 100644 thingconnect.pulse.client/src/components/ui/timeline.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/toggle-tip.tsx create mode 100644 thingconnect.pulse.client/src/components/ui/toggle.tsx create mode 100644 thingconnect.pulse.client/src/pages/ThemeShowcasePage.tsx delete mode 100644 thingconnect.pulse.client/src/theme/index.ts diff --git a/thingconnect.pulse.client/.claude/settings.local.json b/thingconnect.pulse.client/.claude/settings.local.json new file mode 100644 index 0000000..da2a3a5 --- /dev/null +++ b/thingconnect.pulse.client/.claude/settings.local.json @@ -0,0 +1,17 @@ +{ + "permissions": { + "allow": [ + "WebFetch(domain:atlassian.design)", + "Bash(npm run build:*)", + "mcp__chakra-ui__get_theme", + "mcp__chakra-ui__v2_to_v3_code_review", + "mcp__chakra-ui__get_component_example", + "mcp__chakra-ui__get_component_props", + "Bash(npm run dev:*)", + "Bash(npm run lint)", + "Bash(npm run format:*)", + "Bash(git add:*)" + ], + "deny": [] + } +} \ No newline at end of file diff --git a/thingconnect.pulse.client/eslint.config.js b/thingconnect.pulse.client/eslint.config.js index 51afcb7..7b2a0ce 100644 --- a/thingconnect.pulse.client/eslint.config.js +++ b/thingconnect.pulse.client/eslint.config.js @@ -9,26 +9,26 @@ import prettier from 'eslint-config-prettier' import { globalIgnores } from 'eslint/config' export default tseslint.config([ - globalIgnores(['dist']), - { - files: ['**/*.{ts,tsx}'], - extends: [ - js.configs.recommended, - ...tseslint.configs.recommendedTypeChecked, - // ...tseslint.configs.strictTypeChecked, // optional strict rules - reactX.configs['recommended-typescript'], - reactDom.configs.recommended, - reactRefresh.configs.vite, - prettier, - ...pluginQuery.configs['flat/recommended'], - ], - languageOptions: { - ecmaVersion: 2020, - globals: globals.browser, - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - }, + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + // ...tseslint.configs.strictTypeChecked, // optional strict rules + reactX.configs['recommended-typescript'], + reactDom.configs.recommended, + reactRefresh.configs.vite, + prettier, + ...pluginQuery.configs['flat/recommended'], + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, }, + }, ]) diff --git a/thingconnect.pulse.client/package-lock.json b/thingconnect.pulse.client/package-lock.json index 437115a..a205997 100644 --- a/thingconnect.pulse.client/package-lock.json +++ b/thingconnect.pulse.client/package-lock.json @@ -12,15 +12,18 @@ "@emotion/react": "^11.14.0", "@tanstack/react-query": "^5.84.1", "axios": "^1.11.0", + "lodash": "^4.17.21", "next-themes": "^0.4.6", "react": "^19.1.0", "react-dom": "^19.1.0", - "react-icons": "^5.5.0" + "react-icons": "^5.5.0", + "react-router-dom": "^7.8.0" }, "devDependencies": { "@chakra-ui/cli": "^3.24.0", "@eslint/js": "^9.30.1", "@tanstack/eslint-plugin-query": "^5.83.1", + "@types/lodash": "^4.17.20", "@types/node": "^20", "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", @@ -1913,6 +1916,12 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "node_modules/@types/lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==", + "dev": true + }, "node_modules/@types/ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", @@ -3400,6 +3409,14 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "engines": { + "node": ">=18" + } + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -4678,6 +4695,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -5301,6 +5323,42 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-router": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.8.0.tgz", + "integrity": "sha512-r15M3+LHKgM4SOapNmsH3smAizWds1vJ0Z9C4mWaKnT9/wD7+d/0jYcj6LmOvonkrO4Rgdyp4KQ/29gWN2i1eg==", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.8.0.tgz", + "integrity": "sha512-ntInsnDVnVRdtSu6ODmTQ41cbluak/ENeTif7GBce0L6eztFg6/e1hXAysFQI8X25C8ipKmT9cClbJwxx3Kaqw==", + "dependencies": { + "react-router": "7.8.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -5445,6 +5503,11 @@ "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", "dev": true }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -7451,6 +7514,12 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "@types/lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==", + "dev": true + }, "@types/ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", @@ -8700,6 +8769,11 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, + "cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==" + }, "cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -9591,6 +9665,11 @@ "p-locate": "^5.0.0" } }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -9997,6 +10076,23 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "react-router": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.8.0.tgz", + "integrity": "sha512-r15M3+LHKgM4SOapNmsH3smAizWds1vJ0Z9C4mWaKnT9/wD7+d/0jYcj6LmOvonkrO4Rgdyp4KQ/29gWN2i1eg==", + "requires": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + } + }, + "react-router-dom": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.8.0.tgz", + "integrity": "sha512-ntInsnDVnVRdtSu6ODmTQ41cbluak/ENeTif7GBce0L6eztFg6/e1hXAysFQI8X25C8ipKmT9cClbJwxx3Kaqw==", + "requires": { + "react-router": "7.8.0" + } + }, "readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -10093,6 +10189,11 @@ "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", "dev": true }, + "set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==" + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", diff --git a/thingconnect.pulse.client/package.json b/thingconnect.pulse.client/package.json index 5124d8d..48401aa 100644 --- a/thingconnect.pulse.client/package.json +++ b/thingconnect.pulse.client/package.json @@ -16,15 +16,18 @@ "@emotion/react": "^11.14.0", "@tanstack/react-query": "^5.84.1", "axios": "^1.11.0", + "lodash": "^4.17.21", "next-themes": "^0.4.6", "react": "^19.1.0", "react-dom": "^19.1.0", - "react-icons": "^5.5.0" + "react-icons": "^5.5.0", + "react-router-dom": "^7.8.0" }, "devDependencies": { "@chakra-ui/cli": "^3.24.0", "@eslint/js": "^9.30.1", "@tanstack/eslint-plugin-query": "^5.83.1", + "@types/lodash": "^4.17.20", "@types/node": "^20", "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", diff --git a/thingconnect.pulse.client/src/App.tsx b/thingconnect.pulse.client/src/App.tsx index 9b5ef64..68418db 100644 --- a/thingconnect.pulse.client/src/App.tsx +++ b/thingconnect.pulse.client/src/App.tsx @@ -5,6 +5,7 @@ import { AppLayout } from './components/layout/AppLayout' import { authService } from './services/authService' import { Box, Text } from '@chakra-ui/react' import DashboardPage from './pages/DashboardPage' +import ThemeShowcasePage from './pages/ThemeShowcasePage' import ProtectedRoute from './components/routing/ProtectedRoute' function App() { @@ -43,8 +44,8 @@ function App() { return ( - @@ -53,21 +54,29 @@ function App() { ) : ( ) - } + } /> - - } + } /> - + + + } + /> + : - } + } /> ) diff --git a/thingconnect.pulse.client/src/assets/ThingConnect Logo.svg b/thingconnect.pulse.client/src/assets/ThingConnect Logo.svg index c825f31..c1393c9 100644 --- a/thingconnect.pulse.client/src/assets/ThingConnect Logo.svg +++ b/thingconnect.pulse.client/src/assets/ThingConnect Logo.svg @@ -1,12 +1,44 @@ - ThingConnect + + + + + + + + + + + + + + + + + - - + + - - + + \ No newline at end of file diff --git a/thingconnect.pulse.client/src/components/auth/AuthScreen.tsx b/thingconnect.pulse.client/src/components/auth/AuthScreen.tsx index f8dd103..8c45088 100644 --- a/thingconnect.pulse.client/src/components/auth/AuthScreen.tsx +++ b/thingconnect.pulse.client/src/components/auth/AuthScreen.tsx @@ -1,48 +1,44 @@ -import { useState } from 'react'; -import { LoginForm } from './LoginForm'; -import { RegisterForm } from './RegisterForm'; -import { authService } from '../../services/authService'; +import { useState } from 'react' +import { LoginForm } from './LoginForm' +import { RegisterForm } from './RegisterForm' +import { authService } from '../../services/authService' interface AuthScreenProps { - onAuthSuccess: () => void; + onAuthSuccess: () => void } export const AuthScreen = ({ onAuthSuccess }: AuthScreenProps) => { - const [isLogin, setIsLogin] = useState(true); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); + const [isLogin, setIsLogin] = useState(true) + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(null) const handleLogin = async (username: string, password: string) => { - setIsLoading(true); - setError(null); - + setIsLoading(true) + setError(null) + try { - await authService.login(username, password); - onAuthSuccess(); + await authService.login(username, password) + onAuthSuccess() } catch (err: unknown) { - setError( - (err as { response?: { data?: string } })?.response?.data || 'Login failed' - ); + setError((err as { response?: { data?: string } })?.response?.data || 'Login failed') } finally { - setIsLoading(false); + setIsLoading(false) } - }; + } const handleRegister = async (username: string, email: string, password: string) => { - setIsLoading(true); - setError(null); - + setIsLoading(true) + setError(null) + try { - await authService.register(username, email, password); - onAuthSuccess(); + await authService.register(username, email, password) + onAuthSuccess() } catch (err: unknown) { - setError( - (err as { response?: { data?: string } })?.response?.data || 'Registration failed' - ); + setError((err as { response?: { data?: string } })?.response?.data || 'Registration failed') } finally { - setIsLoading(false); + setIsLoading(false) } - }; + } return ( <> @@ -50,8 +46,8 @@ export const AuthScreen = ({ onAuthSuccess }: AuthScreenProps) => { { - setIsLogin(false); - setError(null); + setIsLogin(false) + setError(null) }} isLoading={isLoading} error={error} @@ -60,13 +56,13 @@ export const AuthScreen = ({ onAuthSuccess }: AuthScreenProps) => { { - setIsLogin(true); - setError(null); + setIsLogin(true) + setError(null) }} isLoading={isLoading} error={error} /> )} - ); -}; \ No newline at end of file + ) +} diff --git a/thingconnect.pulse.client/src/components/auth/LoginForm.tsx b/thingconnect.pulse.client/src/components/auth/LoginForm.tsx index 8b7b45e..9ff73a6 100644 --- a/thingconnect.pulse.client/src/components/auth/LoginForm.tsx +++ b/thingconnect.pulse.client/src/components/auth/LoginForm.tsx @@ -1,107 +1,120 @@ -import { useState } from 'react'; -import { - Box, - Button, - Input, - Stack, - Text, - VStack, -} from '@chakra-ui/react'; -import { Logo } from '../ui/logo'; +import { useState } from 'react' +import { Box, Button, Input, Stack, Text, VStack } from '@chakra-ui/react' +import { Logo } from '../ui/logo' interface LoginFormProps { - onLogin: (username: string, password: string) => Promise; - onSwitchToRegister: () => void; - isLoading: boolean; - error: string | null; + onLogin: (username: string, password: string) => Promise + onSwitchToRegister: () => void + isLoading: boolean + error: string | null } export const LoginForm = ({ onLogin, onSwitchToRegister, isLoading, error }: LoginFormProps) => { - const [username, setUsername] = useState(''); - const [password, setPassword] = useState(''); + const [username, setUsername] = useState('') + const [password, setPassword] = useState('') const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - void onLogin(username, password); - }; + e.preventDefault() + void onLogin(username, password) + } return ( - Login to Pulse + + Login to Pulse + - + - - {error && ( - - {error} + {error && ( + + {error} + + )} + + + + Username * + + setUsername(e.target.value)} + placeholder="Enter your username" + required + bg="color.background.default" + borderColor="border" + borderRadius="base" + p="100" + _focus={{ + borderColor: 'brand.solid', + boxShadow: '0 0 0 2px var(--chakra-colors-brand-200)', + }} + /> - )} - - Username * - setUsername(e.target.value)} - placeholder="Enter your username" - required - bg="color.background.default" - borderColor="border" - borderRadius="base" - p="100" - _focus={{ borderColor: "brand.solid", boxShadow: "0 0 0 2px var(--chakra-colors-brand-200)" }} - /> - + + + Password * + + setPassword(e.target.value)} + placeholder="Enter your password" + required + bg="color.background.default" + borderColor="border" + borderRadius="base" + p="100" + _focus={{ + borderColor: 'brand.solid', + boxShadow: '0 0 0 2px var(--chakra-colors-brand-200)', + }} + /> + - - Password * - setPassword(e.target.value)} - placeholder="Enter your password" - required - bg="color.background.default" - borderColor="border" + - - - Don't have an account?{' '} - - + + + Don't have an account?{' '} + + - ); -}; \ No newline at end of file + ) +} diff --git a/thingconnect.pulse.client/src/components/auth/RegisterForm.tsx b/thingconnect.pulse.client/src/components/auth/RegisterForm.tsx index 73c3efa..c854493 100644 --- a/thingconnect.pulse.client/src/components/auth/RegisterForm.tsx +++ b/thingconnect.pulse.client/src/components/auth/RegisterForm.tsx @@ -1,136 +1,162 @@ -import { useState } from 'react'; -import { - Box, - Button, - Input, - Stack, - Text, - VStack, -} from '@chakra-ui/react'; -import { Logo } from '../ui/logo'; +import { useState } from 'react' +import { Box, Button, Input, Stack, Text, VStack } from '@chakra-ui/react' +import { Logo } from '../ui/logo' interface RegisterFormProps { - onRegister: (username: string, email: string, password: string) => Promise; - onSwitchToLogin: () => void; - isLoading: boolean; - error: string | null; + onRegister: (username: string, email: string, password: string) => Promise + onSwitchToLogin: () => void + isLoading: boolean + error: string | null } -export const RegisterForm = ({ onRegister, onSwitchToLogin, isLoading, error }: RegisterFormProps) => { - const [username, setUsername] = useState(''); - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - const [confirmPassword, setConfirmPassword] = useState(''); +export const RegisterForm = ({ + onRegister, + onSwitchToLogin, + isLoading, + error, +}: RegisterFormProps) => { + const [username, setUsername] = useState('') + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + const [confirmPassword, setConfirmPassword] = useState('') const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - + e.preventDefault() + if (password !== confirmPassword) { - return; + return } - - void onRegister(username, email, password); - }; - const passwordMismatch = password !== confirmPassword && confirmPassword !== ''; + void onRegister(username, email, password) + } + + const passwordMismatch = password !== confirmPassword && confirmPassword !== '' return ( - Join Pulse + + Join Pulse + - - - - {error && ( - - {error} - - )} - - Username * - setUsername(e.target.value)} - placeholder="Choose a username" - required - bg="bg.panel" - borderColor="border" - _focus={{ borderColor: "brand.solid", boxShadow: "0 0 0 1px var(--chakra-colors-brand-solid)" }} - /> - + + {error && ( + + {error} + + )} - - Email * - setEmail(e.target.value)} - placeholder="Enter your email" - required - bg="bg.panel" - borderColor="border" - _focus={{ borderColor: "brand.solid", boxShadow: "0 0 0 1px var(--chakra-colors-brand-solid)" }} - /> - + + + Username * + + setUsername(e.target.value)} + placeholder="Choose a username" + required + bg="bg.panel" + borderColor="border" + _focus={{ + borderColor: 'brand.solid', + boxShadow: '0 0 0 1px var(--chakra-colors-brand-solid)', + }} + /> + - - Password * - setPassword(e.target.value)} - placeholder="Create a password" - required - bg="bg.panel" - borderColor="border" - _focus={{ borderColor: "brand.solid", boxShadow: "0 0 0 1px var(--chakra-colors-brand-solid)" }} - /> - + + + Email * + + setEmail(e.target.value)} + placeholder="Enter your email" + required + bg="bg.panel" + borderColor="border" + _focus={{ + borderColor: 'brand.solid', + boxShadow: '0 0 0 1px var(--chakra-colors-brand-solid)', + }} + /> + - - Confirm Password * - setConfirmPassword(e.target.value)} - placeholder="Confirm your password" - required - bg="bg.panel" - borderColor="border" - _focus={{ borderColor: "brand.solid", boxShadow: "0 0 0 1px var(--chakra-colors-brand-solid)" }} - /> - {passwordMismatch && ( - - Passwords do not match + + + Password * - )} - + setPassword(e.target.value)} + placeholder="Create a password" + required + bg="bg.panel" + borderColor="border" + _focus={{ + borderColor: 'brand.solid', + boxShadow: '0 0 0 1px var(--chakra-colors-brand-solid)', + }} + /> + - + + + Confirm Password * + + setConfirmPassword(e.target.value)} + placeholder="Confirm your password" + required + bg="bg.panel" + borderColor="border" + _focus={{ + borderColor: 'brand.solid', + boxShadow: '0 0 0 1px var(--chakra-colors-brand-solid)', + }} + /> + {passwordMismatch && ( + + Passwords do not match + + )} + - - Already have an account?{' '} - - + + + Already have an account?{' '} + + - ); -}; \ No newline at end of file + ) +} diff --git a/thingconnect.pulse.client/src/components/layout/AppLayout.tsx b/thingconnect.pulse.client/src/components/layout/AppLayout.tsx index ac68d49..1f104db 100644 --- a/thingconnect.pulse.client/src/components/layout/AppLayout.tsx +++ b/thingconnect.pulse.client/src/components/layout/AppLayout.tsx @@ -1,7 +1,7 @@ -import { Box, Flex, HStack } from "@chakra-ui/react" -import { ColorModeButton } from "../ui/color-mode" -import { Logo } from "../ui/logo" -import type { ReactNode } from "react" +import { Box, Flex, HStack } from '@chakra-ui/react' +import { ColorModeButton } from '../ui/color-mode' +import { Logo } from '../ui/logo' +import type { ReactNode } from 'react' interface AppLayoutProps { children: ReactNode @@ -16,10 +16,10 @@ export function AppLayout({ children, showHeader = true }: AppLayoutProps) { {/* Show full logo on larger screens, icon on mobile */} - + - + @@ -32,10 +32,10 @@ export function AppLayout({ children, showHeader = true }: AppLayoutProps) { )} - + {children} ) -} \ No newline at end of file +} diff --git a/thingconnect.pulse.client/src/components/routing/ProtectedRoute.tsx b/thingconnect.pulse.client/src/components/routing/ProtectedRoute.tsx index 87396bd..7fc8c37 100644 --- a/thingconnect.pulse.client/src/components/routing/ProtectedRoute.tsx +++ b/thingconnect.pulse.client/src/components/routing/ProtectedRoute.tsx @@ -12,11 +12,7 @@ function ProtectedRoute({ isAuthenticated, children }: ProtectedRouteProps) { return } - return ( - - {children} - - ) + return {children} } -export default ProtectedRoute \ No newline at end of file +export default ProtectedRoute diff --git a/thingconnect.pulse.client/src/components/ui/Button.tsx b/thingconnect.pulse.client/src/components/ui/Button.tsx index 6f97e94..4d055fb 100644 --- a/thingconnect.pulse.client/src/components/ui/Button.tsx +++ b/thingconnect.pulse.client/src/components/ui/Button.tsx @@ -1,35 +1,28 @@ -import { Button as ChakraButton, ButtonProps as ChakraButtonProps } from "@chakra-ui/react" +import { Button as ChakraButton, ButtonProps as ChakraButtonProps } from '@chakra-ui/react' export interface ButtonProps extends ChakraButtonProps { variant?: 'primary' | 'secondary' | 'danger' } -export function Button({ - variant = 'primary', - children, - ...props -}: ButtonProps) { +export function Button({ variant = 'primary', children, ...props }: ButtonProps) { const variantProps = { primary: { colorPalette: 'blue', - variant: 'solid' + variant: 'solid', }, secondary: { colorPalette: 'gray', - variant: 'outline' + variant: 'outline', }, danger: { colorPalette: 'red', - variant: 'solid' - } + variant: 'solid', + }, }[variant] return ( - + {children} ) -} \ No newline at end of file +} diff --git a/thingconnect.pulse.client/src/components/ui/CLAUDE.md b/thingconnect.pulse.client/src/components/ui/CLAUDE.md new file mode 100644 index 0000000..a19697a --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/CLAUDE.md @@ -0,0 +1,196 @@ +# Chakra UI Theme & Customization Guide + +This document provides comprehensive guidance for working with Chakra UI theming and customization in the ThingConnect.Pulse project. + +## Theme Structure + +### Tokens (Core Theme Tokens) + +Non-semantic tokens that define the foundational design values: + +- Colors: `gray.50`, `red.500`, `blue.100`, etc. +- Spacing, fonts, shadows, radii, and other design primitives + +### Semantic Tokens + +Context-aware tokens that automatically work for light and dark mode. **Always prefer semantic tokens over hard-coded color values**. + +Available semantic tokens: + +- **Background**: `bg`, `bg.subtle`, `bg.muted`, `bg.emphasized`, `bg.inverted`, `bg.panel` +- **Foreground**: `fg`, `fg.muted`, `fg.subtle`, `fg.inverted` +- **Borders**: `border`, `border.muted`, `border.subtle`, `border.emphasized` +- **Status Colors**: `*.error`, `*.warning`, `*.success`, `*.info` (where \* = bg, fg, border) +- **Color Palettes**: Each color has semantic variants: `contrast`, `fg`, `subtle`, `muted`, `emphasized`, `solid`, `focusRing` + +### Text Styles + +Consistent text styling that combines font size, font weight, and line height: + +- Sizes: `2xs`, `xs`, `sm`, `md`, `lg`, `xl`, `2xl`, `3xl`, `4xl`, `5xl`, `6xl`, `7xl` +- Special: `label` for form labels + +### Layer Styles + +Consistent container styling for common UI patterns: + +- **Fill styles**: `fill.muted`, `fill.subtle`, `fill.surface`, `fill.solid` +- **Outline styles**: `outline.subtle`, `outline.solid` +- **Indicators**: `indicator.bottom`, `indicator.top`, `indicator.start`, `indicator.end` +- **States**: `disabled`, `none` + +### Animation Styles + +Shorthand for animation patterns (YOU MUST specify duration and ease when using): + +- `slide-fade-in`, `slide-fade-out` +- `scale-fade-in`, `scale-fade-out` + +## Theme Customization + +### Adding Custom Colors + +```typescript +import { createSystem, defaultConfig, defineConfig } from '@chakra-ui/react' + +const config = defineConfig({ + theme: { + tokens: { + colors: { + brand: { + 50: { value: '#e6f2ff' }, + 100: { value: '#bfdeff' }, + 200: { value: '#99caff' }, + 500: { value: '#0080ff' }, + 950: { value: '#001a33' }, + }, + }, + }, + semanticTokens: { + colors: { + brand: { + solid: { value: '{colors.brand.500}' }, + contrast: { value: '{colors.brand.100}' }, + fg: { + value: { + _light: '{colors.brand.700}', + _dark: '{colors.brand.600}', + }, + }, + muted: { value: '{colors.brand.100}' }, + subtle: { value: '{colors.brand.200}' }, + emphasized: { value: '{colors.brand.300}' }, + focusRing: { value: '{colors.brand.500}' }, + }, + }, + }, + }, +}) + +export const system = createSystem(defaultConfig, config) +``` + +**Important**: For new colors, YOU MUST create matching semantic tokens: `solid`, `contrast`, `fg`, `muted`, `subtle`, `emphasized`, `focusRing`. + +### Adding Custom Text Styles + +```typescript +const config = defineConfig({ + theme: { + textStyles: { + heading: { + value: { + fontSize: '24px', + fontWeight: 'bold', + lineHeight: '1.2', + }, + }, + body: { + value: { + fontSize: '16px', + fontWeight: 'normal', + lineHeight: '1.5', + }, + }, + }, + }, +}) +``` + +### Adding Custom Layer Styles + +```typescript +const config = defineConfig({ + theme: { + layerStyles: { + card: { + value: { + background: 'bg.panel', + borderRadius: 'md', + padding: '4', + boxShadow: 'sm', + }, + }, + panel: { + value: { + background: 'bg.subtle', + border: '1px solid', + borderColor: 'border.muted', + borderRadius: 'lg', + padding: '6', + }, + }, + }, + }, +}) +``` + +## TypeScript Support + +To get proper autocompletion and type safety, YOU MUST run the typegen command after making theme changes: + +```bash +npx @chakra-ui/cli typegen src/components/ui/theme/index.ts +``` + +This generates TypeScript definitions for your custom theme tokens. + +## Best Practices + +1. **Use Semantic Tokens**: Always prefer semantic tokens over raw color values + - ✅ `color="fg.muted"` + - ❌ `color="gray.600"` + +2. **Dark Mode Support**: Semantic tokens automatically handle light/dark mode + - Define light/dark variants using `_light` and `_dark` properties + +3. **Consistent Spacing**: Use the predefined spacing tokens + - `padding="4"`, `margin="2"`, `gap="6"` + +4. **Text Consistency**: Use textStyles for consistent typography + - `textStyle="lg"` instead of manual fontSize/lineHeight + +5. **Layer Styles for Containers**: Use layerStyles for consistent container patterns + - `layerStyle="card"` for card-like containers + +6. **Animation Consistency**: Always specify duration and ease with animationStyles + ```typescript + + ``` + +## Current Theme Structure + +The project uses an Atlassian-inspired design system with: + +- **Brand colors**: ThingConnect blue palette +- **Accent colors**: Teal secondary palette +- **Status colors**: Success (green), warning (yellow), danger (red) +- **Neutral grays**: Clean, accessible gray scale +- **Atlassian typography**: Clean, readable font hierarchy +- **Enterprise layer styles**: Card, panel, header, modal, button variants + +All theme files are located in `src/components/ui/theme/`. diff --git a/thingconnect.pulse.client/src/components/ui/Card.tsx b/thingconnect.pulse.client/src/components/ui/Card.tsx index 00a1a39..0065138 100644 --- a/thingconnect.pulse.client/src/components/ui/Card.tsx +++ b/thingconnect.pulse.client/src/components/ui/Card.tsx @@ -1,42 +1,33 @@ -import { Box, BoxProps } from "@chakra-ui/react" -import { ReactNode } from "react" +import { Box, BoxProps } from '@chakra-ui/react' +import { ReactNode } from 'react' export interface CardProps extends BoxProps { children: ReactNode variant?: 'elevated' | 'outline' | 'ghost' } -export function Card({ - children, - variant = 'elevated', - ...props -}: CardProps) { +export function Card({ children, variant = 'elevated', ...props }: CardProps) { const variantStyles = { elevated: { boxShadow: 'md', borderWidth: '1px', - borderColor: 'transparent' + borderColor: 'transparent', }, outline: { boxShadow: 'none', borderWidth: '1px', - borderColor: 'border.default' + borderColor: 'border.default', }, ghost: { boxShadow: 'none', borderWidth: '0', - bg: 'transparent' - } + bg: 'transparent', + }, }[variant] return ( - + {children} ) -} \ No newline at end of file +} diff --git a/thingconnect.pulse.client/src/components/ui/Input.tsx b/thingconnect.pulse.client/src/components/ui/Input.tsx index 03159a5..c5ff9ed 100644 --- a/thingconnect.pulse.client/src/components/ui/Input.tsx +++ b/thingconnect.pulse.client/src/components/ui/Input.tsx @@ -1,28 +1,23 @@ -import { - Input as ChakraInput, - InputProps as ChakraInputProps, - FormControl, - FormLabel, - FormErrorMessage -} from "@chakra-ui/react" -import { forwardRef } from "react" +import { + Input as ChakraInput, + InputProps as ChakraInputProps, + FormControl, + FormLabel, + FormErrorMessage, +} from '@chakra-ui/react' +import { forwardRef } from 'react' export interface InputProps extends ChakraInputProps { label?: string error?: string } -export const Input = forwardRef( - ({ label, error, ...props }, ref) => { - return ( - - {label && {label}} - - {error && {error}} - - ) - } -) \ No newline at end of file +export const Input = forwardRef(({ label, error, ...props }, ref) => { + return ( + + {label && {label}} + + {error && {error}} + + ) +}) diff --git a/thingconnect.pulse.client/src/components/ui/accordion.tsx b/thingconnect.pulse.client/src/components/ui/accordion.tsx new file mode 100644 index 0000000..7b6cf6f --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/accordion.tsx @@ -0,0 +1,45 @@ +import { Accordion, HStack } from '@chakra-ui/react' +import * as React from 'react' +import { LuChevronDown } from 'react-icons/lu' + +interface AccordionItemTriggerProps extends Accordion.ItemTriggerProps { + indicatorPlacement?: 'start' | 'end' +} + +export const AccordionItemTrigger = React.forwardRef( + function AccordionItemTrigger(props, ref) { + const { children, indicatorPlacement = 'end', ...rest } = props + return ( + + {indicatorPlacement === 'start' && ( + + + + )} + + {children} + + {indicatorPlacement === 'end' && ( + + + + )} + + ) + } +) + +interface AccordionItemContentProps extends Accordion.ItemContentProps {} + +export const AccordionItemContent = React.forwardRef( + function AccordionItemContent(props, ref) { + return ( + + + + ) + } +) + +export const AccordionRoot = Accordion.Root +export const AccordionItem = Accordion.Item diff --git a/thingconnect.pulse.client/src/components/ui/action-bar.tsx b/thingconnect.pulse.client/src/components/ui/action-bar.tsx new file mode 100644 index 0000000..e80f94a --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/action-bar.tsx @@ -0,0 +1,39 @@ +import { ActionBar, Portal } from '@chakra-ui/react' +import { CloseButton } from './close-button' +import * as React from 'react' + +interface ActionBarContentProps extends ActionBar.ContentProps { + portalled?: boolean + portalRef?: React.RefObject +} + +export const ActionBarContent = React.forwardRef( + function ActionBarContent(props, ref) { + const { children, portalled = true, portalRef, ...rest } = props + + return ( + + + + {children} + + + + ) + } +) + +export const ActionBarCloseTrigger = React.forwardRef< + HTMLButtonElement, + ActionBar.CloseTriggerProps +>(function ActionBarCloseTrigger(props, ref) { + return ( + + + + ) +}) + +export const ActionBarRoot = ActionBar.Root +export const ActionBarSelectionTrigger = ActionBar.SelectionTrigger +export const ActionBarSeparator = ActionBar.Separator diff --git a/thingconnect.pulse.client/src/components/ui/alert.tsx b/thingconnect.pulse.client/src/components/ui/alert.tsx new file mode 100644 index 0000000..9b6c149 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/alert.tsx @@ -0,0 +1,27 @@ +import { Alert as ChakraAlert } from '@chakra-ui/react' +import * as React from 'react' + +export interface AlertProps extends Omit { + startElement?: React.ReactNode + endElement?: React.ReactNode + title?: React.ReactNode + icon?: React.ReactElement +} + +export const Alert = React.forwardRef(function Alert(props, ref) { + const { title, children, icon, startElement, endElement, ...rest } = props + return ( + + {startElement || {icon}} + {children ? ( + + {title} + {children} + + ) : ( + {title} + )} + {endElement} + + ) +}) diff --git a/thingconnect.pulse.client/src/components/ui/avatar.tsx b/thingconnect.pulse.client/src/components/ui/avatar.tsx new file mode 100644 index 0000000..4291033 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/avatar.tsx @@ -0,0 +1,26 @@ +import { Avatar as ChakraAvatar, AvatarGroup as ChakraAvatarGroup } from '@chakra-ui/react' +import * as React from 'react' + +type ImageProps = React.ImgHTMLAttributes + +export interface AvatarProps extends ChakraAvatar.RootProps { + name?: string + src?: string + srcSet?: string + loading?: ImageProps['loading'] + icon?: React.ReactElement + fallback?: React.ReactNode +} + +export const Avatar = React.forwardRef(function Avatar(props, ref) { + const { name, src, srcSet, loading, icon, fallback, children, ...rest } = props + return ( + + {icon || fallback} + + {children} + + ) +}) + +export const AvatarGroup = ChakraAvatarGroup diff --git a/thingconnect.pulse.client/src/components/ui/blockquote.tsx b/thingconnect.pulse.client/src/components/ui/blockquote.tsx new file mode 100644 index 0000000..28e9059 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/blockquote.tsx @@ -0,0 +1,29 @@ +import { Blockquote as ChakraBlockquote } from '@chakra-ui/react' +import * as React from 'react' + +export interface BlockquoteProps extends ChakraBlockquote.RootProps { + cite?: React.ReactNode + citeUrl?: string + icon?: React.ReactNode + showDash?: boolean +} + +export const Blockquote = React.forwardRef( + function Blockquote(props, ref) { + const { children, cite, citeUrl, showDash, icon, ...rest } = props + + return ( + + {icon} + {children} + {cite && ( + + {showDash ? <>— : null} {cite} + + )} + + ) + } +) + +export const BlockquoteIcon = ChakraBlockquote.Icon diff --git a/thingconnect.pulse.client/src/components/ui/breadcrumb.tsx b/thingconnect.pulse.client/src/components/ui/breadcrumb.tsx new file mode 100644 index 0000000..675a1eb --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/breadcrumb.tsx @@ -0,0 +1,35 @@ +import { Breadcrumb, type SystemStyleObject } from '@chakra-ui/react' +import * as React from 'react' + +export interface BreadcrumbRootProps extends Breadcrumb.RootProps { + separator?: React.ReactNode + separatorGap?: SystemStyleObject['gap'] +} + +export const BreadcrumbRoot = React.forwardRef( + function BreadcrumbRoot(props, ref) { + const { separator, separatorGap, children, ...rest } = props + + const validChildren = React.Children.toArray(children).filter(React.isValidElement) + + return ( + + + {validChildren.map((child, index) => { + const last = index === validChildren.length - 1 + return ( + + {child} + {!last && {separator}} + + ) + })} + + + ) + } +) + +export const BreadcrumbLink = Breadcrumb.Link +export const BreadcrumbCurrentLink = Breadcrumb.CurrentLink +export const BreadcrumbEllipsis = Breadcrumb.Ellipsis diff --git a/thingconnect.pulse.client/src/components/ui/checkbox-card.tsx b/thingconnect.pulse.client/src/components/ui/checkbox-card.tsx new file mode 100644 index 0000000..033d5b1 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/checkbox-card.tsx @@ -0,0 +1,53 @@ +import { CheckboxCard as ChakraCheckboxCard } from '@chakra-ui/react' +import * as React from 'react' + +export interface CheckboxCardProps extends ChakraCheckboxCard.RootProps { + icon?: React.ReactElement + label?: React.ReactNode + description?: React.ReactNode + addon?: React.ReactNode + indicator?: React.ReactNode | null + indicatorPlacement?: 'start' | 'end' | 'inside' + inputProps?: React.InputHTMLAttributes +} + +export const CheckboxCard = React.forwardRef( + function CheckboxCard(props, ref) { + const { + inputProps, + label, + description, + icon, + addon, + indicator = , + indicatorPlacement = 'end', + ...rest + } = props + + const hasContent = label || description || icon + const ContentWrapper = indicator ? ChakraCheckboxCard.Content : React.Fragment + + return ( + + + + {indicatorPlacement === 'start' && indicator} + {hasContent && ( + + {icon} + {label && {label}} + {description && ( + {description} + )} + {indicatorPlacement === 'inside' && indicator} + + )} + {indicatorPlacement === 'end' && indicator} + + {addon && {addon}} + + ) + } +) + +export const CheckboxCardIndicator = ChakraCheckboxCard.Indicator diff --git a/thingconnect.pulse.client/src/components/ui/checkbox.tsx b/thingconnect.pulse.client/src/components/ui/checkbox.tsx new file mode 100644 index 0000000..640daf2 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/checkbox.tsx @@ -0,0 +1,21 @@ +import { Checkbox as ChakraCheckbox } from '@chakra-ui/react' +import * as React from 'react' + +export interface CheckboxProps extends ChakraCheckbox.RootProps { + icon?: React.ReactNode + inputProps?: React.InputHTMLAttributes + rootRef?: React.RefObject +} + +export const Checkbox = React.forwardRef( + function Checkbox(props, ref) { + const { icon, children, inputProps, rootRef, ...rest } = props + return ( + + + {icon || } + {children != null && {children}} + + ) + } +) diff --git a/thingconnect.pulse.client/src/components/ui/clipboard.tsx b/thingconnect.pulse.client/src/components/ui/clipboard.tsx new file mode 100644 index 0000000..db26b80 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/clipboard.tsx @@ -0,0 +1,99 @@ +import type { ButtonProps, InputProps } from '@chakra-ui/react' +import { Button, Clipboard as ChakraClipboard, IconButton, Input } from '@chakra-ui/react' +import * as React from 'react' +import { LuCheck, LuClipboard, LuLink } from 'react-icons/lu' + +const ClipboardIcon = React.forwardRef( + function ClipboardIcon(props, ref) { + return ( + } {...props} ref={ref}> + + + ) + } +) + +const ClipboardCopyText = React.forwardRef( + function ClipboardCopyText(props, ref) { + return ( + + Copy + + ) + } +) + +export const ClipboardLabel = React.forwardRef( + function ClipboardLabel(props, ref) { + return ( + + ) + } +) + +export const ClipboardButton = React.forwardRef( + function ClipboardButton(props, ref) { + return ( + + + + ) + } +) + +export const ClipboardLink = React.forwardRef( + function ClipboardLink(props, ref) { + return ( + + + + ) + } +) + +export const ClipboardIconButton = React.forwardRef( + function ClipboardIconButton(props, ref) { + return ( + + + + + + + ) + } +) + +export const ClipboardInput = React.forwardRef( + function ClipboardInputElement(props, ref) { + return ( + + + + ) + } +) + +export const ClipboardRoot = ChakraClipboard.Root diff --git a/thingconnect.pulse.client/src/components/ui/close-button.tsx b/thingconnect.pulse.client/src/components/ui/close-button.tsx new file mode 100644 index 0000000..f834ed9 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/close-button.tsx @@ -0,0 +1,16 @@ +import type { ButtonProps } from '@chakra-ui/react' +import { IconButton as ChakraIconButton } from '@chakra-ui/react' +import * as React from 'react' +import { LuX } from 'react-icons/lu' + +export type CloseButtonProps = ButtonProps + +export const CloseButton = React.forwardRef( + function CloseButton(props, ref) { + return ( + + {props.children ?? } + + ) + } +) diff --git a/thingconnect.pulse.client/src/components/ui/color-mode.tsx b/thingconnect.pulse.client/src/components/ui/color-mode.tsx index f86a744..fb9051a 100644 --- a/thingconnect.pulse.client/src/components/ui/color-mode.tsx +++ b/thingconnect.pulse.client/src/components/ui/color-mode.tsx @@ -1,76 +1,103 @@ -"use client" +'use client' -import type { IconButtonProps, SpanProps } from "@chakra-ui/react" -import { ClientOnly, IconButton, Skeleton, Span } from "@chakra-ui/react" -import { ThemeProvider } from "next-themes" -import type { ThemeProviderProps } from "next-themes" -import * as React from "react" -import { LuMoon, LuSun } from "react-icons/lu" -import { useColorMode } from "../../lib/color-mode" +import type { IconButtonProps, SpanProps } from '@chakra-ui/react' +import { ClientOnly, IconButton, Skeleton, Span } from '@chakra-ui/react' +import { ThemeProvider, useTheme } from 'next-themes' +import type { ThemeProviderProps } from 'next-themes' +import * as React from 'react' +import { LuMoon, LuSun } from 'react-icons/lu' -export type ColorModeProviderProps = ThemeProviderProps +export interface ColorModeProviderProps extends ThemeProviderProps {} export function ColorModeProvider(props: ColorModeProviderProps) { - return ( - - ) + return } -export function ColorModeIcon() { - const { colorMode } = useColorMode() - return colorMode === "dark" ? : +export type ColorMode = 'light' | 'dark' + +export interface UseColorModeReturn { + colorMode: ColorMode + setColorMode: (colorMode: ColorMode) => void + toggleColorMode: () => void +} + +export function useColorMode(): UseColorModeReturn { + const { resolvedTheme, setTheme, forcedTheme } = useTheme() + const colorMode = forcedTheme || resolvedTheme + const toggleColorMode = () => { + setTheme(resolvedTheme === 'dark' ? 'light' : 'dark') + } + return { + colorMode: colorMode as ColorMode, + setColorMode: setTheme, + toggleColorMode, + } } -type ColorModeButtonProps = Omit +export function useColorModeValue(light: T, dark: T) { + const { colorMode } = useColorMode() + return colorMode === 'dark' ? dark : light +} -export const ColorModeButton = function ColorModeButton({ ref, ...props }: ColorModeButtonProps & { ref?: React.RefObject }) { - const { toggleColorMode } = useColorMode() - return ( - }> - - - - - ) +export function ColorModeIcon() { + const { colorMode } = useColorMode() + return colorMode === 'dark' ? : } -export const LightMode = function LightMode({ ref, ...props }: SpanProps & { ref?: React.RefObject }) { +interface ColorModeButtonProps extends Omit {} + +export const ColorModeButton = React.forwardRef( + function ColorModeButton(props, ref) { + const { toggleColorMode } = useColorMode() return ( - + }> + + + + ) } +) -export const DarkMode = function DarkMode({ ref, ...props }: SpanProps & { ref?: React.RefObject }) { +export const LightMode = React.forwardRef( + function LightMode(props, ref) { return ( ) } +) + +export const DarkMode = React.forwardRef(function DarkMode(props, ref) { + return ( + + ) +}) diff --git a/thingconnect.pulse.client/src/components/ui/color-picker.tsx b/thingconnect.pulse.client/src/components/ui/color-picker.tsx new file mode 100644 index 0000000..a2124d2 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/color-picker.tsx @@ -0,0 +1,201 @@ +import type { IconButtonProps, StackProps } from '@chakra-ui/react' +import { + ColorPicker as ChakraColorPicker, + For, + IconButton, + Portal, + Span, + Stack, + Text, + VStack, +} from '@chakra-ui/react' +import * as React from 'react' +import { LuCheck, LuPipette } from 'react-icons/lu' + +export const ColorPickerTrigger = React.forwardRef< + HTMLButtonElement, + ChakraColorPicker.TriggerProps & { fitContent?: boolean } +>(function ColorPickerTrigger(props, ref) { + const { fitContent, ...rest } = props + return ( + + {props.children || } + + ) +}) + +export const ColorPickerInput = React.forwardRef< + HTMLInputElement, + Omit +>(function ColorHexInput(props, ref) { + return +}) + +interface ColorPickerContentProps extends ChakraColorPicker.ContentProps { + portalled?: boolean + portalRef?: React.RefObject +} + +export const ColorPickerContent = React.forwardRef( + function ColorPickerContent(props, ref) { + const { portalled = true, portalRef, ...rest } = props + return ( + + + + + + ) + } +) + +export const ColorPickerInlineContent = React.forwardRef< + HTMLDivElement, + ChakraColorPicker.ContentProps +>(function ColorPickerInlineContent(props, ref) { + return ( + + ) +}) + +export const ColorPickerSliders = React.forwardRef( + function ColorPickerSliders(props, ref) { + return ( + + + + + ) + } +) + +export const ColorPickerArea = React.forwardRef( + function ColorPickerArea(props, ref) { + return ( + + + + + ) + } +) + +export const ColorPickerEyeDropper = React.forwardRef( + function ColorPickerEyeDropper(props, ref) { + return ( + + + + + + ) + } +) + +export const ColorPickerChannelSlider = React.forwardRef< + HTMLDivElement, + ChakraColorPicker.ChannelSliderProps +>(function ColorPickerSlider(props, ref) { + return ( + + + + + + ) +}) + +export const ColorPickerSwatchTrigger = React.forwardRef< + HTMLButtonElement, + ChakraColorPicker.SwatchTriggerProps & { + swatchSize?: ChakraColorPicker.SwatchTriggerProps['boxSize'] + } +>(function ColorPickerSwatchTrigger(props, ref) { + const { swatchSize, children, ...rest } = props + return ( + + {children || ( + + + + + + )} + + ) +}) + +export const ColorPickerRoot = React.forwardRef( + function ColorPickerRoot(props, ref) { + return ( + + {props.children} + + + ) + } +) + +const formatMap = { + rgba: ['red', 'green', 'blue', 'alpha'], + hsla: ['hue', 'saturation', 'lightness', 'alpha'], + hsba: ['hue', 'saturation', 'brightness', 'alpha'], + hexa: ['hex', 'alpha'], +} as const + +export const ColorPickerChannelInputs = React.forwardRef< + HTMLDivElement, + ChakraColorPicker.ViewProps +>(function ColorPickerChannelInputs(props, ref) { + const channels = formatMap[props.format] + return ( + + {channels.map((channel) => ( + + + + {channel.charAt(0).toUpperCase()} + + + ))} + + ) +}) + +export const ColorPickerChannelSliders = React.forwardRef< + HTMLDivElement, + ChakraColorPicker.ViewProps +>(function ColorPickerChannelSliders(props, ref) { + const channels = formatMap[props.format] + return ( + + + {(channel) => ( + + + {channel} + + + + )} + + + ) +}) + +export const ColorPickerLabel = ChakraColorPicker.Label +export const ColorPickerControl = ChakraColorPicker.Control +export const ColorPickerValueText = ChakraColorPicker.ValueText +export const ColorPickerValueSwatch = ChakraColorPicker.ValueSwatch +export const ColorPickerChannelInput = ChakraColorPicker.ChannelInput +export const ColorPickerSwatchGroup = ChakraColorPicker.SwatchGroup diff --git a/thingconnect.pulse.client/src/components/ui/data-list.tsx b/thingconnect.pulse.client/src/components/ui/data-list.tsx new file mode 100644 index 0000000..84264f9 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/data-list.tsx @@ -0,0 +1,28 @@ +import { DataList as ChakraDataList } from '@chakra-ui/react' +import { InfoTip } from './toggle-tip' +import * as React from 'react' + +export const DataListRoot = ChakraDataList.Root + +interface ItemProps extends ChakraDataList.ItemProps { + label: React.ReactNode + value: React.ReactNode + info?: React.ReactNode + grow?: boolean +} + +export const DataListItem = React.forwardRef( + function DataListItem(props, ref) { + const { label, info, value, children, grow, ...rest } = props + return ( + + + {label} + {info && {info}} + + {value} + {children} + + ) + } +) diff --git a/thingconnect.pulse.client/src/components/ui/dialog.tsx b/thingconnect.pulse.client/src/components/ui/dialog.tsx new file mode 100644 index 0000000..a986e10 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/dialog.tsx @@ -0,0 +1,49 @@ +import { Dialog as ChakraDialog, Portal } from '@chakra-ui/react' +import { CloseButton } from './close-button' +import * as React from 'react' + +interface DialogContentProps extends ChakraDialog.ContentProps { + portalled?: boolean + portalRef?: React.RefObject + backdrop?: boolean +} + +export const DialogContent = React.forwardRef( + function DialogContent(props, ref) { + const { children, portalled = true, portalRef, backdrop = true, ...rest } = props + + return ( + + {backdrop && } + + + {children} + + + + ) + } +) + +export const DialogCloseTrigger = React.forwardRef< + HTMLButtonElement, + ChakraDialog.CloseTriggerProps +>(function DialogCloseTrigger(props, ref) { + return ( + + + {props.children} + + + ) +}) + +export const DialogRoot = ChakraDialog.Root +export const DialogFooter = ChakraDialog.Footer +export const DialogHeader = ChakraDialog.Header +export const DialogBody = ChakraDialog.Body +export const DialogBackdrop = ChakraDialog.Backdrop +export const DialogTitle = ChakraDialog.Title +export const DialogDescription = ChakraDialog.Description +export const DialogTrigger = ChakraDialog.Trigger +export const DialogActionTrigger = ChakraDialog.ActionTrigger diff --git a/thingconnect.pulse.client/src/components/ui/drawer.tsx b/thingconnect.pulse.client/src/components/ui/drawer.tsx new file mode 100644 index 0000000..cd07cb5 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/drawer.tsx @@ -0,0 +1,45 @@ +import { Drawer as ChakraDrawer, Portal } from '@chakra-ui/react' +import { CloseButton } from './close-button' +import * as React from 'react' + +interface DrawerContentProps extends ChakraDrawer.ContentProps { + portalled?: boolean + portalRef?: React.RefObject + offset?: ChakraDrawer.ContentProps['padding'] +} + +export const DrawerContent = React.forwardRef( + function DrawerContent(props, ref) { + const { children, portalled = true, portalRef, offset, ...rest } = props + return ( + + + + {children} + + + + ) + } +) + +export const DrawerCloseTrigger = React.forwardRef< + HTMLButtonElement, + ChakraDrawer.CloseTriggerProps +>(function DrawerCloseTrigger(props, ref) { + return ( + + + + ) +}) + +export const DrawerTrigger = ChakraDrawer.Trigger +export const DrawerRoot = ChakraDrawer.Root +export const DrawerFooter = ChakraDrawer.Footer +export const DrawerHeader = ChakraDrawer.Header +export const DrawerBody = ChakraDrawer.Body +export const DrawerBackdrop = ChakraDrawer.Backdrop +export const DrawerDescription = ChakraDrawer.Description +export const DrawerTitle = ChakraDrawer.Title +export const DrawerActionTrigger = ChakraDrawer.ActionTrigger diff --git a/thingconnect.pulse.client/src/components/ui/empty-state.tsx b/thingconnect.pulse.client/src/components/ui/empty-state.tsx new file mode 100644 index 0000000..c146311 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/empty-state.tsx @@ -0,0 +1,30 @@ +import { EmptyState as ChakraEmptyState, VStack } from '@chakra-ui/react' +import * as React from 'react' + +export interface EmptyStateProps extends ChakraEmptyState.RootProps { + title: string + description?: string + icon?: React.ReactNode +} + +export const EmptyState = React.forwardRef( + function EmptyState(props, ref) { + const { title, description, icon, children, ...rest } = props + return ( + + + {icon && {icon}} + {description ? ( + + {title} + {description} + + ) : ( + {title} + )} + {children} + + + ) + } +) diff --git a/thingconnect.pulse.client/src/components/ui/field.tsx b/thingconnect.pulse.client/src/components/ui/field.tsx new file mode 100644 index 0000000..abffff3 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/field.tsx @@ -0,0 +1,26 @@ +import { Field as ChakraField } from '@chakra-ui/react' +import * as React from 'react' + +export interface FieldProps extends Omit { + label?: React.ReactNode + helperText?: React.ReactNode + errorText?: React.ReactNode + optionalText?: React.ReactNode +} + +export const Field = React.forwardRef(function Field(props, ref) { + const { label, children, helperText, errorText, optionalText, ...rest } = props + return ( + + {label && ( + + {label} + + + )} + {children} + {helperText && {helperText}} + {errorText && {errorText}} + + ) +}) diff --git a/thingconnect.pulse.client/src/components/ui/file-upload.tsx b/thingconnect.pulse.client/src/components/ui/file-upload.tsx new file mode 100644 index 0000000..8943789 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/file-upload.tsx @@ -0,0 +1,153 @@ +'use client' + +import type { ButtonProps, RecipeProps } from '@chakra-ui/react' +import { + Button, + FileUpload as ChakraFileUpload, + Icon, + IconButton, + Span, + Text, + useFileUploadContext, + useRecipe, +} from '@chakra-ui/react' +import * as React from 'react' +import { LuFile, LuUpload, LuX } from 'react-icons/lu' + +export interface FileUploadRootProps extends ChakraFileUpload.RootProps { + inputProps?: React.InputHTMLAttributes +} + +export const FileUploadRoot = React.forwardRef( + function FileUploadRoot(props, ref) { + const { children, inputProps, ...rest } = props + return ( + + + {children} + + ) + } +) + +export interface FileUploadDropzoneProps extends ChakraFileUpload.DropzoneProps { + label: React.ReactNode + description?: React.ReactNode +} + +export const FileUploadDropzone = React.forwardRef( + function FileUploadDropzone(props, ref) { + const { children, label, description, ...rest } = props + return ( + + + + + +
{label}
+ {description && {description}} +
+ {children} +
+ ) + } +) + +interface VisibilityProps { + showSize?: boolean + clearable?: boolean +} + +interface FileUploadItemProps extends VisibilityProps { + file: File +} + +const FileUploadItem = React.forwardRef( + function FileUploadItem(props, ref) { + const { file, showSize, clearable } = props + return ( + + + + + + + + {showSize ? ( + + + + + ) : ( + + )} + + {clearable && ( + + + + + + )} + + ) + } +) + +interface FileUploadListProps extends VisibilityProps, ChakraFileUpload.ItemGroupProps { + files?: File[] +} + +export const FileUploadList = React.forwardRef( + function FileUploadList(props, ref) { + const { showSize, clearable, files, ...rest } = props + + const fileUpload = useFileUploadContext() + const acceptedFiles = files ?? fileUpload.acceptedFiles + + if (acceptedFiles.length === 0) return null + + return ( + + {acceptedFiles.map((file) => ( + + ))} + + ) + } +) + +type Assign = Omit & U + +interface FileInputProps extends Assign> { + placeholder?: React.ReactNode +} + +export const FileInput = React.forwardRef( + function FileInput(props, ref) { + const inputRecipe = useRecipe({ key: 'input' }) + const [recipeProps, restProps] = inputRecipe.splitVariantProps(props) + const { placeholder = 'Select file(s)', ...rest } = restProps + return ( + + + + ) + } +) + +export const FileUploadLabel = ChakraFileUpload.Label +export const FileUploadClearTrigger = ChakraFileUpload.ClearTrigger +export const FileUploadTrigger = ChakraFileUpload.Trigger diff --git a/thingconnect.pulse.client/src/components/ui/hover-card.tsx b/thingconnect.pulse.client/src/components/ui/hover-card.tsx new file mode 100644 index 0000000..b49e035 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/hover-card.tsx @@ -0,0 +1,34 @@ +import { HoverCard, Portal } from '@chakra-ui/react' +import * as React from 'react' + +interface HoverCardContentProps extends HoverCard.ContentProps { + portalled?: boolean + portalRef?: React.RefObject +} + +export const HoverCardContent = React.forwardRef( + function HoverCardContent(props, ref) { + const { portalled = true, portalRef, ...rest } = props + + return ( + + + + + + ) + } +) + +export const HoverCardArrow = React.forwardRef( + function HoverCardArrow(props, ref) { + return ( + + + + ) + } +) + +export const HoverCardRoot = HoverCard.Root +export const HoverCardTrigger = HoverCard.Trigger diff --git a/thingconnect.pulse.client/src/components/ui/index.ts b/thingconnect.pulse.client/src/components/ui/index.ts index ad81259..6d1b092 100644 --- a/thingconnect.pulse.client/src/components/ui/index.ts +++ b/thingconnect.pulse.client/src/components/ui/index.ts @@ -1,3 +1,3 @@ export { Button } from './Button' export { Input } from './Input' -export { Card } from './Card' \ No newline at end of file +export { Card } from './Card' diff --git a/thingconnect.pulse.client/src/components/ui/input-group.tsx b/thingconnect.pulse.client/src/components/ui/input-group.tsx new file mode 100644 index 0000000..698950a --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/input-group.tsx @@ -0,0 +1,52 @@ +import type { BoxProps, InputElementProps } from '@chakra-ui/react' +import { Group, InputElement } from '@chakra-ui/react' +import * as React from 'react' + +export interface InputGroupProps extends BoxProps { + startElementProps?: InputElementProps + endElementProps?: InputElementProps + startElement?: React.ReactNode + endElement?: React.ReactNode + children: React.ReactElement + startOffset?: InputElementProps['paddingStart'] + endOffset?: InputElementProps['paddingEnd'] +} + +export const InputGroup = React.forwardRef( + function InputGroup(props, ref) { + const { + startElement, + startElementProps, + endElement, + endElementProps, + children, + startOffset = '6px', + endOffset = '6px', + ...rest + } = props + + const child = React.Children.only>(children) + + return ( + + {startElement && ( + + {startElement} + + )} + {React.cloneElement(child, { + ...(startElement && { + ps: `calc(var(--input-height) - ${startOffset})`, + }), + ...(endElement && { pe: `calc(var(--input-height) - ${endOffset})` }), + ...children.props, + })} + {endElement && ( + + {endElement} + + )} + + ) + } +) diff --git a/thingconnect.pulse.client/src/components/ui/link-button.tsx b/thingconnect.pulse.client/src/components/ui/link-button.tsx new file mode 100644 index 0000000..c13c1d8 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/link-button.tsx @@ -0,0 +1,11 @@ +'use client' + +import type { HTMLChakraProps, RecipeProps } from '@chakra-ui/react' +import { createRecipeContext } from '@chakra-ui/react' + +export interface LinkButtonProps extends HTMLChakraProps<'a', RecipeProps<'button'>> {} + +const { withContext } = createRecipeContext({ key: 'button' }) + +// Replace "a" with your framework's link component +export const LinkButton = withContext('a') diff --git a/thingconnect.pulse.client/src/components/ui/logo.tsx b/thingconnect.pulse.client/src/components/ui/logo.tsx index 75b220c..a967aac 100644 --- a/thingconnect.pulse.client/src/components/ui/logo.tsx +++ b/thingconnect.pulse.client/src/components/ui/logo.tsx @@ -1,6 +1,6 @@ -import { Image, type ImageProps } from "@chakra-ui/react" -import ThingConnectLogo from "../../assets/ThingConnect Logo.svg" -import ThingConnectIcon from "../../assets/ThingConnect Icon.svg" +import { Image, type ImageProps } from '@chakra-ui/react' +import ThingConnectLogo from '../../assets/ThingConnect Logo.svg' +import ThingConnectIcon from '../../assets/ThingConnect Icon.svg' interface LogoProps extends Omit { variant?: 'full' | 'icon' @@ -8,17 +8,17 @@ interface LogoProps extends Omit { } const sizeMap = { - sm: { full: "30px", icon: "24px" }, - md: { full: "40px", icon: "32px" }, - lg: { full: "50px", icon: "40px" }, - xl: { full: "60px", icon: "48px" }, + sm: { full: '30px', icon: '24px' }, + md: { full: '40px', icon: '32px' }, + lg: { full: '50px', icon: '40px' }, + xl: { full: '60px', icon: '48px' }, } export function Logo({ variant = 'full', size = 'md', ...props }: LogoProps) { const logoSrc = variant === 'full' ? ThingConnectLogo : ThingConnectIcon const logoAlt = variant === 'full' ? 'ThingConnect Logo' : 'ThingConnect' const logoHeight = sizeMap[size][variant] - + return ( ) -} \ No newline at end of file +} diff --git a/thingconnect.pulse.client/src/components/ui/menu.tsx b/thingconnect.pulse.client/src/components/ui/menu.tsx new file mode 100644 index 0000000..bac0b3f --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/menu.tsx @@ -0,0 +1,103 @@ +'use client' + +import { AbsoluteCenter, Menu as ChakraMenu, Portal } from '@chakra-ui/react' +import * as React from 'react' +import { LuCheck, LuChevronRight } from 'react-icons/lu' + +interface MenuContentProps extends ChakraMenu.ContentProps { + portalled?: boolean + portalRef?: React.RefObject +} + +export const MenuContent = React.forwardRef( + function MenuContent(props, ref) { + const { portalled = true, portalRef, ...rest } = props + return ( + + + + + + ) + } +) + +export const MenuArrow = React.forwardRef( + function MenuArrow(props, ref) { + return ( + + + + ) + } +) + +export const MenuCheckboxItem = React.forwardRef( + function MenuCheckboxItem(props, ref) { + return ( + + + + + + + {props.children} + + ) + } +) + +export const MenuRadioItem = React.forwardRef( + function MenuRadioItem(props, ref) { + const { children, ...rest } = props + return ( + + + + + + + {children} + + ) + } +) + +export const MenuItemGroup = React.forwardRef( + function MenuItemGroup(props, ref) { + const { title, children, ...rest } = props + return ( + + {title && {title}} + {children} + + ) + } +) + +export interface MenuTriggerItemProps extends ChakraMenu.ItemProps { + startIcon?: React.ReactNode +} + +export const MenuTriggerItem = React.forwardRef( + function MenuTriggerItem(props, ref) { + const { startIcon, children, ...rest } = props + return ( + + {startIcon} + {children} + + + ) + } +) + +export const MenuRadioItemGroup = ChakraMenu.RadioItemGroup +export const MenuContextTrigger = ChakraMenu.ContextTrigger +export const MenuRoot = ChakraMenu.Root +export const MenuSeparator = ChakraMenu.Separator + +export const MenuItem = ChakraMenu.Item +export const MenuItemText = ChakraMenu.ItemText +export const MenuItemCommand = ChakraMenu.ItemCommand +export const MenuTrigger = ChakraMenu.Trigger diff --git a/thingconnect.pulse.client/src/components/ui/native-select.tsx b/thingconnect.pulse.client/src/components/ui/native-select.tsx new file mode 100644 index 0000000..3ae56e8 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/native-select.tsx @@ -0,0 +1,53 @@ +'use client' + +import { NativeSelect as Select } from '@chakra-ui/react' +import * as React from 'react' + +interface NativeSelectRootProps extends Select.RootProps { + icon?: React.ReactNode +} + +export const NativeSelectRoot = React.forwardRef( + function NativeSelect(props, ref) { + const { icon, children, ...rest } = props + return ( + + {children} + {icon} + + ) + } +) + +interface NativeSelectItem { + value: string + label: string + disabled?: boolean +} + +interface NativeSelectFieldProps extends Select.FieldProps { + items?: Array +} + +export const NativeSelectField = React.forwardRef( + function NativeSelectField(props, ref) { + const { items: itemsProp, children, ...rest } = props + + const items = React.useMemo( + () => + itemsProp?.map((item) => (typeof item === 'string' ? { label: item, value: item } : item)), + [itemsProp] + ) + + return ( + + {children} + {items?.map((item) => ( + + ))} + + ) + } +) diff --git a/thingconnect.pulse.client/src/components/ui/number-input.tsx b/thingconnect.pulse.client/src/components/ui/number-input.tsx new file mode 100644 index 0000000..36f22a4 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/number-input.tsx @@ -0,0 +1,23 @@ +import { NumberInput as ChakraNumberInput } from '@chakra-ui/react' +import * as React from 'react' + +export interface NumberInputProps extends ChakraNumberInput.RootProps {} + +export const NumberInputRoot = React.forwardRef( + function NumberInput(props, ref) { + const { children, ...rest } = props + return ( + + {children} + + + + + + ) + } +) + +export const NumberInputField = ChakraNumberInput.Input +export const NumberInputScrubber = ChakraNumberInput.Scrubber +export const NumberInputLabel = ChakraNumberInput.Label diff --git a/thingconnect.pulse.client/src/components/ui/pagination.tsx b/thingconnect.pulse.client/src/components/ui/pagination.tsx new file mode 100644 index 0000000..cbf13f9 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/pagination.tsx @@ -0,0 +1,188 @@ +'use client' + +import type { ButtonProps, TextProps } from '@chakra-ui/react' +import { + Button, + Pagination as ChakraPagination, + IconButton, + Text, + createContext, + usePaginationContext, +} from '@chakra-ui/react' +import * as React from 'react' +import { HiChevronLeft, HiChevronRight, HiMiniEllipsisHorizontal } from 'react-icons/hi2' +import { LinkButton } from './link-button' + +interface ButtonVariantMap { + current: ButtonProps['variant'] + default: ButtonProps['variant'] + ellipsis: ButtonProps['variant'] +} + +type PaginationVariant = 'outline' | 'solid' | 'subtle' + +interface ButtonVariantContext { + size: ButtonProps['size'] + variantMap: ButtonVariantMap + getHref?: (page: number) => string +} + +const [RootPropsProvider, useRootProps] = createContext({ + name: 'RootPropsProvider', +}) + +export interface PaginationRootProps extends Omit { + size?: ButtonProps['size'] + variant?: PaginationVariant + getHref?: (page: number) => string +} + +const variantMap: Record = { + outline: { default: 'ghost', ellipsis: 'plain', current: 'outline' }, + solid: { default: 'outline', ellipsis: 'outline', current: 'solid' }, + subtle: { default: 'ghost', ellipsis: 'plain', current: 'subtle' }, +} + +export const PaginationRoot = React.forwardRef( + function PaginationRoot(props, ref) { + const { size = 'sm', variant = 'outline', getHref, ...rest } = props + return ( + + + + ) + } +) + +export const PaginationEllipsis = React.forwardRef( + function PaginationEllipsis(props, ref) { + const { size, variantMap } = useRootProps() + return ( + + + + ) + } +) + +export const PaginationItem = React.forwardRef( + function PaginationItem(props, ref) { + const { page } = usePaginationContext() + const { size, variantMap, getHref } = useRootProps() + + const current = page === props.value + const variant = current ? variantMap.current : variantMap.default + + if (getHref) { + return ( + + {props.value} + + ) + } + + return ( + + + + ) + } +) + +export const PaginationPrevTrigger = React.forwardRef< + HTMLButtonElement, + ChakraPagination.PrevTriggerProps +>(function PaginationPrevTrigger(props, ref) { + const { size, variantMap, getHref } = useRootProps() + const { previousPage } = usePaginationContext() + + if (getHref) { + return ( + + + + ) + } + + return ( + + + + + + ) +}) + +export const PaginationNextTrigger = React.forwardRef< + HTMLButtonElement, + ChakraPagination.NextTriggerProps +>(function PaginationNextTrigger(props, ref) { + const { size, variantMap, getHref } = useRootProps() + const { nextPage } = usePaginationContext() + + if (getHref) { + return ( + + + + ) + } + + return ( + + + + + + ) +}) + +export const PaginationItems = (props: React.HTMLAttributes) => { + return ( + + {({ pages }) => + pages.map((page, index) => { + return page.type === 'ellipsis' ? ( + + ) : ( + + ) + }) + } + + ) +} + +interface PageTextProps extends TextProps { + format?: 'short' | 'compact' | 'long' +} + +export const PaginationPageText = React.forwardRef( + function PaginationPageText(props, ref) { + const { format = 'compact', ...rest } = props + const { page, totalPages, pageRange, count } = usePaginationContext() + const content = React.useMemo(() => { + if (format === 'short') return `${page} / ${totalPages}` + if (format === 'compact') return `${page} of ${totalPages}` + return `${pageRange.start + 1} - ${Math.min(pageRange.end, count)} of ${count}` + }, [format, page, totalPages, pageRange, count]) + + return ( + + {content} + + ) + } +) diff --git a/thingconnect.pulse.client/src/components/ui/password-input.tsx b/thingconnect.pulse.client/src/components/ui/password-input.tsx new file mode 100644 index 0000000..4014f18 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/password-input.tsx @@ -0,0 +1,146 @@ +'use client' + +import type { ButtonProps, GroupProps, InputProps, StackProps } from '@chakra-ui/react' +import { + Box, + HStack, + IconButton, + Input, + InputGroup, + Stack, + mergeRefs, + useControllableState, +} from '@chakra-ui/react' +import * as React from 'react' +import { LuEye, LuEyeOff } from 'react-icons/lu' + +export interface PasswordVisibilityProps { + /** + * The default visibility state of the password input. + */ + defaultVisible?: boolean + /** + * The controlled visibility state of the password input. + */ + visible?: boolean + /** + * Callback invoked when the visibility state changes. + */ + onVisibleChange?: (visible: boolean) => void + /** + * Custom icons for the visibility toggle button. + */ + visibilityIcon?: { on: React.ReactNode; off: React.ReactNode } +} + +export interface PasswordInputProps extends InputProps, PasswordVisibilityProps { + rootProps?: GroupProps +} + +export const PasswordInput = React.forwardRef( + function PasswordInput(props, ref) { + const { + rootProps, + defaultVisible, + visible: visibleProp, + onVisibleChange, + visibilityIcon = { on: , off: }, + ...rest + } = props + + const [visible, setVisible] = useControllableState({ + value: visibleProp, + defaultValue: defaultVisible || false, + onChange: onVisibleChange, + }) + + const inputRef = React.useRef(null) + + return ( + { + if (rest.disabled) return + if (e.button !== 0) return + e.preventDefault() + setVisible(!visible) + }} + > + {visible ? visibilityIcon.off : visibilityIcon.on} + + } + {...rootProps} + > + + + ) + } +) + +const VisibilityTrigger = React.forwardRef( + function VisibilityTrigger(props, ref) { + return ( + + ) + } +) + +interface PasswordStrengthMeterProps extends StackProps { + max?: number + value: number +} + +export const PasswordStrengthMeter = React.forwardRef( + function PasswordStrengthMeter(props, ref) { + const { max = 4, value, ...rest } = props + + const percent = (value / max) * 100 + const { label, colorPalette } = getColorPalette(percent) + + return ( + + + {Array.from({ length: max }).map((_, index) => ( + + ))} + + {label && {label}} + + ) + } +) + +function getColorPalette(percent: number) { + switch (true) { + case percent < 33: + return { label: 'Low', colorPalette: 'red' } + case percent < 66: + return { label: 'Medium', colorPalette: 'orange' } + default: + return { label: 'High', colorPalette: 'green' } + } +} diff --git a/thingconnect.pulse.client/src/components/ui/pin-input.tsx b/thingconnect.pulse.client/src/components/ui/pin-input.tsx new file mode 100644 index 0000000..af57461 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/pin-input.tsx @@ -0,0 +1,27 @@ +import { PinInput as ChakraPinInput, Group } from '@chakra-ui/react' +import * as React from 'react' + +export interface PinInputProps extends ChakraPinInput.RootProps { + rootRef?: React.RefObject + count?: number + inputProps?: React.InputHTMLAttributes + attached?: boolean +} + +export const PinInput = React.forwardRef( + function PinInput(props, ref) { + const { count = 4, inputProps, rootRef, attached, ...rest } = props + return ( + + + + + {Array.from({ length: count }).map((_, index) => ( + + ))} + + + + ) + } +) diff --git a/thingconnect.pulse.client/src/components/ui/popover.tsx b/thingconnect.pulse.client/src/components/ui/popover.tsx new file mode 100644 index 0000000..6a2dad4 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/popover.tsx @@ -0,0 +1,57 @@ +import { Popover as ChakraPopover, Portal } from '@chakra-ui/react' +import { CloseButton } from './close-button' +import * as React from 'react' + +interface PopoverContentProps extends ChakraPopover.ContentProps { + portalled?: boolean + portalRef?: React.RefObject +} + +export const PopoverContent = React.forwardRef( + function PopoverContent(props, ref) { + const { portalled = true, portalRef, ...rest } = props + return ( + + + + + + ) + } +) + +export const PopoverArrow = React.forwardRef( + function PopoverArrow(props, ref) { + return ( + + + + ) + } +) + +export const PopoverCloseTrigger = React.forwardRef< + HTMLButtonElement, + ChakraPopover.CloseTriggerProps +>(function PopoverCloseTrigger(props, ref) { + return ( + + + + ) +}) + +export const PopoverTitle = ChakraPopover.Title +export const PopoverDescription = ChakraPopover.Description +export const PopoverFooter = ChakraPopover.Footer +export const PopoverHeader = ChakraPopover.Header +export const PopoverRoot = ChakraPopover.Root +export const PopoverBody = ChakraPopover.Body +export const PopoverTrigger = ChakraPopover.Trigger diff --git a/thingconnect.pulse.client/src/components/ui/progress-circle.tsx b/thingconnect.pulse.client/src/components/ui/progress-circle.tsx new file mode 100644 index 0000000..324c2bc --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/progress-circle.tsx @@ -0,0 +1,33 @@ +import type { SystemStyleObject } from '@chakra-ui/react' +import { AbsoluteCenter, ProgressCircle as ChakraProgressCircle } from '@chakra-ui/react' +import * as React from 'react' + +interface ProgressCircleRingProps extends ChakraProgressCircle.CircleProps { + trackColor?: SystemStyleObject['stroke'] + cap?: SystemStyleObject['strokeLinecap'] +} + +export const ProgressCircleRing = React.forwardRef( + function ProgressCircleRing(props, ref) { + const { trackColor, cap, color, ...rest } = props + return ( + + + + + ) + } +) + +export const ProgressCircleValueText = React.forwardRef< + HTMLDivElement, + ChakraProgressCircle.ValueTextProps +>(function ProgressCircleValueText(props, ref) { + return ( + + + + ) +}) + +export const ProgressCircleRoot = ChakraProgressCircle.Root diff --git a/thingconnect.pulse.client/src/components/ui/progress.tsx b/thingconnect.pulse.client/src/components/ui/progress.tsx new file mode 100644 index 0000000..cdc6671 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/progress.tsx @@ -0,0 +1,32 @@ +import { Progress as ChakraProgress } from '@chakra-ui/react' +import { InfoTip } from './toggle-tip' +import * as React from 'react' + +export const ProgressBar = React.forwardRef( + function ProgressBar(props, ref) { + return ( + + + + ) + } +) + +export interface ProgressLabelProps extends ChakraProgress.LabelProps { + info?: React.ReactNode +} + +export const ProgressLabel = React.forwardRef( + function ProgressLabel(props, ref) { + const { children, info, ...rest } = props + return ( + + {children} + {info && {info}} + + ) + } +) + +export const ProgressRoot = ChakraProgress.Root +export const ProgressValueText = ChakraProgress.ValueText diff --git a/thingconnect.pulse.client/src/components/ui/prose.tsx b/thingconnect.pulse.client/src/components/ui/prose.tsx new file mode 100644 index 0000000..3d0552b --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/prose.tsx @@ -0,0 +1,275 @@ +'use client' + +import { chakra } from '@chakra-ui/react' + +const TRAILING_PSEUDO_REGEX = /(::?[\w-]+(?:\([^)]*\))?)+$/ +const EXCLUDE_CLASSNAME = '.not-prose' +function inWhere(selector: T): T { + const rebuiltSelector = selector.startsWith('& ') ? selector.slice(2) : selector + const match = selector.match(TRAILING_PSEUDO_REGEX) + const pseudo = match ? match[0] : '' + const base = match ? selector.slice(0, -match[0].length) : rebuiltSelector + return `& :where(${base}):not(${EXCLUDE_CLASSNAME}, ${EXCLUDE_CLASSNAME} *)${pseudo}` as T +} + +export const Prose = chakra('div', { + base: { + color: 'fg.muted', + maxWidth: '65ch', + fontSize: 'sm', + lineHeight: '1.7em', + [inWhere('& p')]: { + marginTop: '1em', + marginBottom: '1em', + }, + [inWhere('& blockquote')]: { + marginTop: '1.285em', + marginBottom: '1.285em', + paddingInline: '1.285em', + borderInlineStartWidth: '0.25em', + color: 'fg', + }, + [inWhere('& a')]: { + color: 'fg', + textDecoration: 'underline', + textUnderlineOffset: '3px', + textDecorationThickness: '2px', + textDecorationColor: 'border.muted', + fontWeight: '500', + }, + [inWhere('& strong')]: { + fontWeight: '600', + }, + [inWhere('& a strong')]: { + color: 'inherit', + }, + [inWhere('& h1')]: { + fontSize: '2.15em', + letterSpacing: '-0.02em', + marginTop: '0', + marginBottom: '0.8em', + lineHeight: '1.2em', + }, + [inWhere('& h2')]: { + fontSize: '1.4em', + letterSpacing: '-0.02em', + marginTop: '1.6em', + marginBottom: '0.8em', + lineHeight: '1.4em', + }, + [inWhere('& h3')]: { + fontSize: '1.285em', + letterSpacing: '-0.01em', + marginTop: '1.5em', + marginBottom: '0.4em', + lineHeight: '1.5em', + }, + [inWhere('& h4')]: { + marginTop: '1.4em', + marginBottom: '0.5em', + letterSpacing: '-0.01em', + lineHeight: '1.5em', + }, + [inWhere('& img')]: { + marginTop: '1.7em', + marginBottom: '1.7em', + borderRadius: 'lg', + boxShadow: 'inset', + }, + [inWhere('& picture')]: { + marginTop: '1.7em', + marginBottom: '1.7em', + }, + [inWhere('& picture > img')]: { + marginTop: '0', + marginBottom: '0', + }, + [inWhere('& video')]: { + marginTop: '1.7em', + marginBottom: '1.7em', + }, + [inWhere('& kbd')]: { + fontSize: '0.85em', + borderRadius: 'xs', + paddingTop: '0.15em', + paddingBottom: '0.15em', + paddingInlineEnd: '0.35em', + paddingInlineStart: '0.35em', + fontFamily: 'inherit', + color: 'fg.muted', + '--shadow': 'colors.border', + boxShadow: '0 0 0 1px var(--shadow),0 1px 0 1px var(--shadow)', + }, + [inWhere('& code')]: { + fontSize: '0.925em', + letterSpacing: '-0.01em', + borderRadius: 'md', + borderWidth: '1px', + padding: '0.25em', + }, + [inWhere('& pre code')]: { + fontSize: 'inherit', + letterSpacing: 'inherit', + borderWidth: 'inherit', + padding: '0', + }, + [inWhere('& h2 code')]: { + fontSize: '0.9em', + }, + [inWhere('& h3 code')]: { + fontSize: '0.8em', + }, + [inWhere('& pre')]: { + backgroundColor: 'bg.subtle', + marginTop: '1.6em', + marginBottom: '1.6em', + borderRadius: 'md', + fontSize: '0.9em', + paddingTop: '0.65em', + paddingBottom: '0.65em', + paddingInlineEnd: '1em', + paddingInlineStart: '1em', + overflowX: 'auto', + fontWeight: '400', + }, + [inWhere('& ol')]: { + marginTop: '1em', + marginBottom: '1em', + paddingInlineStart: '1.5em', + }, + [inWhere('& ul')]: { + marginTop: '1em', + marginBottom: '1em', + paddingInlineStart: '1.5em', + }, + [inWhere('& li')]: { + marginTop: '0.285em', + marginBottom: '0.285em', + }, + [inWhere('& ol > li')]: { + paddingInlineStart: '0.4em', + listStyleType: 'decimal', + '&::marker': { + color: 'fg.muted', + }, + }, + [inWhere('& ul > li')]: { + paddingInlineStart: '0.4em', + listStyleType: 'disc', + '&::marker': { + color: 'fg.muted', + }, + }, + [inWhere('& > ul > li p')]: { + marginTop: '0.5em', + marginBottom: '0.5em', + }, + [inWhere('& > ul > li > p:first-of-type')]: { + marginTop: '1em', + }, + [inWhere('& > ul > li > p:last-of-type')]: { + marginBottom: '1em', + }, + [inWhere('& > ol > li > p:first-of-type')]: { + marginTop: '1em', + }, + [inWhere('& > ol > li > p:last-of-type')]: { + marginBottom: '1em', + }, + [inWhere('& ul ul, ul ol, ol ul, ol ol')]: { + marginTop: '0.5em', + marginBottom: '0.5em', + }, + [inWhere('& dl')]: { + marginTop: '1em', + marginBottom: '1em', + }, + [inWhere('& dt')]: { + fontWeight: '600', + marginTop: '1em', + }, + [inWhere('& dd')]: { + marginTop: '0.285em', + paddingInlineStart: '1.5em', + }, + [inWhere('& hr')]: { + marginTop: '2.25em', + marginBottom: '2.25em', + }, + [inWhere('& :is(h1,h2,h3,h4,h5,hr) + *')]: { + marginTop: '0', + }, + [inWhere('& table')]: { + width: '100%', + tableLayout: 'auto', + textAlign: 'start', + lineHeight: '1.5em', + marginTop: '2em', + marginBottom: '2em', + }, + [inWhere('& thead')]: { + borderBottomWidth: '1px', + color: 'fg', + }, + [inWhere('& tbody tr')]: { + borderBottomWidth: '1px', + borderBottomColor: 'border', + }, + [inWhere('& thead th')]: { + paddingInlineEnd: '1em', + paddingBottom: '0.65em', + paddingInlineStart: '1em', + fontWeight: 'medium', + textAlign: 'start', + }, + [inWhere('& thead th:first-of-type')]: { + paddingInlineStart: '0', + }, + [inWhere('& thead th:last-of-type')]: { + paddingInlineEnd: '0', + }, + [inWhere('& tbody td, tfoot td')]: { + paddingTop: '0.65em', + paddingInlineEnd: '1em', + paddingBottom: '0.65em', + paddingInlineStart: '1em', + }, + [inWhere('& tbody td:first-of-type, tfoot td:first-of-type')]: { + paddingInlineStart: '0', + }, + [inWhere('& tbody td:last-of-type, tfoot td:last-of-type')]: { + paddingInlineEnd: '0', + }, + [inWhere('& figure')]: { + marginTop: '1.625em', + marginBottom: '1.625em', + }, + [inWhere('& figure > *')]: { + marginTop: '0', + marginBottom: '0', + }, + [inWhere('& figcaption')]: { + fontSize: '0.85em', + lineHeight: '1.25em', + marginTop: '0.85em', + color: 'fg.muted', + }, + [inWhere('& h1, h2, h3, h4')]: { + color: 'fg', + fontWeight: '600', + }, + }, + variants: { + size: { + md: { + fontSize: 'sm', + }, + lg: { + fontSize: 'md', + }, + }, + }, + defaultVariants: { + size: 'md', + }, +}) diff --git a/thingconnect.pulse.client/src/components/ui/provider.tsx b/thingconnect.pulse.client/src/components/ui/provider.tsx index afd7f9b..4ab35b2 100644 --- a/thingconnect.pulse.client/src/components/ui/provider.tsx +++ b/thingconnect.pulse.client/src/components/ui/provider.tsx @@ -1,11 +1,8 @@ -"use client" +'use client' -import { ChakraProvider } from "@chakra-ui/react" -import { - ColorModeProvider, - type ColorModeProviderProps, -} from "./color-mode" -import { system } from "../../theme" +import { ChakraProvider } from '@chakra-ui/react' +import { ColorModeProvider, type ColorModeProviderProps } from './color-mode' +import { system } from './theme' export function Provider(props: ColorModeProviderProps) { return ( diff --git a/thingconnect.pulse.client/src/components/ui/qr-code.tsx b/thingconnect.pulse.client/src/components/ui/qr-code.tsx new file mode 100644 index 0000000..e943494 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/qr-code.tsx @@ -0,0 +1,20 @@ +import { QrCode as ChakraQrCode } from '@chakra-ui/react' +import * as React from 'react' + +export interface QrCodeProps extends Omit { + fill?: string + overlay?: React.ReactNode +} + +export const QrCode = React.forwardRef(function QrCode(props, ref) { + const { children, fill, overlay, ...rest } = props + return ( + + + + + {overlay} + {children && {children}} + + ) +}) diff --git a/thingconnect.pulse.client/src/components/ui/radio-card.tsx b/thingconnect.pulse.client/src/components/ui/radio-card.tsx new file mode 100644 index 0000000..9aab10b --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/radio-card.tsx @@ -0,0 +1,53 @@ +import { RadioCard } from '@chakra-ui/react' +import * as React from 'react' + +interface RadioCardItemProps extends RadioCard.ItemProps { + icon?: React.ReactElement + label?: React.ReactNode + description?: React.ReactNode + addon?: React.ReactNode + indicator?: React.ReactNode | null + indicatorPlacement?: 'start' | 'end' | 'inside' + inputProps?: React.InputHTMLAttributes +} + +export const RadioCardItem = React.forwardRef( + function RadioCardItem(props, ref) { + const { + inputProps, + label, + description, + addon, + icon, + indicator = , + indicatorPlacement = 'end', + ...rest + } = props + + const hasContent = label || description || icon + const ContentWrapper = indicator ? RadioCard.ItemContent : React.Fragment + + return ( + + + + {indicatorPlacement === 'start' && indicator} + {hasContent && ( + + {icon} + {label && {label}} + {description && {description}} + {indicatorPlacement === 'inside' && indicator} + + )} + {indicatorPlacement === 'end' && indicator} + + {addon && {addon}} + + ) + } +) + +export const RadioCardRoot = RadioCard.Root +export const RadioCardLabel = RadioCard.Label +export const RadioCardItemIndicator = RadioCard.ItemIndicator diff --git a/thingconnect.pulse.client/src/components/ui/radio.tsx b/thingconnect.pulse.client/src/components/ui/radio.tsx new file mode 100644 index 0000000..142d847 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/radio.tsx @@ -0,0 +1,20 @@ +import { RadioGroup as ChakraRadioGroup } from '@chakra-ui/react' +import * as React from 'react' + +export interface RadioProps extends ChakraRadioGroup.ItemProps { + rootRef?: React.RefObject + inputProps?: React.InputHTMLAttributes +} + +export const Radio = React.forwardRef(function Radio(props, ref) { + const { children, inputProps, rootRef, ...rest } = props + return ( + + + + {children && {children}} + + ) +}) + +export const RadioGroup = ChakraRadioGroup.Root diff --git a/thingconnect.pulse.client/src/components/ui/rating.tsx b/thingconnect.pulse.client/src/components/ui/rating.tsx new file mode 100644 index 0000000..fd05933 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/rating.tsx @@ -0,0 +1,25 @@ +import { RatingGroup } from '@chakra-ui/react' +import * as React from 'react' + +export interface RatingProps extends RatingGroup.RootProps { + icon?: React.ReactElement + count?: number + label?: React.ReactNode +} + +export const Rating = React.forwardRef(function Rating(props, ref) { + const { icon, count = 5, label, ...rest } = props + return ( + + {label && {label}} + + + {Array.from({ length: count }).map((_, index) => ( + + + + ))} + + + ) +}) diff --git a/thingconnect.pulse.client/src/components/ui/segmented-control.tsx b/thingconnect.pulse.client/src/components/ui/segmented-control.tsx new file mode 100644 index 0000000..46295fe --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/segmented-control.tsx @@ -0,0 +1,42 @@ +'use client' + +import { For, SegmentGroup } from '@chakra-ui/react' +import * as React from 'react' + +interface Item { + value: string + label: React.ReactNode + disabled?: boolean +} + +export interface SegmentedControlProps extends SegmentGroup.RootProps { + items: Array +} + +function normalize(items: Array): Item[] { + return items.map((item) => { + if (typeof item === 'string') return { value: item, label: item } + return item + }) +} + +export const SegmentedControl = React.forwardRef( + function SegmentedControl(props, ref) { + const { items, ...rest } = props + const data = React.useMemo(() => normalize(items), [items]) + + return ( + + + + {(item) => ( + + {item.label} + + + )} + + + ) + } +) diff --git a/thingconnect.pulse.client/src/components/ui/select.tsx b/thingconnect.pulse.client/src/components/ui/select.tsx new file mode 100644 index 0000000..7aa29ae --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/select.tsx @@ -0,0 +1,134 @@ +'use client' + +import type { CollectionItem } from '@chakra-ui/react' +import { Select as ChakraSelect, Portal } from '@chakra-ui/react' +import { CloseButton } from './close-button' +import * as React from 'react' + +interface SelectTriggerProps extends ChakraSelect.ControlProps { + clearable?: boolean +} + +export const SelectTrigger = React.forwardRef( + function SelectTrigger(props, ref) { + const { children, clearable, ...rest } = props + return ( + + {children} + + {clearable && } + + + + ) + } +) + +const SelectClearTrigger = React.forwardRef( + function SelectClearTrigger(props, ref) { + return ( + + + + ) + } +) + +interface SelectContentProps extends ChakraSelect.ContentProps { + portalled?: boolean + portalRef?: React.RefObject +} + +export const SelectContent = React.forwardRef( + function SelectContent(props, ref) { + const { portalled = true, portalRef, ...rest } = props + return ( + + + + + + ) + } +) + +export const SelectItem = React.forwardRef( + function SelectItem(props, ref) { + const { item, children, ...rest } = props + return ( + + {children} + + + ) + } +) + +interface SelectValueTextProps extends Omit { + children?(items: CollectionItem[]): React.ReactNode +} + +export const SelectValueText = React.forwardRef( + function SelectValueText(props, ref) { + const { children, ...rest } = props + return ( + + + {(select) => { + const items = select.selectedItems + if (items.length === 0) return props.placeholder + if (children) return children(items) + if (items.length === 1) return select.collection.stringifyItem(items[0]) + return `${items.length} selected` + }} + + + ) + } +) + +export const SelectRoot = React.forwardRef( + function SelectRoot(props, ref) { + return ( + + {props.asChild ? ( + props.children + ) : ( + <> + + {props.children} + + )} + + ) + } +) as ChakraSelect.RootComponent + +interface SelectItemGroupProps extends ChakraSelect.ItemGroupProps { + label: React.ReactNode +} + +export const SelectItemGroup = React.forwardRef( + function SelectItemGroup(props, ref) { + const { children, label, ...rest } = props + return ( + + {label} + {children} + + ) + } +) + +export const SelectLabel = ChakraSelect.Label +export const SelectItemText = ChakraSelect.ItemText diff --git a/thingconnect.pulse.client/src/components/ui/skeleton.tsx b/thingconnect.pulse.client/src/components/ui/skeleton.tsx new file mode 100644 index 0000000..76b729e --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/skeleton.tsx @@ -0,0 +1,37 @@ +import type { SkeletonProps as ChakraSkeletonProps, CircleProps } from '@chakra-ui/react' +import { Skeleton as ChakraSkeleton, Circle, Stack } from '@chakra-ui/react' +import * as React from 'react' + +export interface SkeletonCircleProps extends ChakraSkeletonProps { + size?: CircleProps['size'] +} + +export const SkeletonCircle = React.forwardRef( + function SkeletonCircle(props, ref) { + const { size, ...rest } = props + return ( + + + + ) + } +) + +export interface SkeletonTextProps extends ChakraSkeletonProps { + noOfLines?: number +} + +export const SkeletonText = React.forwardRef( + function SkeletonText(props, ref) { + const { noOfLines = 3, gap, ...rest } = props + return ( + + {Array.from({ length: noOfLines }).map((_, index) => ( + + ))} + + ) + } +) + +export const Skeleton = ChakraSkeleton diff --git a/thingconnect.pulse.client/src/components/ui/slider.tsx b/thingconnect.pulse.client/src/components/ui/slider.tsx new file mode 100644 index 0000000..4d935a7 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/slider.tsx @@ -0,0 +1,78 @@ +import { Slider as ChakraSlider, For, HStack } from '@chakra-ui/react' +import * as React from 'react' + +export interface SliderProps extends ChakraSlider.RootProps { + marks?: Array + label?: React.ReactNode + showValue?: boolean +} + +export const Slider = React.forwardRef(function Slider(props, ref) { + const { marks: marksProp, label, showValue, ...rest } = props + const value = props.defaultValue ?? props.value + + const marks = marksProp?.map((mark) => { + if (typeof mark === 'number') return { value: mark, label: undefined } + return mark + }) + + const hasMarkLabel = !!marks?.some((mark) => mark.label) + + return ( + + {label && !showValue && {label}} + {label && showValue && ( + + {label} + + + )} + + + + + + + + + ) +}) + +function SliderThumbs(props: { value?: number[] }) { + const { value } = props + return ( + + {(_, index) => ( + + + + )} + + ) +} + +interface SliderMarksProps { + marks?: Array +} + +const SliderMarks = React.forwardRef( + function SliderMarks(props, ref) { + const { marks } = props + if (!marks?.length) return null + + return ( + + {marks.map((mark, index) => { + const value = typeof mark === 'number' ? mark : mark.value + const label = typeof mark === 'number' ? undefined : mark.label + return ( + + + {label} + + ) + })} + + ) + } +) diff --git a/thingconnect.pulse.client/src/components/ui/stat.tsx b/thingconnect.pulse.client/src/components/ui/stat.tsx new file mode 100644 index 0000000..03bfee0 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/stat.tsx @@ -0,0 +1,61 @@ +import { Badge, type BadgeProps, Stat as ChakraStat, FormatNumber } from '@chakra-ui/react' +import { InfoTip } from './toggle-tip' +import * as React from 'react' + +interface StatLabelProps extends ChakraStat.LabelProps { + info?: React.ReactNode +} + +export const StatLabel = React.forwardRef( + function StatLabel(props, ref) { + const { info, children, ...rest } = props + return ( + + {children} + {info && {info}} + + ) + } +) + +interface StatValueTextProps extends ChakraStat.ValueTextProps { + value?: number + formatOptions?: Intl.NumberFormatOptions +} + +export const StatValueText = React.forwardRef( + function StatValueText(props, ref) { + const { value, formatOptions, children, ...rest } = props + return ( + + {children || (value != null && )} + + ) + } +) + +export const StatUpTrend = React.forwardRef( + function StatUpTrend(props, ref) { + return ( + + + {props.children} + + ) + } +) + +export const StatDownTrend = React.forwardRef( + function StatDownTrend(props, ref) { + return ( + + + {props.children} + + ) + } +) + +export const StatRoot = ChakraStat.Root +export const StatHelpText = ChakraStat.HelpText +export const StatValueUnit = ChakraStat.ValueUnit diff --git a/thingconnect.pulse.client/src/components/ui/status.tsx b/thingconnect.pulse.client/src/components/ui/status.tsx new file mode 100644 index 0000000..ae91d39 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/status.tsx @@ -0,0 +1,27 @@ +import type { ColorPalette } from '@chakra-ui/react' +import { Status as ChakraStatus } from '@chakra-ui/react' +import * as React from 'react' + +type StatusValue = 'success' | 'error' | 'warning' | 'info' + +export interface StatusProps extends ChakraStatus.RootProps { + value?: StatusValue +} + +const statusMap: Record = { + success: 'green', + error: 'red', + warning: 'orange', + info: 'blue', +} + +export const Status = React.forwardRef(function Status(props, ref) { + const { children, value = 'info', ...rest } = props + const colorPalette = rest.colorPalette ?? statusMap[value] + return ( + + + {children} + + ) +}) diff --git a/thingconnect.pulse.client/src/components/ui/stepper-input.tsx b/thingconnect.pulse.client/src/components/ui/stepper-input.tsx new file mode 100644 index 0000000..4e884bc --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/stepper-input.tsx @@ -0,0 +1,47 @@ +import { HStack, IconButton, NumberInput } from '@chakra-ui/react' +import * as React from 'react' +import { LuMinus, LuPlus } from 'react-icons/lu' + +export interface StepperInputProps extends NumberInput.RootProps { + label?: React.ReactNode +} + +export const StepperInput = React.forwardRef( + function StepperInput(props, ref) { + const { label, ...rest } = props + return ( + + {label && {label}} + + + + + + + ) + } +) + +const DecrementTrigger = React.forwardRef( + function DecrementTrigger(props, ref) { + return ( + + + + + + ) + } +) + +const IncrementTrigger = React.forwardRef( + function IncrementTrigger(props, ref) { + return ( + + + + + + ) + } +) diff --git a/thingconnect.pulse.client/src/components/ui/steps.tsx b/thingconnect.pulse.client/src/components/ui/steps.tsx new file mode 100644 index 0000000..48a0620 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/steps.tsx @@ -0,0 +1,77 @@ +import { Box, Steps as ChakraSteps } from '@chakra-ui/react' +import * as React from 'react' +import { LuCheck } from 'react-icons/lu' + +interface StepInfoProps { + title?: React.ReactNode + description?: React.ReactNode +} + +export interface StepsItemProps extends Omit, StepInfoProps { + completedIcon?: React.ReactNode + icon?: React.ReactNode +} + +export const StepsItem = React.forwardRef( + function StepsItem(props, ref) { + const { title, description, completedIcon, icon, ...rest } = props + return ( + + + + } + incomplete={icon || } + /> + + + + + + ) + } +) + +const StepInfo = (props: StepInfoProps) => { + const { title, description } = props + + if (title && description) { + return ( + + {title} + {description} + + ) + } + + return ( + <> + {title && {title}} + {description && {description}} + + ) +} + +interface StepsIndicatorProps { + completedIcon: React.ReactNode + icon?: React.ReactNode +} + +export const StepsIndicator = React.forwardRef( + function StepsIndicator(props, ref) { + const { icon = , completedIcon } = props + return ( + + + + ) + } +) + +export const StepsList = ChakraSteps.List +export const StepsRoot = ChakraSteps.Root +export const StepsContent = ChakraSteps.Content +export const StepsCompletedContent = ChakraSteps.CompletedContent + +export const StepsNextTrigger = ChakraSteps.NextTrigger +export const StepsPrevTrigger = ChakraSteps.PrevTrigger diff --git a/thingconnect.pulse.client/src/components/ui/switch.tsx b/thingconnect.pulse.client/src/components/ui/switch.tsx new file mode 100644 index 0000000..d43c2d7 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/switch.tsx @@ -0,0 +1,32 @@ +import { Switch as ChakraSwitch } from '@chakra-ui/react' +import * as React from 'react' + +export interface SwitchProps extends ChakraSwitch.RootProps { + inputProps?: React.InputHTMLAttributes + rootRef?: React.RefObject + trackLabel?: { on: React.ReactNode; off: React.ReactNode } + thumbLabel?: { on: React.ReactNode; off: React.ReactNode } +} + +export const Switch = React.forwardRef(function Switch(props, ref) { + const { inputProps, children, rootRef, trackLabel, thumbLabel, ...rest } = props + + return ( + + + + + {thumbLabel && ( + + {thumbLabel?.on} + + )} + + {trackLabel && ( + {trackLabel.on} + )} + + {children != null && {children}} + + ) +}) diff --git a/thingconnect.pulse.client/src/components/ui/tag.tsx b/thingconnect.pulse.client/src/components/ui/tag.tsx new file mode 100644 index 0000000..d6a13fc --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/tag.tsx @@ -0,0 +1,26 @@ +import { Tag as ChakraTag } from '@chakra-ui/react' +import * as React from 'react' + +export interface TagProps extends ChakraTag.RootProps { + startElement?: React.ReactNode + endElement?: React.ReactNode + onClose?: VoidFunction + closable?: boolean +} + +export const Tag = React.forwardRef(function Tag(props, ref) { + const { startElement, endElement, onClose, closable = !!onClose, children, ...rest } = props + + return ( + + {startElement && {startElement}} + {children} + {endElement && {endElement}} + {closable && ( + + + + )} + + ) +}) diff --git a/thingconnect.pulse.client/src/components/ui/theme-toggle.tsx b/thingconnect.pulse.client/src/components/ui/theme-toggle.tsx index b515617..de9e7b4 100644 --- a/thingconnect.pulse.client/src/components/ui/theme-toggle.tsx +++ b/thingconnect.pulse.client/src/components/ui/theme-toggle.tsx @@ -1,67 +1,55 @@ -"use client" +'use client' -import { - Button, - HStack, - VStack, - Text, - Box, - Heading, - Stack -} from "@chakra-ui/react" -import { ColorModeButton } from "./color-mode" -import { LuPalette, LuSun, LuMoon } from "react-icons/lu" -import { useColorMode } from "../../lib/color-mode" +import { Button, HStack, VStack, Text, Box, Heading, Stack } from '@chakra-ui/react' +import { ColorModeButton } from './color-mode' +import { LuPalette, LuSun, LuMoon } from 'react-icons/lu' +import { useColorMode } from '../../lib/color-mode' export function ThemeToggle() { const { colorMode, toggleColorMode } = useColorMode() - + return ( - + Theme Settings - + Switch between light and dark themes for the best viewing experience - - + + - + - + Current theme: - {colorMode === "light" ? "Light" : "Dark"} + {colorMode === 'light' ? 'Light' : 'Dark'} @@ -84,22 +72,50 @@ export function EnterpriseThemeShowcase() { - + {/* Brand Colors Card */} Brand Colors - + Primary - + Success - + Warning - + Danger @@ -121,34 +137,39 @@ export function EnterpriseThemeShowcase() { {/* Layout Examples */} - + - Section Style + + Section Style + This uses the enterprise.section layer style for content areas. - + - Header Style + + Header Style + This uses the enterprise.header layer style for navigation areas. - - 🎨 Professional enterprise theme with semantic color tokens that automatically adapt to light and dark modes + 🎨 Professional enterprise theme with semantic color tokens that automatically adapt to + light and dark modes ) -} \ No newline at end of file +} diff --git a/thingconnect.pulse.client/src/components/ui/theme/CLAUDE.md b/thingconnect.pulse.client/src/components/ui/theme/CLAUDE.md new file mode 100644 index 0000000..2b13d29 --- /dev/null +++ b/thingconnect.pulse.client/src/components/ui/theme/CLAUDE.md @@ -0,0 +1,335 @@ +# ThingConnect.Pulse Theme System + +This document describes the complete Chakra UI theme configuration for ThingConnect.Pulse, based on Atlassian Design System principles. + +## Theme Structure + +The theme is split across multiple files for better organization: + +- `index.ts` - Main theme configuration with semantic tokens, typography, and layer styles +- `colors.ts` - All color definitions using `defineTokens.colors()` + +## Color System + +### Core Color Palettes + +The theme includes comprehensive color palettes with specific shade ranges: + +#### Blue (Primary Brand Color) + +- **Range**: 100-1000 (no 50) +- **Key shades**: + - `blue.500` (#388BFF) - Primary blue + - `blue.700` (#0C66E4) - Dark accent + - `blue.900` (#09326C) - Darkest + +#### Red (Status/Error Color) + +- **Range**: 100-1000 +- **Key shades**: + - `red.500` (#F15B50) - Primary red + - `red.600` (#E2483D) - Medium dark + - `red.1000` (#42221F) - Darkest + +#### Green (Success Color) + +- **Range**: 100-1000 +- **Key shades**: + - `green.500` (#2ABB7F) - Success green + - `green.400` (#4BCE97) - Light success + +#### Other Colors + +- **Yellow**: 100-1000 (warning states) +- **Purple**: 100-1000 (accent color) +- **Teal**: 100-1000 (secondary accent) +- **Orange**: 100-1000 (additional status) +- **Gray**: 100-1000 (neutrals with fine-grained steps) + +#### Special Palettes + +- **Neutrals**: 0, 100-1100, plus alpha variants (100A-500A) +- **Dark Neutrals**: -100 to 1100, plus alpha variants +- **White/Black Alpha**: 50-950 (transparency variants) + +### Semantic Color Tokens + +Each color family has semantic variants that adapt to light/dark mode: + +```typescript +// Example for blue +blue: { + solid: { value: '{colors.blue.500}' }, // Solid background + contrast: { value: 'white' }, // Contrasting text + fg: { // Foreground text + value: { + _light: '{colors.blue.600}', + _dark: '{colors.blue.400}', + }, + }, + muted: { // Muted background + value: { + _light: '{colors.blue.100}', + _dark: '{colors.blue.900}', + }, + }, + subtle: { /* ... */ }, // Subtle background + emphasized: { /* ... */ }, // Emphasized state + focusRing: { value: '{colors.blue.500}' }, // Focus indicators +} +``` + +### Global Semantic Tokens + +Atlassian-style surface tokens for consistent theming: + +- `color.background.default` - Main background +- `color.background.sunken` - Recessed areas +- `color.background.raised` - Elevated surfaces +- `color.background.overlay` - Modal/overlay backgrounds + +Legacy tokens for compatibility: + +- `bg`, `bg.panel` - Background surfaces +- `fg`, `fg.muted`, `fg.subtle` - Text colors +- `border`, `border.muted`, `border.subtle` - Border colors + +## Typography System + +### Font Stack + +All text uses Atlassian Sans with system fallbacks: + +``` +'Atlassian Sans', ui-sans-serif, -apple-system, BlinkMacSystemFont, +'Segoe UI', Roboto, Helvetica, Arial, sans-serif +``` + +### Text Styles + +#### Heading Scales + +- `ui.heading.xxlarge` - 72px, weight 600 +- `ui.heading.xlarge` - 60px, weight 600 +- `ui.heading.large` - 48px, weight 600 +- `ui.heading.medium` - 36px, weight 500 +- `ui.heading.small` - 30px, weight 500 +- `ui.heading.xsmall` - 24px, weight 500 +- `ui.heading.xxsmall` - 20px, weight 500 + +#### Body Text + +- `ui.body.large` - 18px, line-height 1.556 +- `ui.body.medium` - 16px, line-height 1.5 (default) +- `ui.body.small` - 14px, line-height 1.429 + +#### Utility Styles + +- `ui.helper` - 12px helper text +- `ui.code` - 14px monospace code + +### Font Sizes + +Precise scale from 2xs (10px) to 7xl (72px): + +- `2xs`: 0.625rem (10px) +- `xs`: 0.75rem (12px) +- `sm`: 0.875rem (14px) +- `md`: 1rem (16px) - default +- `lg`: 1.125rem (18px) +- `xl`: 1.25rem (20px) +- `2xl`: 1.5rem (24px) +- `3xl`: 1.875rem (30px) +- `4xl`: 2.25rem (36px) +- `5xl`: 3rem (48px) +- `6xl`: 3.75rem (60px) +- `7xl`: 4.5rem (72px) + +## Spacing System + +Atlassian Design System 4px grid-based spacing: + +- `025`: 0.125rem (2px) - minimal +- `050`: 0.25rem (4px) - space.050 +- `075`: 0.375rem (6px) - space.075 +- `100`: 0.5rem (8px) - space.100 +- `150`: 0.75rem (12px) - space.150 +- `200`: 1rem (16px) - space.200 +- `250`: 1.25rem (20px) - space.250 +- `300`: 1.5rem (24px) - space.300 +- `400`: 2rem (32px) - space.400 +- `500`: 2.5rem (40px) - space.500 +- `600`: 3rem (48px) - space.600 +- `800`: 4rem (64px) - space.800 +- `1000`: 5rem (80px) - space.1000 + +## Border Radius + +Atlassian-inspired radius scale: + +- `none`: 0 +- `sm`: 2px +- `base`: 3px (Atlassian button standard) +- `md`: 4px +- `lg`: 6px +- `xl`: 8px +- `2xl`: 12px +- `3xl`: 16px +- `full`: 9999px + +## Shadows + +Atlassian elevation system: + +- `raised`: Subtle card elevation +- `overlay`: Modal/popover shadows +- `xs`, `sm`, `base`, `md`, `lg`: Progressive elevation +- Dark mode variants: `raised.dark`, `overlay.dark` + +## Gradients + +Subtle brand gradients: + +- `brand.subtle`: Blue gradient +- `accent.subtle`: Teal to green +- `surface.overlay`: Dark overlay +- `surface.overlay.light`: Light overlay + +## Layer Styles + +### Atlassian Component Styles + +#### Cards & Panels + +```typescript +'atlassian.card': { + bg: 'color.background.raised', + borderRadius: 'base', + border: '1px solid', + borderColor: 'border.muted', + boxShadow: 'raised', + p: '200', // 16px +} + +'atlassian.panel': { + bg: 'color.background.sunken', + borderRadius: 'base', + border: '1px solid', + borderColor: 'border.subtle', + p: '300', // 24px +} +``` + +#### Buttons + +```typescript +'atlassian.button.primary': { + bg: 'blue.500', + color: 'white', + borderRadius: 'base', + px: '200', + py: '100', + minHeight: '32px', +} + +'atlassian.button.secondary': { + bg: 'transparent', + color: 'blue.500', + border: '1px solid', + borderColor: 'blue.500', + borderRadius: 'base', + px: '200', + py: '100', + minHeight: '32px', +} +``` + +#### Layout Elements + +```typescript +'atlassian.header': { + bg: 'color.background.default', + borderBottom: '1px solid', + borderColor: 'border.muted', + px: '200', + py: '150', + minHeight: '44px', +} + +'atlassian.modal': { + bg: 'color.background.overlay', + borderRadius: 'lg', + boxShadow: 'overlay', + p: '300', +} +``` + +## Usage Guidelines + +### Color Usage + +1. **Always prefer semantic tokens** over raw color values + - ✅ `bg="blue.subtle"` + - ❌ `bg="blue.100"` + +2. **Use appropriate color families** + - Blue: Primary actions, links + - Red: Errors, destructive actions + - Green: Success states + - Yellow: Warnings + - Purple: Special features + +3. **Leverage automatic light/dark mode** + - Semantic tokens automatically adapt + - Use `_light` and `_dark` variants when needed + +### Typography Usage + +1. **Use text styles for consistency** + - ✅ `textStyle="ui.body.medium"` + - ❌ Manual fontSize/lineHeight + +2. **Maintain hierarchy** + - Headings: xxlarge → xxsmall + - Body: large → small + - Helper text: ui.helper + +### Spacing Usage + +1. **Follow the 4px grid** + - Use spacing tokens: `p="200"` (16px) + - Avoid arbitrary values + +2. **Consistent patterns** + - Cards: padding `200` (16px) + - Panels: padding `300` (24px) + - Headers: padding `150`/`200` (12px/16px) + +### Layer Style Usage + +1. **Use Atlassian layer styles** + - `layerStyle="atlassian.card"` for cards + - `layerStyle="atlassian.button.primary"` for primary buttons + +2. **Maintain touch targets** + - Minimum 32px height for interactive elements + - Consistent 44px header height + +## Dark Mode Support + +The theme automatically supports dark mode through: + +- Semantic tokens with `_light`/`_dark` variants +- Global CSS that respects `colorScheme` +- Automatic color inversion for appropriate elements + +## File Organization + +``` +src/components/ui/theme/ +├── index.ts # Main theme config +├── colors.ts # Color definitions +└── CLAUDE.md # This documentation +``` + +The theme is exported as `system` from `index.ts` and should be used with Chakra UI's Provider. diff --git a/thingconnect.pulse.client/src/theme/README.md b/thingconnect.pulse.client/src/components/ui/theme/README.md similarity index 97% rename from thingconnect.pulse.client/src/theme/README.md rename to thingconnect.pulse.client/src/components/ui/theme/README.md index 8efcea3..3301f44 100644 --- a/thingconnect.pulse.client/src/theme/README.md +++ b/thingconnect.pulse.client/src/components/ui/theme/README.md @@ -5,6 +5,7 @@ This directory contains the enterprise-focused theme configuration for ThingConn ## Design System Foundation ### 🏗️ Atlassian Design System Integration + - **Design Tokens**: Follows Atlassian's token naming conventions and structure - **4px Grid System**: Consistent spacing based on Atlassian's rhythm - **Surface System**: Semantic background colors (default, sunken, raised, overlay) @@ -12,6 +13,7 @@ This directory contains the enterprise-focused theme configuration for ThingConn - **Typography**: 2025 typography refresh with bolder fonts and improved readability ### 🎨 ThingConnect Brand Colors + - **Primary Brand**: `#076bb3` - The signature ThingConnect blue extracted from logo - **Secondary Accent**: `#16a5a4` - The distinctive ThingConnect teal from logo - **Supporting Colors**: Dark variants `#124771` and `#097a7d` for depth and contrast @@ -20,19 +22,23 @@ This directory contains the enterprise-focused theme configuration for ThingConn - **Accessibility**: WCAG compliant color contrasts ### 🌙 Dark/Light Mode Support + - Seamless theme switching with `next-themes` - Automatic system preference detection - Smooth transitions between modes - Enterprise-optimized color schemes for both modes ### 📐 Typography System + - Inter font family for modern, readable text - Comprehensive text styles (display, heading, body, caption, overline) - Consistent spacing and line heights - Professional letter spacing ### 🧱 Layout Styles + Pre-built layer styles for common enterprise UI patterns: + - `enterprise.card` - Card containers with proper shadows and borders - `enterprise.section` - Content sections with subtle backgrounds - `enterprise.header` - Navigation and header areas @@ -41,8 +47,9 @@ Pre-built layer styles for common enterprise UI patterns: ## Usage ### Atlassian-Style Usage + ```tsx -import { Box, Text, Heading } from "@chakra-ui/react" +import { Box, Text, Heading } from '@chakra-ui/react' function MyComponent() { return ( @@ -57,6 +64,7 @@ function MyComponent() { ``` ### Legacy Enterprise Usage (Still Supported) + ```tsx function LegacyComponent() { return ( @@ -71,6 +79,7 @@ function LegacyComponent() { ``` ### Color Palette Usage + ```tsx // Use semantic tokens that automatically adapt to light/dark mode @@ -47,4 +45,4 @@ function DashboardPage() { ) } -export default DashboardPage \ No newline at end of file +export default DashboardPage diff --git a/thingconnect.pulse.client/src/pages/ThemeShowcasePage.tsx b/thingconnect.pulse.client/src/pages/ThemeShowcasePage.tsx new file mode 100644 index 0000000..11f1847 --- /dev/null +++ b/thingconnect.pulse.client/src/pages/ThemeShowcasePage.tsx @@ -0,0 +1,50 @@ +import { Box, Heading, Stack, Button } from '@chakra-ui/react' +import { useColorMode } from '@/components/ui/color-mode' +import { Tooltip } from '@/components/ui/tooltip' +import _ from 'lodash' + +const ThemeShowcasePage = () => { + const { colorMode, toggleColorMode } = useColorMode() + const colorFamilies = ['blue', 'red', 'green', 'yellow', 'teal', 'purple', 'neutral'] + + return ( + + + {/* Header */} + + + Color Palette + + + + + {/* Color Grid - Columns like screenshot */} + + + {colorFamilies.map((family) => ( + + {_.range(100, 1000, 100).map((value) => ( + + + + ))} + + ))} + + + + + ) +} + +export default ThemeShowcasePage diff --git a/thingconnect.pulse.client/src/services/authService.ts b/thingconnect.pulse.client/src/services/authService.ts index 9038b9c..5ffb476 100644 --- a/thingconnect.pulse.client/src/services/authService.ts +++ b/thingconnect.pulse.client/src/services/authService.ts @@ -1,71 +1,71 @@ -import axios from 'axios'; +import axios from 'axios' -const API_BASE_URL = 'https://localhost:7286/api'; +const API_BASE_URL = '/api' export interface AuthResponse { - token: string; - username: string; - email: string; + token: string + username: string + email: string } export interface LoginRequest { - username: string; - password: string; + username: string + password: string } export interface RegisterRequest { - username: string; - email: string; - password: string; + username: string + email: string + password: string } export interface UserProfile { - id: number; - username: string; - email: string; - createdAt: string; + id: number + username: string + email: string + createdAt: string } class AuthService { - private token: string | null = null; + private token: string | null = null constructor() { - this.token = localStorage.getItem('token'); - this.setupAxiosInterceptors(); + this.token = localStorage.getItem('token') + this.setupAxiosInterceptors() } private setupAxiosInterceptors() { axios.interceptors.request.use((config) => { if (this.token) { - config.headers.Authorization = `Bearer ${this.token}`; + config.headers.Authorization = `Bearer ${this.token}` } - return config; - }); + return config + }) axios.interceptors.response.use( (response) => response, (error: unknown) => { if ((error as { response?: { status?: number } })?.response?.status === 401) { - this.logout(); + this.logout() } - return Promise.reject(new Error( - (error as { message?: string })?.message || 'An error occurred' - )); + return Promise.reject( + new Error((error as { message?: string })?.message || 'An error occurred') + ) } - ); + ) } async login(username: string, password: string): Promise { const response = await axios.post(`${API_BASE_URL}/auth/login`, { username, password, - }); + }) - const { token } = response.data; - this.token = token; - localStorage.setItem('token', token); + const { token } = response.data + this.token = token + localStorage.setItem('token', token) - return response.data; + return response.data } async register(username: string, email: string, password: string): Promise { @@ -73,32 +73,32 @@ class AuthService { username, email, password, - }); + }) - const { token } = response.data; - this.token = token; - localStorage.setItem('token', token); + const { token } = response.data + this.token = token + localStorage.setItem('token', token) - return response.data; + return response.data } async getUserProfile(): Promise { - const response = await axios.get(`${API_BASE_URL}/user/profile`); - return response.data; + const response = await axios.get(`${API_BASE_URL}/user/profile`) + return response.data } logout(): void { - this.token = null; - localStorage.removeItem('token'); + this.token = null + localStorage.removeItem('token') } isAuthenticated(): boolean { - return this.token !== null; + return this.token !== null } getToken(): string | null { - return this.token; + return this.token } } -export const authService = new AuthService(); \ No newline at end of file +export const authService = new AuthService() diff --git a/thingconnect.pulse.client/src/theme/index.ts b/thingconnect.pulse.client/src/theme/index.ts deleted file mode 100644 index adbf42e..0000000 --- a/thingconnect.pulse.client/src/theme/index.ts +++ /dev/null @@ -1,670 +0,0 @@ -import { createSystem, defaultConfig, defineConfig } from "@chakra-ui/react" - -const config = defineConfig({ - theme: { - tokens: { - colors: { - // Atlassian brand colors - primary blue palette (B400 = #0052CC) - brand: { - 50: { value: "#e6f2ff" }, - 100: { value: "#cce6ff" }, - 200: { value: "#99ccff" }, - 300: { value: "#66b3ff" }, - 400: { value: "#0052cc" }, // Atlassian primary blue (B400) - 500: { value: "#0052cc" }, // Atlassian primary blue (B400) - 600: { value: "#0046b3" }, - 700: { value: "#003a99" }, - 800: { value: "#002e80" }, - 900: { value: "#002266" }, - 950: { value: "#001a4d" }, - }, - // Atlassian neutral surfaces - clean whites and dark backgrounds - surfaceDefault: { - 50: { value: "#ffffff" }, // Atlassian white (N0) - 900: { value: "#091e42" } // Atlassian dark blue (N900) - }, - surfaceSunken: { - 50: { value: "#f4f5f7" }, // Atlassian light gray (N20) - 900: { value: "#0c2140" } // Atlassian darker blue - }, - surfaceRaised: { - 50: { value: "#ffffff" }, // Atlassian white (N0) - 900: { value: "#172b4d" } // Atlassian neutral text (N800) - }, - surfaceOverlay: { - 50: { value: "#ffffff" }, // Atlassian white (N0) - 900: { value: "#253858" } // Atlassian modal overlay - }, - // Atlassian teal/secondary colors (T300 = #00B8D9) - accent: { - 50: { value: "#e6fcff" }, - 100: { value: "#ccf9ff" }, - 200: { value: "#99f2ff" }, - 300: { value: "#66ebff" }, - 400: { value: "#33e4ff" }, - 500: { value: "#00b8d9" }, // Atlassian teal (T300) - 600: { value: "#00a3c7" }, - 700: { value: "#008fb5" }, - 800: { value: "#007ba3" }, - 900: { value: "#006691" }, - 950: { value: "#00527f" }, - }, - // Atlassian neutral grays (N800 = #172B4D for text) - neutral: { - 50: { value: "#fafbfc" }, // Atlassian light (N10) - 100: { value: "#f4f5f7" }, // Atlassian light gray (N20) - 200: { value: "#ebecf0" }, // Atlassian border (N40) - 300: { value: "#dfe1e6" }, // Atlassian subtle (N60) - 400: { value: "#97a0af" }, // Atlassian muted (N200) - 500: { value: "#6b778c" }, // Atlassian secondary (N300) - 600: { value: "#505f79" }, // Atlassian primary text (N500) - 700: { value: "#42526e" }, // Atlassian darker text (N600) - 800: { value: "#172b4d" }, // Atlassian text dark (N800) - 900: { value: "#091e42" }, // Atlassian darkest (N900) - 950: { value: "#061b35" }, // Atlassian deep dark - }, - // Atlassian success colors (G300 = #36B37E) - success: { - 50: { value: "#e3fcef" }, - 100: { value: "#c7f9df" }, - 200: { value: "#8ff2bf" }, - 300: { value: "#57eb9f" }, - 400: { value: "#36b37e" }, // Atlassian green (G300) - 500: { value: "#36b37e" }, // Atlassian green (G300) - 600: { value: "#2e9f6b" }, - 700: { value: "#268b58" }, - 800: { value: "#1e7745" }, - 900: { value: "#166332" }, - 950: { value: "#0e4f1f" }, - }, - // Atlassian warning colors (Y300 = #FFAB00) - warning: { - 50: { value: "#fff8e6" }, - 100: { value: "#fff0cc" }, - 200: { value: "#ffe199" }, - 300: { value: "#ffd266" }, - 400: { value: "#ffc333" }, - 500: { value: "#ffab00" }, // Atlassian yellow (Y300) - 600: { value: "#e6970e" }, - 700: { value: "#cc831c" }, - 800: { value: "#b36f2a" }, - 900: { value: "#995b38" }, - 950: { value: "#804746" }, - }, - // Atlassian error/danger colors (R300 = #FF5630) - danger: { - 50: { value: "#ffebe6" }, - 100: { value: "#ffd6cc" }, - 200: { value: "#ffad99" }, - 300: { value: "#ff8566" }, - 400: { value: "#ff5c33" }, - 500: { value: "#ff5630" }, // Atlassian red (R300) - 600: { value: "#e64a2b" }, - 700: { value: "#cc3e26" }, - 800: { value: "#b33221" }, - 900: { value: "#99261c" }, - 950: { value: "#801a17" }, - }, - }, - fonts: { - heading: { value: "'Atlassian Sans', ui-sans-serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" }, - body: { value: "'Atlassian Sans', ui-sans-serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" }, - mono: { value: "SF Mono, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace" }, - }, - fontSizes: { - "2xs": { value: "0.625rem" }, // 10px - xs: { value: "0.75rem" }, // 12px - sm: { value: "0.875rem" }, // 14px - md: { value: "1rem" }, // 16px - lg: { value: "1.125rem" }, // 18px - xl: { value: "1.25rem" }, // 20px - "2xl": { value: "1.5rem" }, // 24px - "3xl": { value: "1.875rem" }, // 30px - "4xl": { value: "2.25rem" }, // 36px - "5xl": { value: "3rem" }, // 48px - "6xl": { value: "3.75rem" }, // 60px - "7xl": { value: "4.5rem" }, // 72px - }, - spacing: { - // Atlassian Design System 4px grid spacing tokens - "025": { value: "0.125rem" }, // 2px - minimal spacing - "050": { value: "0.25rem" }, // 4px - space.050 - "075": { value: "0.375rem" }, // 6px - space.075 - "100": { value: "0.5rem" }, // 8px - space.100 - "150": { value: "0.75rem" }, // 12px - space.150 - "200": { value: "1rem" }, // 16px - space.200 - "250": { value: "1.25rem" }, // 20px - space.250 - "300": { value: "1.5rem" }, // 24px - space.300 - "400": { value: "2rem" }, // 32px - space.400 - "500": { value: "2.5rem" }, // 40px - space.500 - "600": { value: "3rem" }, // 48px - space.600 - "800": { value: "4rem" }, // 64px - space.800 - "1000": { value: "5rem" }, // 80px - space.1000 - }, - radii: { - none: { value: "0" }, - sm: { value: "0.125rem" }, // 2px - base: { value: "0.1875rem" }, // 3px - Atlassian button border radius - md: { value: "0.25rem" }, // 4px - lg: { value: "0.375rem" }, // 6px - xl: { value: "0.5rem" }, // 8px - "2xl": { value: "0.75rem" }, // 12px - "3xl": { value: "1rem" }, // 16px - full: { value: "9999px" }, - }, - shadows: { - // Atlassian-inspired shadow system - subtle and clean - raised: { value: "0 1px 1px rgba(9, 30, 66, 0.25), 0 0 1px rgba(9, 30, 66, 0.31)" }, - overlay: { value: "0 8px 16px rgba(9, 30, 66, 0.25), 0 0 1px rgba(9, 30, 66, 0.31)" }, - "raised.dark": { value: "0 1px 1px rgba(0, 0, 0, 0.25), 0 0 1px rgba(0, 0, 0, 0.31)" }, - "overlay.dark": { value: "0 8px 16px rgba(0, 0, 0, 0.36), 0 0 1px rgba(0, 0, 0, 0.31)" }, - // Atlassian elevation shadows - xs: { value: "0 1px 1px rgba(9, 30, 66, 0.25)" }, - sm: { value: "0 1px 1px rgba(9, 30, 66, 0.25), 0 0 1px rgba(9, 30, 66, 0.31)" }, - base: { value: "0 4px 8px rgba(9, 30, 66, 0.25), 0 0 1px rgba(9, 30, 66, 0.31)" }, - md: { value: "0 8px 16px rgba(9, 30, 66, 0.25), 0 0 1px rgba(9, 30, 66, 0.31)" }, - lg: { value: "0 16px 24px rgba(9, 30, 66, 0.25), 0 0 1px rgba(9, 30, 66, 0.31)" }, - }, - gradients: { - // Atlassian subtle gradients for overlays and backgrounds - "brand.subtle": { value: "linear-gradient(135deg, #0052cc 0%, #0065ff 100%)" }, - "accent.subtle": { value: "linear-gradient(135deg, #00b8d9 0%, #36b37e 100%)" }, - "surface.overlay": { value: "linear-gradient(rgba(9, 30, 66, 0), rgba(9, 30, 66, 0.6))" }, - "surface.overlay.light": { value: "linear-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.9))" }, - }, - }, - semanticTokens: { - colors: { - // ThingConnect brand semantic tokens - brand: { - solid: { value: "{colors.brand.500}" }, - contrast: { value: "white" }, - fg: { - value: { - _light: "{colors.brand.600}", - _dark: "{colors.brand.400}" - } - }, - muted: { - value: { - _light: "{colors.brand.50}", - _dark: "{colors.brand.950}" - } - }, - subtle: { - value: { - _light: "{colors.brand.100}", - _dark: "{colors.brand.900}" - } - }, - emphasized: { - value: { - _light: "{colors.brand.200}", - _dark: "{colors.brand.800}" - } - }, - focusRing: { value: "{colors.brand.500}" }, - }, - // ThingConnect accent/secondary semantic tokens - accent: { - solid: { value: "{colors.accent.500}" }, - contrast: { value: "white" }, - fg: { - value: { - _light: "{colors.accent.600}", - _dark: "{colors.accent.400}" - } - }, - muted: { - value: { - _light: "{colors.accent.50}", - _dark: "{colors.accent.950}" - } - }, - subtle: { - value: { - _light: "{colors.accent.100}", - _dark: "{colors.accent.900}" - } - }, - emphasized: { - value: { - _light: "{colors.accent.200}", - _dark: "{colors.accent.800}" - } - }, - focusRing: { value: "{colors.accent.500}" }, - }, - // Success semantic tokens - success: { - solid: { value: "{colors.success.500}" }, - contrast: { value: "white" }, - fg: { - value: { - _light: "{colors.success.600}", - _dark: "{colors.success.400}" - } - }, - muted: { - value: { - _light: "{colors.success.50}", - _dark: "{colors.success.950}" - } - }, - subtle: { - value: { - _light: "{colors.success.100}", - _dark: "{colors.success.900}" - } - }, - emphasized: { - value: { - _light: "{colors.success.200}", - _dark: "{colors.success.800}" - } - }, - focusRing: { value: "{colors.success.500}" }, - }, - // Warning semantic tokens - warning: { - solid: { value: "{colors.warning.500}" }, - contrast: { value: "white" }, - fg: { - value: { - _light: "{colors.warning.600}", - _dark: "{colors.warning.400}" - } - }, - muted: { - value: { - _light: "{colors.warning.50}", - _dark: "{colors.warning.950}" - } - }, - subtle: { - value: { - _light: "{colors.warning.100}", - _dark: "{colors.warning.900}" - } - }, - emphasized: { - value: { - _light: "{colors.warning.200}", - _dark: "{colors.warning.800}" - } - }, - focusRing: { value: "{colors.warning.500}" }, - }, - // Danger semantic tokens - danger: { - solid: { value: "{colors.danger.500}" }, - contrast: { value: "white" }, - fg: { - value: { - _light: "{colors.danger.600}", - _dark: "{colors.danger.400}" - } - }, - muted: { - value: { - _light: "{colors.danger.50}", - _dark: "{colors.danger.950}" - } - }, - subtle: { - value: { - _light: "{colors.danger.100}", - _dark: "{colors.danger.900}" - } - }, - emphasized: { - value: { - _light: "{colors.danger.200}", - _dark: "{colors.danger.800}" - } - }, - focusRing: { value: "{colors.danger.500}" }, - }, - // Neutral semantic tokens - neutral: { - solid: { value: "{colors.neutral.500}" }, - contrast: { value: "white" }, - fg: { - value: { - _light: "{colors.neutral.600}", - _dark: "{colors.neutral.400}" - } - }, - muted: { - value: { - _light: "{colors.neutral.50}", - _dark: "{colors.neutral.950}" - } - }, - subtle: { - value: { - _light: "{colors.neutral.100}", - _dark: "{colors.neutral.900}" - } - }, - emphasized: { - value: { - _light: "{colors.neutral.200}", - _dark: "{colors.neutral.800}" - } - }, - focusRing: { value: "{colors.neutral.500}" }, - }, - // Atlassian-style surface semantic tokens - "color.background.default": { - value: { - _light: "{colors.surfaceDefault.50}", - _dark: "{colors.surfaceDefault.900}" - } - }, - "color.background.sunken": { - value: { - _light: "{colors.surfaceSunken.50}", - _dark: "{colors.surfaceSunken.900}" - } - }, - "color.background.raised": { - value: { - _light: "{colors.surfaceRaised.50}", - _dark: "{colors.surfaceRaised.900}" - } - }, - "color.background.overlay": { - value: { - _light: "{colors.surfaceOverlay.50}", - _dark: "{colors.surfaceOverlay.900}" - } - }, - // Legacy background tokens for compatibility - bg: { - value: { - _light: "{colors.surfaceSunken.50}", - _dark: "{colors.surfaceSunken.900}" - } - }, - "bg.panel": { - value: { - _light: "{colors.surfaceDefault.50}", - _dark: "{colors.surfaceDefault.900}" - } - }, - fg: { - value: { - _light: "{colors.neutral.900}", - _dark: "{colors.neutral.100}" - } - }, - "fg.muted": { - value: { - _light: "{colors.neutral.600}", - _dark: "{colors.neutral.400}" - } - }, - "fg.subtle": { - value: { - _light: "{colors.neutral.500}", - _dark: "{colors.neutral.500}" - } - }, - border: { - value: { - _light: "{colors.neutral.200}", - _dark: "{colors.neutral.700}" - } - }, - "border.muted": { - value: { - _light: "{colors.neutral.100}", - _dark: "{colors.neutral.800}" - } - }, - "border.subtle": { - value: { - _light: "{colors.neutral.300}", - _dark: "{colors.neutral.600}" - } - }, - }, - }, - textStyles: { - // Atlassian typography scale - clean, accessible typography - "ui.heading.xxlarge": { - value: { - fontSize: "7xl", // 72px - fontWeight: "600", // Atlassian medium weight - lineHeight: "1.125", - letterSpacing: "-0.01em", - }, - }, - "ui.heading.xlarge": { - value: { - fontSize: "6xl", // 60px - fontWeight: "600", - lineHeight: "1.133", - letterSpacing: "-0.008em", - }, - }, - "ui.heading.large": { - value: { - fontSize: "5xl", // 48px - fontWeight: "600", - lineHeight: "1.167", - letterSpacing: "-0.006em", - }, - }, - "ui.heading.medium": { - value: { - fontSize: "4xl", // 36px - fontWeight: "500", // Atlassian medium - lineHeight: "1.222", - letterSpacing: "-0.004em", - }, - }, - "ui.heading.small": { - value: { - fontSize: "3xl", // 30px - fontWeight: "500", - lineHeight: "1.267", - letterSpacing: "-0.002em", - }, - }, - "ui.heading.xsmall": { - value: { - fontSize: "2xl", // 24px - fontWeight: "500", - lineHeight: "1.333", - }, - }, - "ui.heading.xxsmall": { - value: { - fontSize: "xl", // 20px - fontWeight: "500", - lineHeight: "1.4", - }, - }, - // Atlassian body text styles - optimized for readability - "ui.body.large": { - value: { - fontSize: "lg", // 18px - fontWeight: "400", - lineHeight: "1.556", // ~28px line height - }, - }, - "ui.body.medium": { - value: { - fontSize: "md", // 16px - fontWeight: "400", - lineHeight: "1.5", // 24px line height - }, - }, - "ui.body.small": { - value: { - fontSize: "sm", // 14px - fontWeight: "400", - lineHeight: "1.429", // ~20px line height - }, - }, - // Helper text styles - "ui.helper": { - value: { - fontSize: "xs", // 12px - fontWeight: "400", - lineHeight: "1.333", // 16px line height - }, - }, - // Code text style - "ui.code": { - value: { - fontSize: "sm", // 14px - fontWeight: "400", - lineHeight: "1.571", // ~22px - fontFamily: "mono", - }, - }, - }, - layerStyles: { - // Atlassian surface styles - clean and accessible - "atlassian.card": { - value: { - bg: "color.background.raised", - borderRadius: "base", // 3px - Atlassian border radius - border: "1px solid", - borderColor: "border.muted", - boxShadow: "raised", - p: "200", // 16px - consistent spacing - }, - }, - "atlassian.panel": { - value: { - bg: "color.background.sunken", - borderRadius: "base", - border: "1px solid", - borderColor: "border.subtle", - p: "300", // 24px - }, - }, - "atlassian.header": { - value: { - bg: "color.background.default", - borderBottom: "1px solid", - borderColor: "border.muted", - px: "200", // 16px - py: "150", // 12px - minHeight: "44px", // Atlassian touch target - }, - }, - "atlassian.modal": { - value: { - bg: "color.background.overlay", - borderRadius: "lg", // Larger radius for modals - boxShadow: "overlay", - p: "300", // 24px - }, - }, - "atlassian.button.primary": { - value: { - bg: "brand.500", - color: "white", - borderRadius: "base", // 3px - px: "200", // 16px - py: "100", // 8px - minHeight: "32px", // Atlassian button height - }, - }, - "atlassian.button.secondary": { - value: { - bg: "transparent", - color: "brand.500", - border: "1px solid", - borderColor: "brand.500", - borderRadius: "base", // 3px - px: "200", // 16px - py: "100", // 8px - minHeight: "32px", // Atlassian button height - }, - }, - // Legacy styles for backward compatibility - "enterprise.card": { - value: { - bg: "color.background.raised", - borderRadius: "base", - boxShadow: "raised", - p: "300", - }, - }, - "enterprise.header": { - value: { - bg: "color.background.default", - borderBottom: "1px solid", - borderColor: "border", - px: "300", - py: "200", - }, - }, - }, - }, - globalCss: { - "html": { - colorScheme: "light dark", - }, - "body": { - bg: "bg", - color: "fg", - fontFamily: "body", - lineHeight: "1.6", - }, - "*": { - borderColor: "border", - }, - "*::placeholder": { - color: "fg.muted", - }, - }, -}) - -config.theme.components = { - ...config.theme.components, - Button: { - baseStyle: { - fontWeight: 'semibold', - borderRadius: 'base', - }, - variants: { - primary: { - bg: 'brand.500', - color: 'white', - _hover: { bg: 'brand.600' } - }, - secondary: { - bg: 'transparent', - color: 'brand.500', - border: '1px solid', - borderColor: 'brand.500', - _hover: { bg: 'brand.50' } - }, - danger: { - bg: 'danger.500', - color: 'white', - _hover: { bg: 'danger.600' } - } - } - }, - Input: { - baseStyle: { - field: { - borderColor: 'border.default', - _focus: { - borderColor: 'brand.500', - boxShadow: '0 0 0 1px brand.500' - } - } - } - } -} - -export const system = createSystem(defaultConfig, config) \ No newline at end of file diff --git a/thingconnect.pulse.client/vite.config.ts b/thingconnect.pulse.client/vite.config.ts index 179b744..5d36fec 100644 --- a/thingconnect.pulse.client/vite.config.ts +++ b/thingconnect.pulse.client/vite.config.ts @@ -50,6 +50,11 @@ export default defineConfig({ }, server: { proxy: { + '^/api': { + target, + secure: false, + changeOrigin: true, + }, '^/weatherforecast': { target, secure: false, From 8a59ed9aed568712320fac035cade6bffd750fe3 Mon Sep 17 00:00:00 2001 From: Hemanand Date: Sat, 9 Aug 2025 19:22:05 +0530 Subject: [PATCH 5/5] updated --- .claude/settings.local.json | 9 ++++++++- ThingConnect.Pulse.Server/pulse.db | Bin 20480 -> 20480 bytes .../.claude/settings.local.json | 3 ++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index aef1b40..70f86d7 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -14,7 +14,14 @@ "Bash(cp:*)", "WebFetch(domain:atlassian.design)", "WebFetch(domain:atlaskit.atlassian.com)", - "mcp__chakra-ui__get_component_props" + "mcp__chakra-ui__get_component_props", + "mcp__puppeteer__puppeteer_screenshot", + "Bash(npm install:*)", + "Bash(git add:*)", + "Bash(git commit:*)", + "Bash(mkdir:*)", + "mcp__chakra-ui__list_components", + "mcp__chakra-ui__get_component_example" ], "deny": [] } diff --git a/ThingConnect.Pulse.Server/pulse.db b/ThingConnect.Pulse.Server/pulse.db index c7e55366bdaabf56d8f6b48e3d2259baeb497470..cc104b9e4cebb35395bacc4f637eb307d511cf94 100644 GIT binary patch delta 408 zcmZozz}T>Wae_1>^F$eEM&^wPOZYjM`1u(4JNWrF3kopt*H>W#FElnz2y8{6{AEILqnA+Z;#R{uhK|g!&FN%GYiLTgG9?Pe;<#+JmV6x zpn}M#Qn$RK0`o{OgJcsobE5(iPxsPrBLgE-T>}dsQZO*FGBvj{Hq$e)FfcVXH7jFe zXArM923nn%mza_VB=SWae_1><3t%}M#hZ^OZb@>1U3sgJmH@>L5z)y0SIt%c~N8m;YAYD diff --git a/thingconnect.pulse.client/.claude/settings.local.json b/thingconnect.pulse.client/.claude/settings.local.json index da2a3a5..3ddc117 100644 --- a/thingconnect.pulse.client/.claude/settings.local.json +++ b/thingconnect.pulse.client/.claude/settings.local.json @@ -10,7 +10,8 @@ "Bash(npm run dev:*)", "Bash(npm run lint)", "Bash(npm run format:*)", - "Bash(git add:*)" + "Bash(git add:*)", + "Bash(git push:*)" ], "deny": [] }