Compare commits

...

9 Commits

Author SHA1 Message Date
hsiegeln
0bb49b83e5 feat: DataTable scrollable layout with 200+ mock exchanges
All checks were successful
Build & Publish / publish (push) Successful in 1m55s
SonarQube Analysis / sonarqube (push) Successful in 1m52s
Make Dashboard table fill viewport height with sticky header/footer
and internal scrolling. Expand mock exchange data from 15 to 200
records and Inventory showcase from 5 to 500 records.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 10:43:03 +02:00
hsiegeln
8070fdea7c fix(ci): read SONAR_HOST_URL from secrets instead of vars
All checks were successful
Build & Publish / publish (push) Successful in 1m6s
SonarQube Analysis / sonarqube (push) Successful in 2m0s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 17:38:09 +01:00
hsiegeln
7830ac5e0d fix(ci): validate SONAR_HOST_URL before running scanner
All checks were successful
Build & Publish / publish (push) Successful in 1m35s
Fail early with a clear message if the variable is missing or lacks
an http(s) scheme.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 17:28:59 +01:00
hsiegeln
fdccca5378 fix(ci): detect arm64 arch for sonar-scanner download
All checks were successful
Build & Publish / publish (push) Successful in 1m15s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 17:21:31 +01:00
hsiegeln
0d4215678a fix(ci): use native sonar-scanner CLI instead of npm wrapper
All checks were successful
Build & Publish / publish (push) Successful in 1m11s
The npm sonarqube-scanner bootstrapper was ignoring the host URL and
defaulting to sonarcloud.io. Switch to the official sonar-scanner-cli
binary which respects -D flags directly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 17:15:49 +01:00
hsiegeln
28690b2a7a fix(ci): add @vitest/coverage-v8 for SonarQube workflow
All checks were successful
Build & Publish / publish (push) Successful in 55s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 17:12:45 +01:00
hsiegeln
5eb807c572 ci: add nightly SonarQube analysis workflow
All checks were successful
Build & Publish / publish (push) Successful in 1m40s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 16:59:24 +01:00
hsiegeln
f359a2ba3d feat: add Sidebar onNavigate callback and DataTable fillHeight prop
All checks were successful
Build & Publish / publish (push) Successful in 1m3s
Sidebar: add optional onNavigate prop so consuming apps can intercept
and remap navigation paths instead of relying on internal React Router
links.

DataTable: add fillHeight prop for flex-fill layouts with scrolling
body. Make the table header sticky by default so it stays visible
during vertical scroll.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 16:28:49 +01:00
hsiegeln
384ee97643 chore: npm audit fix
All checks were successful
Build & Publish / publish (push) Successful in 1m39s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 23:33:37 +01:00
12 changed files with 931 additions and 40 deletions

View File

@@ -0,0 +1,62 @@
name: SonarQube Analysis
on:
schedule:
- cron: '0 3 * * *'
workflow_dispatch:
jobs:
sonarqube:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Install dependencies
run: npm ci
- name: Run tests with coverage
run: npx vitest run --exclude 'e2e/**' --coverage --coverage.reporter=lcov
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17
- name: Install sonar-scanner
run: |
SONAR_SCANNER_VERSION=6.2.1.4610
ARCH=$(uname -m)
if [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then
PLATFORM="linux-aarch64"
else
PLATFORM="linux-x64"
fi
curl -sSLo sonar-scanner.zip "https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${SONAR_SCANNER_VERSION}-${PLATFORM}.zip"
unzip -q sonar-scanner.zip
echo "$PWD/sonar-scanner-${SONAR_SCANNER_VERSION}-${PLATFORM}/bin" >> "$GITHUB_PATH"
- name: Run SonarQube analysis
env:
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: |
if [ -z "$SONAR_HOST_URL" ] || ! echo "$SONAR_HOST_URL" | grep -qE '^https?://'; then
echo "::error::SONAR_HOST_URL is missing or invalid (got: '$SONAR_HOST_URL'). Set it as a repo variable with full URL (e.g. https://sonar.example.com)."
exit 1
fi
sonar-scanner \
-Dsonar.host.url="$SONAR_HOST_URL" \
-Dsonar.login="$SONAR_TOKEN" \
-Dsonar.projectKey=cameleer-design-system \
-Dsonar.projectName="Cameleer Design System" \
-Dsonar.sources=src/design-system \
-Dsonar.tests=src/design-system \
-Dsonar.test.inclusions="**/*.test.tsx,**/*.test.ts" \
-Dsonar.javascript.lcov.reportPaths=coverage/lcov.info \
-Dsonar.exclusions="**/node_modules/**,**/dist/**"

727
package-lock.json generated
View File

@@ -21,6 +21,7 @@
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@vitejs/plugin-react": "^4.3.0",
"@vitest/coverage-v8": "^3.2.4",
"happy-dom": "^20.8.4",
"typescript": "^5.6.0",
"vite": "^6.0.0",
@@ -40,6 +41,20 @@
"dev": true,
"license": "MIT"
},
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@babel/code-frame": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
@@ -332,6 +347,16 @@
"node": ">=6.9.0"
}
},
"node_modules/@bcoe/v8-coverage": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz",
"integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
@@ -774,6 +799,34 @@
"node": ">=18"
}
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"dev": true,
"license": "ISC",
"dependencies": {
"string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0",
"strip-ansi": "^7.0.1",
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
"wrap-ansi": "^8.1.0",
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@istanbuljs/schema": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
"integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
@@ -932,6 +985,17 @@
"resolve": "~1.22.2"
}
},
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=14"
}
},
"node_modules/@playwright/test": {
"version": "1.58.2",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz",
@@ -1698,6 +1762,40 @@
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
}
},
"node_modules/@vitest/coverage-v8": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz",
"integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.3.0",
"@bcoe/v8-coverage": "^1.0.2",
"ast-v8-to-istanbul": "^0.3.3",
"debug": "^4.4.1",
"istanbul-lib-coverage": "^3.2.2",
"istanbul-lib-report": "^3.0.1",
"istanbul-lib-source-maps": "^5.0.6",
"istanbul-reports": "^3.1.7",
"magic-string": "^0.30.17",
"magicast": "^0.3.5",
"std-env": "^3.9.0",
"test-exclude": "^7.0.1",
"tinyrainbow": "^2.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
"@vitest/browser": "3.2.4",
"vitest": "3.2.4"
},
"peerDependenciesMeta": {
"@vitest/browser": {
"optional": true
}
}
},
"node_modules/@vitest/expect": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz",
@@ -1918,9 +2016,9 @@
"license": "MIT"
},
"node_modules/@vue/language-core/node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz",
"integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2026,7 +2124,6 @@
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=8"
}
@@ -2075,6 +2172,25 @@
"node": ">=12"
}
},
"node_modules/ast-v8-to-istanbul": {
"version": "0.3.12",
"resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.12.tgz",
"integrity": "sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.31",
"estree-walker": "^3.0.3",
"js-tokens": "^10.0.0"
}
},
"node_modules/ast-v8-to-istanbul/node_modules/js-tokens": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz",
"integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==",
"dev": true,
"license": "MIT"
},
"node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
@@ -2099,9 +2215,9 @@
}
},
"node_modules/brace-expansion": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
"integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2203,6 +2319,26 @@
"node": ">= 16"
}
},
"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,
"license": "MIT",
"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,
"license": "MIT"
},
"node_modules/compare-versions": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz",
@@ -2237,6 +2373,21 @@
"url": "https://opencollective.com/express"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/css.escape": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
@@ -2314,6 +2465,13 @@
"license": "MIT",
"peer": true
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"dev": true,
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.313",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.313.tgz",
@@ -2321,6 +2479,13 @@
"dev": true,
"license": "ISC"
},
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true,
"license": "MIT"
},
"node_modules/entities": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
@@ -2462,6 +2627,23 @@
}
}
},
"node_modules/foreground-child": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
"dev": true,
"license": "ISC",
"dependencies": {
"cross-spawn": "^7.0.6",
"signal-exit": "^4.0.1"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/fs-extra": {
"version": "11.3.4",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz",
@@ -2512,6 +2694,61 @@
"node": ">=6.9.0"
}
},
"node_modules/glob": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"dev": true,
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^1.11.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/glob/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT"
},
"node_modules/glob/node_modules/brace-expansion": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz",
"integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/glob/node_modules/minimatch": {
"version": "9.0.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
"integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.2"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@@ -2520,9 +2757,9 @@
"license": "ISC"
},
"node_modules/happy-dom": {
"version": "20.8.4",
"resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.8.4.tgz",
"integrity": "sha512-GKhjq4OQCYB4VLFBzv8mmccUadwlAusOZOI7hC1D9xDIT5HhzkJK17c4el2f6R6C715P9xB4uiMxeKUa2nHMwQ==",
"version": "20.8.9",
"resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.8.9.tgz",
"integrity": "sha512-Tz23LR9T9jOGVZm2x1EPdXqwA37G/owYMxRwU0E4miurAtFsPMQ1d2Jc2okUaSjZqAFz2oEn3FLXC5a0a+siyA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2570,6 +2807,13 @@
"he": "bin/he"
}
},
"node_modules/html-escaper": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
"dev": true,
"license": "MIT"
},
"node_modules/import-lazy": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz",
@@ -2606,6 +2850,106 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"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,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true,
"license": "ISC"
},
"node_modules/istanbul-lib-coverage": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
"integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=8"
}
},
"node_modules/istanbul-lib-report": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
"integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"istanbul-lib-coverage": "^3.0.0",
"make-dir": "^4.0.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/istanbul-lib-report/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/istanbul-lib-source-maps": {
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz",
"integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.23",
"debug": "^4.1.1",
"istanbul-lib-coverage": "^3.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/istanbul-reports": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
"integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"html-escaper": "^2.0.0",
"istanbul-lib-report": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/jackspeak": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
},
"optionalDependencies": {
"@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/jju": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz",
@@ -2745,6 +3089,47 @@
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/magicast": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz",
"integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.25.4",
"@babel/types": "^7.25.4",
"source-map-js": "^1.2.0"
}
},
"node_modules/make-dir": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
"integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
"dev": true,
"license": "MIT",
"dependencies": {
"semver": "^7.5.3"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/make-dir/node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/min-indent": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
@@ -2771,6 +3156,16 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/minipass": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
"integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/mlly": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.1.tgz",
@@ -2843,6 +3238,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
"dev": true,
"license": "BlueOak-1.0.0"
},
"node_modules/path-browserify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
@@ -2850,6 +3252,16 @@
"dev": true,
"license": "MIT"
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
@@ -2857,6 +3269,30 @@
"dev": true,
"license": "MIT"
},
"node_modules/path-scurry": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
"node": ">=16 || 14 >=14.18"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/path-scurry/node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"dev": true,
"license": "ISC"
},
"node_modules/pathe": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
@@ -2882,9 +3318,9 @@
"license": "ISC"
},
"node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3204,6 +3640,29 @@
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
"license": "MIT"
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/siginfo": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
@@ -3211,6 +3670,19 @@
"dev": true,
"license": "ISC"
},
"node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -3262,6 +3734,103 @@
"node": ">=0.6.19"
}
},
"node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/string-width-cjs": {
"name": "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,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/string-width-cjs/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,
"license": "MIT"
},
"node_modules/string-width-cjs/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,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
"integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.2.2"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/strip-ansi-cjs": {
"name": "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,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi/node_modules/ansi-regex": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/strip-indent": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
@@ -3337,6 +3906,21 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/test-exclude": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz",
"integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==",
"dev": true,
"license": "ISC",
"dependencies": {
"@istanbuljs/schema": "^0.1.2",
"glob": "^10.4.1",
"minimatch": "^10.2.2"
},
"engines": {
"node": ">=18"
}
},
"node_modules/tinybench": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
@@ -3682,6 +4266,22 @@
"node": ">=12"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/why-is-node-running": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
@@ -3699,6 +4299,107 @@
"node": ">=8"
}
},
"node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs": {
"name": "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,
"license": "MIT",
"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/wrap-ansi-cjs/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,
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs/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,
"license": "MIT"
},
"node_modules/wrap-ansi-cjs/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,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi-cjs/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,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi/node_modules/ansi-styles": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/ws": {
"version": "8.19.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",

View File

@@ -53,6 +53,7 @@
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@vitejs/plugin-react": "^4.3.0",
"@vitest/coverage-v8": "^3.2.4",
"happy-dom": "^20.8.4",
"typescript": "^5.6.0",
"vite": "^6.0.0",

View File

@@ -12,6 +12,23 @@
box-shadow: none;
}
.fillHeight {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
}
.fillHeight .scroll {
flex: 1;
min-height: 0;
overflow-y: auto;
}
.fillHeight .footer {
flex-shrink: 0;
}
.scroll {
overflow-x: auto;
}
@@ -35,6 +52,9 @@
background: var(--bg-raised);
border-bottom: 1px solid var(--border);
transition: color 0.12s;
position: sticky;
top: 0;
z-index: 1;
}
.th.sortable {

View File

@@ -24,6 +24,7 @@ export function DataTable<T extends { id: string }>({
rowAccent,
expandedContent,
flush = false,
fillHeight = false,
onSortChange,
}: DataTableProps<T>) {
const [sortKey, setSortKey] = useState<string | null>(null)
@@ -81,7 +82,7 @@ export function DataTable<T extends { id: string }>({
}))
return (
<div className={`${styles.wrapper} ${flush ? styles.flush : ''}`}>
<div className={`${styles.wrapper} ${flush ? styles.flush : ''} ${fillHeight ? styles.fillHeight : ''}`}>
<div className={styles.scroll}>
<table className={styles.table}>
<thead>

View File

@@ -20,6 +20,10 @@ export interface DataTableProps<T extends { id: string }> {
expandedContent?: (row: T) => ReactNode | null
/** Strip border, radius, and shadow so the table sits flush inside a parent container. */
flush?: boolean
/** Make the table fill remaining vertical space in a flex parent.
* The table body scrolls while the header stays sticky and the
* pagination footer stays pinned at the bottom. */
fillHeight?: boolean
/** Controlled sort: called when the user clicks a sortable column header.
* When provided, the component skips client-side sorting — the caller is
* responsible for providing `data` in the desired order. */

View File

@@ -34,6 +34,7 @@ export interface SidebarAgent {
interface SidebarProps {
apps: SidebarApp[]
className?: string
onNavigate?: (path: string) => void
}
// ── Helpers ──────────────────────────────────────────────────────────────────
@@ -246,7 +247,7 @@ function StarredGroup({
// ── Sidebar ──────────────────────────────────────────────────────────────────
export function Sidebar({ apps, className }: SidebarProps) {
export function Sidebar({ apps, className, onNavigate }: SidebarProps) {
const [search, setSearch] = useState('')
const [appsCollapsed, _setAppsCollapsed] = useState(() => localStorage.getItem('cameleer:sidebar:apps-collapsed') === 'true')
const [agentsCollapsed, _setAgentsCollapsed] = useState(() => localStorage.getItem('cameleer:sidebar:agents-collapsed') === 'true')
@@ -275,7 +276,8 @@ export function Sidebar({ apps, className }: SidebarProps) {
return next
})
}
const navigate = useNavigate()
const routerNavigate = useNavigate()
const nav = onNavigate ?? routerNavigate
const location = useLocation()
const { starredIds, isStarred, toggleStar } = useStarred()
@@ -330,7 +332,7 @@ export function Sidebar({ apps, className }: SidebarProps) {
return (
<aside className={`${styles.sidebar} ${className ?? ''}`}>
{/* Logo */}
<div className={styles.logo} onClick={() => navigate('/apps')} style={{ cursor: 'pointer' }}>
<div className={styles.logo} onClick={() => nav('/apps')} style={{ cursor: 'pointer' }}>
<img src={camelLogoUrl} alt="" aria-hidden="true" className={styles.logoImg} />
<div>
<span className={styles.brand}>cameleer</span>
@@ -381,10 +383,10 @@ export function Sidebar({ apps, className }: SidebarProps) {
</button>
<span
className={`${styles.treeSectionLabel} ${location.pathname === '/apps' ? styles.treeSectionLabelActive : ''}`}
onClick={() => navigate('/apps')}
onClick={() => nav('/apps')}
role="button"
tabIndex={0}
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') navigate('/apps') }}
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') nav('/apps') }}
>
Applications
</span>
@@ -398,6 +400,7 @@ export function Sidebar({ apps, className }: SidebarProps) {
filterQuery={search}
persistKey="cameleer:expanded:apps"
autoRevealPath={sidebarRevealPath}
onNavigate={onNavigate}
/>
)}
</div>
@@ -415,10 +418,10 @@ export function Sidebar({ apps, className }: SidebarProps) {
</button>
<span
className={`${styles.treeSectionLabel} ${location.pathname.startsWith('/agents') ? styles.treeSectionLabelActive : ''}`}
onClick={() => navigate('/agents')}
onClick={() => nav('/agents')}
role="button"
tabIndex={0}
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') navigate('/agents') }}
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') nav('/agents') }}
>
Agents
</span>
@@ -432,6 +435,7 @@ export function Sidebar({ apps, className }: SidebarProps) {
filterQuery={search}
persistKey="cameleer:expanded:agents"
autoRevealPath={sidebarRevealPath}
onNavigate={onNavigate}
/>
)}
</div>
@@ -449,10 +453,10 @@ export function Sidebar({ apps, className }: SidebarProps) {
</button>
<span
className={`${styles.treeSectionLabel} ${location.pathname === '/routes' ? styles.treeSectionLabelActive : ''}`}
onClick={() => navigate('/routes')}
onClick={() => nav('/routes')}
role="button"
tabIndex={0}
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') navigate('/routes') }}
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') nav('/routes') }}
>
Routes
</span>
@@ -466,6 +470,7 @@ export function Sidebar({ apps, className }: SidebarProps) {
filterQuery={search}
persistKey="cameleer:expanded:routes"
autoRevealPath={sidebarRevealPath}
onNavigate={onNavigate}
/>
)}
</div>
@@ -486,7 +491,7 @@ export function Sidebar({ apps, className }: SidebarProps) {
<StarredGroup
label="Applications"
items={starredApps}
onNavigate={navigate}
onNavigate={nav}
onRemove={toggleStar}
/>
)}
@@ -494,7 +499,7 @@ export function Sidebar({ apps, className }: SidebarProps) {
<StarredGroup
label="Routes"
items={starredRoutes}
onNavigate={navigate}
onNavigate={nav}
onRemove={toggleStar}
/>
)}
@@ -502,7 +507,7 @@ export function Sidebar({ apps, className }: SidebarProps) {
<StarredGroup
label="Agents"
items={starredAgents}
onNavigate={navigate}
onNavigate={nav}
onRemove={toggleStar}
/>
)}
@@ -510,7 +515,7 @@ export function Sidebar({ apps, className }: SidebarProps) {
<StarredGroup
label="Routes"
items={starredRouteStats}
onNavigate={navigate}
onNavigate={nav}
onRemove={toggleStar}
/>
)}
@@ -526,10 +531,10 @@ export function Sidebar({ apps, className }: SidebarProps) {
styles.bottomItem,
location.pathname.startsWith('/admin') ? styles.bottomItemActive : '',
].filter(Boolean).join(' ')}
onClick={() => navigate('/admin')}
onClick={() => nav('/admin')}
role="button"
tabIndex={0}
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') navigate('/admin') }}
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') nav('/admin') }}
>
<span className={styles.bottomIcon}><Settings size={14} /></span>
<div className={styles.itemInfo}>
@@ -541,10 +546,10 @@ export function Sidebar({ apps, className }: SidebarProps) {
styles.bottomItem,
location.pathname === '/api-docs' ? styles.bottomItemActive : '',
].filter(Boolean).join(' ')}
onClick={() => navigate('/api-docs')}
onClick={() => nav('/api-docs')}
role="button"
tabIndex={0}
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') navigate('/api-docs') }}
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') nav('/api-docs') }}
>
<span className={styles.bottomIcon}><FileText size={14} /></span>
<div className={styles.itemInfo}>

View File

@@ -34,6 +34,7 @@ export interface SidebarTreeProps {
filterQuery?: string
persistKey?: string // sessionStorage key to persist expand state across remounts
autoRevealPath?: string | null // when set, auto-expand the parent of the matching node
onNavigate?: (path: string) => void
}
// ── Star icons ───────────────────────────────────────────────────────────────
@@ -134,8 +135,10 @@ export function SidebarTree({
filterQuery,
persistKey,
autoRevealPath,
onNavigate,
}: SidebarTreeProps) {
const navigate = useNavigate()
const routerNavigate = useNavigate()
const navigate = onNavigate ?? routerNavigate
// Expand/collapse state — optionally persisted to sessionStorage
const [userExpandedIds, setUserExpandedIds] = useState<Set<string>>(

View File

@@ -313,3 +313,74 @@ export const exchanges: Exchange[] = [
],
},
]
// ── Generate additional exchanges to reach ~200 total ────────────────────────
const GEN_ROUTES = [
'order-intake', 'payment-validate', 'order-enrichment', 'shipment-dispatch',
'payment-process', 'shipment-track', 'inventory-sync', 'notification-send',
'audit-log', 'customer-update', 'refund-process', 'invoice-generate',
]
const GEN_ROUTE_GROUPS = [
'order-flow', 'payment-flow', 'shipment-flow', 'inventory-flow',
'notification-flow', 'audit-flow', 'customer-flow', 'billing-flow',
]
const GEN_AGENTS = ['prod-1', 'prod-2', 'prod-3', 'prod-4']
const GEN_STATUSES: Exchange['status'][] = [
'completed', 'completed', 'completed', 'completed', 'completed',
'completed', 'failed', 'warning', 'running',
]
const GEN_PROCESSORS: ProcessorData[][] = [
[
{ name: 'from(jms:queue)', type: 'consumer', durationMs: 4, status: 'ok', startMs: 0 },
{ name: 'unmarshal(json)', type: 'transform', durationMs: 6, status: 'ok', startMs: 4 },
{ name: 'validate(schema)', type: 'process', durationMs: 10, status: 'ok', startMs: 10 },
{ name: 'to(target)', type: 'to', durationMs: 15, status: 'ok', startMs: 20 },
],
[
{ name: 'from(jms:queue)', type: 'consumer', durationMs: 5, status: 'ok', startMs: 0 },
{ name: 'enrich(external-api)', type: 'enrich', durationMs: 120, status: 'slow', startMs: 5 },
{ name: 'to(target)', type: 'to', durationMs: 20, status: 'ok', startMs: 125 },
],
[
{ name: 'from(jms:queue)', type: 'consumer', durationMs: 3, status: 'ok', startMs: 0 },
{ name: 'to(external-gateway)', type: 'to', durationMs: 400, status: 'fail', startMs: 3 },
],
]
const GEN_ERRORS = [
{ msg: 'org.apache.camel.CamelExecutionException: Timeout after 5000ms', cls: 'org.apache.camel.CamelExecutionException' },
{ msg: 'java.sql.SQLTransientConnectionException: Connection pool exhausted', cls: 'java.sql.SQLTransientConnectionException' },
{ msg: 'org.apache.camel.component.http.HttpOperationFailedException: HTTP 502 Bad Gateway', cls: 'org.apache.camel.component.http.HttpOperationFailedException' },
]
const BASE_TIME = new Date('2026-03-18T08:00:00').getTime()
for (let i = 0; i < 185; i++) {
const idx = exchanges.length
const status = GEN_STATUSES[i % GEN_STATUSES.length]
const route = GEN_ROUTES[i % GEN_ROUTES.length]
const routeGroup = GEN_ROUTE_GROUPS[i % GEN_ROUTE_GROUPS.length]
const isFailed = status === 'failed'
const durationMs = isFailed
? 1000 + ((i * 37) % 4000)
: status === 'running'
? 10000 + ((i * 53) % 20000)
: 30 + ((i * 73) % 400)
const err = isFailed ? GEN_ERRORS[i % GEN_ERRORS.length] : undefined
exchanges.push({
id: `E-2026-03-18-${String(idx + 200).padStart(5, '0')}`,
orderId: `OP-${80000 + idx}`,
customer: `CUST-${10000 + ((i * 131) % 90000)}`,
route,
routeGroup,
status,
durationMs,
timestamp: new Date(BASE_TIME - i * 2 * 60 * 1000),
correlationId: `cmr-${i.toString(16).padStart(8, '0')}-gen`,
agent: GEN_AGENTS[i % GEN_AGENTS.length],
...(err ? { errorMessage: err.msg, errorClass: err.cls } : {}),
processors: GEN_PROCESSORS[i % GEN_PROCESSORS.length].map((p) => ({ ...p })),
...(i % 3 === 0 ? { correlationGroup: `${routeGroup}-${String(Math.floor(i / 3)).padStart(3, '0')}` } : {}),
})
}

View File

@@ -4,7 +4,10 @@
overflow-y: auto;
padding: 20px 24px 40px;
min-width: 0;
min-height: 0;
background: var(--bg-body);
display: flex;
flex-direction: column;
}
/* Filter bar spacing */
@@ -19,6 +22,10 @@
border-radius: var(--radius-lg);
box-shadow: var(--shadow-card);
overflow: hidden;
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
}
.tableHeader {

View File

@@ -425,6 +425,7 @@ export function Dashboard() {
selectedId={selectedId}
sortable
flush
fillHeight
rowAccent={handleRowAccent}
expandedContent={(row) =>
row.errorMessage ? (

View File

@@ -135,13 +135,27 @@ interface TableRow {
exchanges: number
}
const TABLE_DATA: TableRow[] = [
{ id: '1', name: 'order-ingest', method: 'POST', status: 'live', exchanges: 1243 },
{ id: '2', name: 'payment-validate', method: 'POST', status: 'live', exchanges: 987 },
{ id: '3', name: 'inventory-check', method: 'GET', status: 'stale', exchanges: 432 },
{ id: '4', name: 'notify-customer', method: 'POST', status: 'live', exchanges: 876 },
{ id: '5', name: 'archive-order', method: 'PUT', status: 'dead', exchanges: 54 },
const ROUTE_PREFIXES = [
'order', 'payment', 'inventory', 'customer', 'shipment', 'user', 'cart',
'refund', 'stock', 'email', 'webhook', 'cache', 'log', 'rate', 'health',
'session', 'config', 'metrics', 'batch', 'audit', 'invoice', 'product',
'catalog', 'search', 'report', 'export', 'import', 'sync', 'alert', 'ticket',
]
const ROUTE_SUFFIXES = [
'ingest', 'validate', 'check', 'notify', 'archive', 'track', 'auth',
'update', 'process', 'sync', 'dispatch', 'relay', 'invalidate', 'aggregate',
'limit', 'refresh', 'reload', 'export', 'import', 'purge',
]
const METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']
const STATUSES = ['live', 'live', 'live', 'live', 'stale', 'stale', 'dead']
const TABLE_DATA: TableRow[] = Array.from({ length: 500 }, (_, i) => ({
id: String(i + 1),
name: `${ROUTE_PREFIXES[i % ROUTE_PREFIXES.length]}-${ROUTE_SUFFIXES[(i * 7) % ROUTE_SUFFIXES.length]}`,
method: METHODS[i % METHODS.length],
status: STATUSES[i % STATUSES.length],
exchanges: ((i * 1337 + 421) % 9990) + 10,
}))
const NOW = new Date()
const minsAgo = (n: number) => new Date(NOW.getTime() - n * 60 * 1000)
@@ -468,12 +482,13 @@ export function CompositesSection() {
title="DataTable"
description="Sortable, paginated table with row click, accent rows, and page size selector."
>
<div style={{ width: '100%' }}>
<div style={{ width: '100%', height: 500, display: 'flex', flexDirection: 'column' }}>
<DataTable
columns={tableColumns}
data={TABLE_DATA}
sortable
pageSize={5}
pageSize={25}
fillHeight
/>
</div>
</DemoCard>