diff --git a/.github/workflows/ci-dev.yml b/.github/workflows/ci-dev.yml index 2116b6d..f2054a0 100644 --- a/.github/workflows/ci-dev.yml +++ b/.github/workflows/ci-dev.yml @@ -1,11 +1,20 @@ -name: CI (dev) +name: Deploy Storybook (dev) on: push: branches: [dev] +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages-dev + cancel-in-progress: true + jobs: - checks: + deploy-storybook: runs-on: ubuntu-latest steps: - name: Checkout @@ -17,14 +26,23 @@ jobs: node-version: '22.x' cache: 'npm' - - name: Install + - name: Install dependencies run: npm ci - - name: Lint - run: npm run lint + - name: Build Storybook for GitHub Pages + run: npm run build-storybook:gh-pages + + - name: Add .nojekyll + run: touch storybook-static/.nojekyll - - name: Typecheck - run: npm run lint:ts + - name: Setup Pages + uses: actions/configure-pages@v4 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: storybook-static - - name: Build - run: npm run build + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/deploy-storybook.yml b/.github/workflows/deploy-storybook.yml deleted file mode 100644 index 60dbc57..0000000 --- a/.github/workflows/deploy-storybook.yml +++ /dev/null @@ -1,116 +0,0 @@ -name: Deploy to NPM and GH Pages - -on: - push: - branches: [main] - tags: - - 'v*' - workflow_dispatch: - -permissions: - contents: write - pages: write - id-token: write - packages: write - -concurrency: - group: pages-${{ github.ref }} - cancel-in-progress: true - -jobs: - build-and-test: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: npm - - - name: Install dependencies - run: npm ci - - - name: Lint - run: npm run lint - - - name: Typecheck - run: npm run lint:ts - - - name: Build package - run: npm run build - - publish-npm: - runs-on: ubuntu-latest - needs: build-and-test - if: startsWith(github.ref, 'refs/tags/v') - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: npm - registry-url: 'https://registry.npmjs.org' - - - name: Install dependencies - run: npm ci - - - name: Build package - run: npm run build - - - name: Publish to NPM - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - run: npm publish --access public - - - name: Create GitHub Release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ github.ref_name }} - release_name: Release ${{ github.ref_name }} - draft: false - prerelease: false - - deploy-storybook: - runs-on: ubuntu-latest - needs: build-and-test - if: github.ref == 'refs/heads/main' - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: npm - - - name: Install dependencies - run: npm ci - - - name: Build Storybook with base path - run: npm run build-storybook:gh-pages - env: - STORYBOOK_BASE_HREF: /design-system-kit/ - - - name: Add .nojekyll to prevent Jekyll processing - run: touch storybook-static/.nojekyll - - - name: Setup Pages - uses: actions/configure-pages@v4 - - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: storybook-static - - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 3cfc2ee..feca9e9 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -1,22 +1,15 @@ -name: PR Checks (to main) +name: PR Checks on: pull_request: branches: [main] jobs: - pr-checks: + checks: runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: read - actions: write - id-token: write steps: - name: Checkout uses: actions/checkout@v4 - with: - fetch-depth: 0 - name: Setup Node uses: actions/setup-node@v4 @@ -24,7 +17,7 @@ jobs: node-version: '22.x' cache: 'npm' - - name: Install + - name: Install dependencies run: npm ci - name: Lint @@ -38,8 +31,3 @@ jobs: - name: Build Storybook run: npm run build-storybook - - - name: Upload Storybook artifact - uses: actions/upload-pages-artifact@v3 - with: - path: storybook-static diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..2df0de2 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,57 @@ +name: Release to NPM + +on: + push: + tags: + - 'v*' + workflow_dispatch: + +permissions: + contents: write + id-token: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '22.x' + cache: 'npm' + registry-url: 'https://registry.npmjs.org' + + - name: Install dependencies + run: npm ci + + - name: Build package + run: npm run build + + - name: Publish to NPM (Trusted Publisher) + run: npm publish --provenance --access public + + - name: Extract CHANGELOG for this version + id: changelog + run: | + VERSION=${GITHUB_REF_NAME#v} + CHANGELOG=$(awk -v ver="$VERSION" ' + /^## \[/ { if (p) exit; if ($0 ~ ver) p=1; next } + p { print } + ' CHANGELOG.md) + echo "changelog<> $GITHUB_OUTPUT + echo "$CHANGELOG" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Create GitHub Release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref_name }} + release_name: Release ${{ github.ref_name }} + body: ${{ steps.changelog.outputs.changelog }} + draft: false + prerelease: false diff --git a/.husky/pre-commit b/.husky/pre-commit deleted file mode 100644 index e69de29..0000000 diff --git a/.husky/pre-push b/.husky/pre-push deleted file mode 100755 index 32c3d66..0000000 --- a/.husky/pre-push +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env sh -. "$(dirname -- "$0")/_/husky.sh" - -echo "πŸ” Running linters before push..." - -# ЗапускаСм Prettier - -npm run format || { - echo "❌ Prettier failed. Fix errors before pushing." - exit 1 -} - -# ЗапускаСм ESLint -npm run lint || { - echo "❌ ESLint failed. Fix errors before pushing." - exit 1 -} - -# ЗапускаСм TypeScript ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΡƒ -npm run lint:ts || { - echo "❌ TypeScript check failed. Fix type errors before pushing." - exit 1 -} - -npm run build || { - echo "❌ Build failed. Fix errors before pushing." - exit 1 -} - -echo "βœ… All checks passed!" diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..549a6bc --- /dev/null +++ b/bun.lock @@ -0,0 +1,971 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "@flowscape-ui/design-system-kit", + "dependencies": { + "clsx": "^2.1.1", + "dom-to-image-more": "^3.7.1", + "tailwind-merge": "^3.3.1", + "tinycolor2": "^1.6.0", + }, + "devDependencies": { + "@eslint/js": "^9.36.0", + "@storybook/addon-docs": "^9.1.10", + "@storybook/react-vite": "^9.1.10", + "@tailwindcss/vite": "^4.1.14", + "@types/node": "^24.7.1", + "@types/react": "^19.1.16", + "@types/react-dom": "^19.1.9", + "@types/tinycolor2": "^1.4.6", + "eslint": "^9.36.0", + "eslint-plugin-react-hooks": "^6.1.1", + "gh-pages": "^6.3.0", + "globals": "^16.4.0", + "lucide-react": "^0.544.0", + "prettier": "^3.6.2", + "react": ">=18", + "react-dom": ">=18", + "react-icons": "^5.5.0", + "storybook": "^9.1.10", + "tailwindcss": "^4.1.14", + "tsup": "^8.5.0", + "typescript": "~5.9.3", + "typescript-eslint": "^8.45.0", + "vite": "^7.1.7", + }, + "peerDependencies": { + "framer-motion": ">=10", + "lucide-react": ">=0.400.0", + "react": ">=18", + "react-dom": ">=18", + "react-icons": ">=5.0.0", + }, + }, + }, + "packages": { + "@adobe/css-tools": ["@adobe/css-tools@4.4.4", "", {}, "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg=="], + + "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], + + "@babel/compat-data": ["@babel/compat-data@7.28.4", "", {}, "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw=="], + + "@babel/core": ["@babel/core@7.28.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.4", "@babel/types": "^7.28.4", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA=="], + + "@babel/generator": ["@babel/generator@7.28.3", "", { "dependencies": { "@babel/parser": "^7.28.3", "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw=="], + + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="], + + "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], + + "@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="], + + "@babel/parser": ["@babel/parser@7.28.4", "", { "dependencies": { "@babel/types": "^7.28.4" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg=="], + + "@babel/runtime": ["@babel/runtime@7.28.4", "", {}, "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="], + + "@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], + + "@babel/traverse": ["@babel/traverse@7.28.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", "@babel/types": "^7.28.4", "debug": "^4.3.1" } }, "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ=="], + + "@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.10", "", { "os": "aix", "cpu": "ppc64" }, "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.10", "", { "os": "android", "cpu": "arm" }, "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.10", "", { "os": "android", "cpu": "arm64" }, "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.10", "", { "os": "android", "cpu": "x64" }, "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.10", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.10", "", { "os": "freebsd", "cpu": "x64" }, "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.10", "", { "os": "linux", "cpu": "arm" }, "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.10", "", { "os": "linux", "cpu": "ia32" }, "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.10", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.10", "", { "os": "linux", "cpu": "s390x" }, "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.10", "", { "os": "linux", "cpu": "x64" }, "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.10", "", { "os": "none", "cpu": "arm64" }, "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.10", "", { "os": "none", "cpu": "x64" }, "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.10", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.10", "", { "os": "openbsd", "cpu": "x64" }, "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.10", "", { "os": "none", "cpu": "arm64" }, "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.10", "", { "os": "sunos", "cpu": "x64" }, "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.10", "", { "os": "win32", "cpu": "ia32" }, "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.10", "", { "os": "win32", "cpu": "x64" }, "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw=="], + + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="], + + "@eslint/config-array": ["@eslint/config-array@0.21.0", "", { "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ=="], + + "@eslint/config-helpers": ["@eslint/config-helpers@0.4.0", "", { "dependencies": { "@eslint/core": "^0.16.0" } }, "sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog=="], + + "@eslint/core": ["@eslint/core@0.16.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q=="], + + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="], + + "@eslint/js": ["@eslint/js@9.37.0", "", {}, "sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg=="], + + "@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="], + + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.0", "", { "dependencies": { "@eslint/core": "^0.16.0", "levn": "^0.4.1" } }, "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A=="], + + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], + + "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + + "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "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" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], + + "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], + + "@joshwooding/vite-plugin-react-docgen-typescript": ["@joshwooding/vite-plugin-react-docgen-typescript@0.6.1", "", { "dependencies": { "glob": "^10.0.0", "magic-string": "^0.30.0", "react-docgen-typescript": "^2.2.2" }, "peerDependencies": { "typescript": ">= 4.3.x", "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-J4BaTocTOYFkMHIra1JDWrMWpNmBl4EkplIwHEsV8aeUOtdWjwSnln9U7twjMFTAEB7mptNtSKyVi1Y2W9sDJw=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@mdx-js/react": ["@mdx-js/react@3.1.1", "", { "dependencies": { "@types/mdx": "^2.0.0" }, "peerDependencies": { "@types/react": ">=16", "react": ">=16" } }, "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw=="], + + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + + "@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" } }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.52.4", "", { "os": "android", "cpu": "arm" }, "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.52.4", "", { "os": "android", "cpu": "arm64" }, "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.52.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.52.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.52.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.52.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.52.4", "", { "os": "linux", "cpu": "arm" }, "sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.52.4", "", { "os": "linux", "cpu": "arm" }, "sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.52.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.52.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g=="], + + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.52.4", "", { "os": "linux", "cpu": "none" }, "sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.52.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.52.4", "", { "os": "linux", "cpu": "none" }, "sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.52.4", "", { "os": "linux", "cpu": "none" }, "sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.52.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.52.4", "", { "os": "linux", "cpu": "x64" }, "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.52.4", "", { "os": "linux", "cpu": "x64" }, "sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.52.4", "", { "os": "none", "cpu": "arm64" }, "sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.52.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.52.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.52.4", "", { "os": "win32", "cpu": "x64" }, "sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.52.4", "", { "os": "win32", "cpu": "x64" }, "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w=="], + + "@storybook/addon-docs": ["@storybook/addon-docs@9.1.10", "", { "dependencies": { "@mdx-js/react": "^3.0.0", "@storybook/csf-plugin": "9.1.10", "@storybook/icons": "^1.4.0", "@storybook/react-dom-shim": "9.1.10", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^9.1.10" } }, "sha512-LYK3oXy/0PgY39FhkYVd9D0bzatLGTsMhaPPwSjBOmZgK0f0yBLqaePy7urUFeHYm/rjwAaRmDJNBqUnGTVoig=="], + + "@storybook/builder-vite": ["@storybook/builder-vite@9.1.10", "", { "dependencies": { "@storybook/csf-plugin": "9.1.10", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^9.1.10", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-0ogI+toZJYaFptcFpRcRPOZ9/NrFUYhiaI09ggeEB1FF9ygHMVsobp4eaj4HjZI6V3x7cQwkd2ZmxAMQDBQuMA=="], + + "@storybook/csf-plugin": ["@storybook/csf-plugin@9.1.10", "", { "dependencies": { "unplugin": "^1.3.1" }, "peerDependencies": { "storybook": "^9.1.10" } }, "sha512-247F/JU0Naxm/RIUnQYpqXeCL0wG8UNJkZe+/GkLjdqzsyML0lb+8OwBsWFfG8zfj6fkjmRU2mF44TnNkzoQcg=="], + + "@storybook/global": ["@storybook/global@5.0.0", "", {}, "sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ=="], + + "@storybook/icons": ["@storybook/icons@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta" } }, "sha512-hcFZIjW8yQz8O8//2WTIXylm5Xsgc+lW9ISLgUk1xGmptIJQRdlhVIXCpSyLrQaaRiyhQRaVg7l3BD9S216BHw=="], + + "@storybook/react": ["@storybook/react@9.1.10", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/react-dom-shim": "9.1.10" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "storybook": "^9.1.10", "typescript": ">= 4.9.x" } }, "sha512-flG3Gn3EHZnxn92C7vrA2U4aGqpOKdf85fL43+J/2k9HF5AIyOFGlcv4LGVyKZ3LOAow/nGBVSXL9961h+ICRA=="], + + "@storybook/react-dom-shim": ["@storybook/react-dom-shim@9.1.10", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "storybook": "^9.1.10" } }, "sha512-cxy8GTj73RMJIFPrgqdnMXePGX5iFohM5pDCZ63Te5m5GtzKqsILRXtBBLO6Ouexm/ZYRVznkKiwNKX/Fu24fQ=="], + + "@storybook/react-vite": ["@storybook/react-vite@9.1.10", "", { "dependencies": { "@joshwooding/vite-plugin-react-docgen-typescript": "0.6.1", "@rollup/pluginutils": "^5.0.2", "@storybook/builder-vite": "9.1.10", "@storybook/react": "9.1.10", "find-up": "^7.0.0", "magic-string": "^0.30.0", "react-docgen": "^8.0.0", "resolve": "^1.22.8", "tsconfig-paths": "^4.2.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "storybook": "^9.1.10", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-k0wWlfoWakoHL3NZ1+38oxRH3WYyprFFX+WUb/4W8axrvpKogvdnxKCul/YB1HH5FcTagIfguamsPjKwB1ZkJg=="], + + "@tailwindcss/node": ["@tailwindcss/node@4.1.14", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.0", "lightningcss": "1.30.1", "magic-string": "^0.30.19", "source-map-js": "^1.2.1", "tailwindcss": "4.1.14" } }, "sha512-hpz+8vFk3Ic2xssIA3e01R6jkmsAhvkQdXlEbRTk6S10xDAtiQiM3FyvZVGsucefq764euO/b8WUW9ysLdThHw=="], + + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.14", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.5.1" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.14", "@tailwindcss/oxide-darwin-arm64": "4.1.14", "@tailwindcss/oxide-darwin-x64": "4.1.14", "@tailwindcss/oxide-freebsd-x64": "4.1.14", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.14", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.14", "@tailwindcss/oxide-linux-arm64-musl": "4.1.14", "@tailwindcss/oxide-linux-x64-gnu": "4.1.14", "@tailwindcss/oxide-linux-x64-musl": "4.1.14", "@tailwindcss/oxide-wasm32-wasi": "4.1.14", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.14", "@tailwindcss/oxide-win32-x64-msvc": "4.1.14" } }, "sha512-23yx+VUbBwCg2x5XWdB8+1lkPajzLmALEfMb51zZUBYaYVPDQvBSD/WYDqiVyBIo2BZFa3yw1Rpy3G2Jp+K0dw=="], + + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.14", "", { "os": "android", "cpu": "arm64" }, "sha512-a94ifZrGwMvbdeAxWoSuGcIl6/DOP5cdxagid7xJv6bwFp3oebp7y2ImYsnZBMTwjn5Ev5xESvS3FFYUGgPODQ=="], + + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.14", "", { "os": "darwin", "cpu": "arm64" }, "sha512-HkFP/CqfSh09xCnrPJA7jud7hij5ahKyWomrC3oiO2U9i0UjP17o9pJbxUN0IJ471GTQQmzwhp0DEcpbp4MZTA=="], + + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.14", "", { "os": "darwin", "cpu": "x64" }, "sha512-eVNaWmCgdLf5iv6Qd3s7JI5SEFBFRtfm6W0mphJYXgvnDEAZ5sZzqmI06bK6xo0IErDHdTA5/t7d4eTfWbWOFw=="], + + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.14", "", { "os": "freebsd", "cpu": "x64" }, "sha512-QWLoRXNikEuqtNb0dhQN6wsSVVjX6dmUFzuuiL09ZeXju25dsei2uIPl71y2Ic6QbNBsB4scwBoFnlBfabHkEw=="], + + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.14", "", { "os": "linux", "cpu": "arm" }, "sha512-VB4gjQni9+F0VCASU+L8zSIyjrLLsy03sjcR3bM0V2g4SNamo0FakZFKyUQ96ZVwGK4CaJsc9zd/obQy74o0Fw=="], + + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-qaEy0dIZ6d9vyLnmeg24yzA8XuEAD9WjpM5nIM1sUgQ/Zv7cVkharPDQcmm/t/TvXoKo/0knI3me3AGfdx6w1w=="], + + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-ISZjT44s59O8xKsPEIesiIydMG/sCXoMBCqsphDm/WcbnuWLxxb+GcvSIIA5NjUw6F8Tex7s5/LM2yDy8RqYBQ=="], + + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.14", "", { "os": "linux", "cpu": "x64" }, "sha512-02c6JhLPJj10L2caH4U0zF8Hji4dOeahmuMl23stk0MU1wfd1OraE7rOloidSF8W5JTHkFdVo/O7uRUJJnUAJg=="], + + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.14", "", { "os": "linux", "cpu": "x64" }, "sha512-TNGeLiN1XS66kQhxHG/7wMeQDOoL0S33x9BgmydbrWAb9Qw0KYdd8o1ifx4HOGDWhVmJ+Ul+JQ7lyknQFilO3Q=="], + + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.14", "", { "cpu": "none" }, "sha512-uZYAsaW/jS/IYkd6EWPJKW/NlPNSkWkBlaeVBi/WsFQNP05/bzkebUL8FH1pdsqx4f2fH/bWFcUABOM9nfiJkQ=="], + + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.14", "", { "os": "win32", "cpu": "arm64" }, "sha512-Az0RnnkcvRqsuoLH2Z4n3JfAef0wElgzHD5Aky/e+0tBUxUhIeIqFBTMNQvmMRSP15fWwmvjBxZ3Q8RhsDnxAA=="], + + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.14", "", { "os": "win32", "cpu": "x64" }, "sha512-ttblVGHgf68kEE4om1n/n44I0yGPkCPbLsqzjvybhpwa6mKKtgFfAzy6btc3HRmuW7nHe0OOrSeNP9sQmmH9XA=="], + + "@tailwindcss/vite": ["@tailwindcss/vite@4.1.14", "", { "dependencies": { "@tailwindcss/node": "4.1.14", "@tailwindcss/oxide": "4.1.14", "tailwindcss": "4.1.14" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-BoFUoU0XqgCUS1UXWhmDJroKKhNXeDzD7/XwabjkDIAbMnc4ULn5e2FuEuBbhZ6ENZoSYzKlzvZ44Yr6EUDUSA=="], + + "@testing-library/dom": ["@testing-library/dom@10.4.1", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "picocolors": "1.1.1", "pretty-format": "^27.0.2" } }, "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg=="], + + "@testing-library/jest-dom": ["@testing-library/jest-dom@6.9.1", "", { "dependencies": { "@adobe/css-tools": "^4.4.0", "aria-query": "^5.0.0", "css.escape": "^1.5.1", "dom-accessibility-api": "^0.6.3", "picocolors": "^1.1.1", "redent": "^3.0.0" } }, "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA=="], + + "@testing-library/user-event": ["@testing-library/user-event@14.6.1", "", { "peerDependencies": { "@testing-library/dom": ">=7.21.4" } }, "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw=="], + + "@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="], + + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], + + "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], + + "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], + + "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], + + "@types/chai": ["@types/chai@5.2.2", "", { "dependencies": { "@types/deep-eql": "*" } }, "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg=="], + + "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], + + "@types/doctrine": ["@types/doctrine@0.0.9", "", {}, "sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + + "@types/mdx": ["@types/mdx@2.0.13", "", {}, "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw=="], + + "@types/node": ["@types/node@24.7.1", "", { "dependencies": { "undici-types": "~7.14.0" } }, "sha512-CmyhGZanP88uuC5GpWU9q+fI61j2SkhO3UGMUdfYRE6Bcy0ccyzn1Rqj9YAB/ZY4kOXmNf0ocah5GtphmLMP6Q=="], + + "@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="], + + "@types/react-dom": ["@types/react-dom@19.2.1", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-/EEvYBdT3BflCWvTMO7YkYBHVE9Ci6XdqZciZANQgKpaiDRGOLIlRo91jbTNRQjgPFWVaRxcYc0luVNFitz57A=="], + + "@types/resolve": ["@types/resolve@1.20.6", "", {}, "sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ=="], + + "@types/tinycolor2": ["@types/tinycolor2@1.4.6", "", {}, "sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw=="], + + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.46.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.46.0", "@typescript-eslint/type-utils": "8.46.0", "@typescript-eslint/utils": "8.46.0", "@typescript-eslint/visitor-keys": "8.46.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.46.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-hA8gxBq4ukonVXPy0OKhiaUh/68D0E88GSmtC1iAEnGaieuDi38LhS7jdCHRLi6ErJBNDGCzvh5EnzdPwUc0DA=="], + + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.46.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.46.0", "@typescript-eslint/types": "8.46.0", "@typescript-eslint/typescript-estree": "8.46.0", "@typescript-eslint/visitor-keys": "8.46.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-n1H6IcDhmmUEG7TNVSspGmiHHutt7iVKtZwRppD7e04wha5MrkV1h3pti9xQLcCMt6YWsncpoT0HMjkH1FNwWQ=="], + + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.46.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.46.0", "@typescript-eslint/types": "^8.46.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-OEhec0mH+U5Je2NZOeK1AbVCdm0ChyapAyTeXVIYTPXDJ3F07+cu87PPXcGoYqZ7M9YJVvFnfpGg1UmCIqM+QQ=="], + + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.46.0", "", { "dependencies": { "@typescript-eslint/types": "8.46.0", "@typescript-eslint/visitor-keys": "8.46.0" } }, "sha512-lWETPa9XGcBes4jqAMYD9fW0j4n6hrPtTJwWDmtqgFO/4HF4jmdH/Q6wggTw5qIT5TXjKzbt7GsZUBnWoO3dqw=="], + + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.46.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-WrYXKGAHY836/N7zoK/kzi6p8tXFhasHh8ocFL9VZSAkvH956gfeRfcnhs3xzRy8qQ/dq3q44v1jvQieMFg2cw=="], + + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.46.0", "", { "dependencies": { "@typescript-eslint/types": "8.46.0", "@typescript-eslint/typescript-estree": "8.46.0", "@typescript-eslint/utils": "8.46.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-hy+lvYV1lZpVs2jRaEYvgCblZxUoJiPyCemwbQZ+NGulWkQRy0HRPYAoef/CNSzaLt+MLvMptZsHXHlkEilaeg=="], + + "@typescript-eslint/types": ["@typescript-eslint/types@8.46.0", "", {}, "sha512-bHGGJyVjSE4dJJIO5yyEWt/cHyNwga/zXGJbJJ8TiO01aVREK6gCTu3L+5wrkb1FbDkQ+TKjMNe9R/QQQP9+rA=="], + + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.46.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.46.0", "@typescript-eslint/tsconfig-utils": "8.46.0", "@typescript-eslint/types": "8.46.0", "@typescript-eslint/visitor-keys": "8.46.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ekDCUfVpAKWJbRfm8T1YRrCot1KFxZn21oV76v5Fj4tr7ELyk84OS+ouvYdcDAwZL89WpEkEj2DKQ+qg//+ucg=="], + + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.46.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.46.0", "@typescript-eslint/types": "8.46.0", "@typescript-eslint/typescript-estree": "8.46.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-nD6yGWPj1xiOm4Gk0k6hLSZz2XkNXhuYmyIrOWcHoPuAhjT9i5bAG+xbWPgFeNR8HPHHtpNKdYUXJl/D3x7f5g=="], + + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.46.0", "", { "dependencies": { "@typescript-eslint/types": "8.46.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-FrvMpAK+hTbFy7vH5j1+tMYHMSKLE6RzluFJlkFNKD0p9YsUT75JlBSmr5so3QRzvMwU5/bIEdeNrxm8du8l3Q=="], + + "@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], + + "@vitest/mocker": ["@vitest/mocker@3.2.4", "", { "dependencies": { "@vitest/spy": "3.2.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw"] }, "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], + + "@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="], + + "@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], + + "acorn": ["acorn@8.15.0", "", { "bin": "bin/acorn" }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "aria-query": ["aria-query@5.3.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A=="], + + "array-union": ["array-union@2.1.0", "", {}, "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw=="], + + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + + "ast-types": ["ast-types@0.16.1", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="], + + "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.8.13", "", { "bin": "dist/cli.js" }, "sha512-7s16KR8io8nIBWQyCYhmFhd+ebIzb9VKTzki+wOJXHTxTnV6+mFGH3+Jwn1zoKaY9/H9T/0BcKCZnzXljPnpSQ=="], + + "better-opn": ["better-opn@3.0.2", "", { "dependencies": { "open": "^8.0.4" } }, "sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ=="], + + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "browserslist": ["browserslist@4.26.3", "", { "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", "electron-to-chromium": "^1.5.227", "node-releases": "^2.0.21", "update-browserslist-db": "^1.1.3" }, "bin": "cli.js" }, "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w=="], + + "bundle-require": ["bundle-require@5.1.0", "", { "dependencies": { "load-tsconfig": "^0.2.3" }, "peerDependencies": { "esbuild": ">=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="], + + "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001749", "", {}, "sha512-0rw2fJOmLfnzCRbkm8EyHL8SvI2Apu5UbnQuTsJ0ClgrH8hcwFooJ1s5R0EP8o8aVrFu8++ae29Kt9/gZAZp/Q=="], + + "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "check-error": ["check-error@2.1.1", "", {}, "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw=="], + + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + + "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], + + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "commander": ["commander@13.1.0", "", {}, "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw=="], + + "commondir": ["commondir@1.0.1", "", {}, "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], + + "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "css.escape": ["css.escape@1.5.1", "", {}, "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg=="], + + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], + + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + + "define-lazy-prop": ["define-lazy-prop@2.0.0", "", {}, "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og=="], + + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="], + + "doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="], + + "dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="], + + "dom-to-image-more": ["dom-to-image-more@3.7.1", "", {}, "sha512-XajsuvCLnPVuYFsFOuGuTvFogMTZEQmYLlySPoDRNZWUk953CIoUTS2rqXpwyFjqDgi2fC+Wk9GSlL1UbAvfbw=="], + + "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.233", "", {}, "sha512-iUdTQSf7EFXsDdQsp8MwJz5SVk4APEFqXU/S47OtQ0YLqacSwPXdZ5vRlMX3neb07Cy2vgioNuRnWUXFwuslkg=="], + + "email-addresses": ["email-addresses@5.0.0", "", {}, "sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw=="], + + "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + + "enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="], + + "esbuild": ["esbuild@0.25.10", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.10", "@esbuild/android-arm": "0.25.10", "@esbuild/android-arm64": "0.25.10", "@esbuild/android-x64": "0.25.10", "@esbuild/darwin-arm64": "0.25.10", "@esbuild/darwin-x64": "0.25.10", "@esbuild/freebsd-arm64": "0.25.10", "@esbuild/freebsd-x64": "0.25.10", "@esbuild/linux-arm": "0.25.10", "@esbuild/linux-arm64": "0.25.10", "@esbuild/linux-ia32": "0.25.10", "@esbuild/linux-loong64": "0.25.10", "@esbuild/linux-mips64el": "0.25.10", "@esbuild/linux-ppc64": "0.25.10", "@esbuild/linux-riscv64": "0.25.10", "@esbuild/linux-s390x": "0.25.10", "@esbuild/linux-x64": "0.25.10", "@esbuild/netbsd-arm64": "0.25.10", "@esbuild/netbsd-x64": "0.25.10", "@esbuild/openbsd-arm64": "0.25.10", "@esbuild/openbsd-x64": "0.25.10", "@esbuild/openharmony-arm64": "0.25.10", "@esbuild/sunos-x64": "0.25.10", "@esbuild/win32-arm64": "0.25.10", "@esbuild/win32-ia32": "0.25.10", "@esbuild/win32-x64": "0.25.10" }, "bin": "bin/esbuild" }, "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ=="], + + "esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "eslint": ["eslint@9.37.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", "@eslint/config-helpers": "^0.4.0", "@eslint/core": "^0.16.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.37.0", "@eslint/plugin-kit": "^0.4.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "bin": "bin/eslint.js" }, "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig=="], + + "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@6.1.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "zod": "^3.22.4 || ^4.0.0", "zod-validation-error": "^3.0.3 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-St9EKZzOAQF704nt2oJvAKZHjhrpg25ClQoaAlHmPZuajFldVLqRDW4VBNAS01NzeiQF0m0qhG1ZA807K6aVaQ=="], + + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], + + "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], + + "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], + + "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + + "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" } }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + + "filename-reserved-regex": ["filename-reserved-regex@2.0.0", "", {}, "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ=="], + + "filenamify": ["filenamify@4.3.0", "", { "dependencies": { "filename-reserved-regex": "^2.0.0", "strip-outer": "^1.0.1", "trim-repeated": "^1.0.0" } }, "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "find-cache-dir": ["find-cache-dir@3.3.2", "", { "dependencies": { "commondir": "^1.0.1", "make-dir": "^3.0.2", "pkg-dir": "^4.1.0" } }, "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig=="], + + "find-up": ["find-up@7.0.0", "", { "dependencies": { "locate-path": "^7.2.0", "path-exists": "^5.0.0", "unicorn-magic": "^0.1.0" } }, "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g=="], + + "fix-dts-default-cjs-exports": ["fix-dts-default-cjs-exports@1.0.1", "", { "dependencies": { "magic-string": "^0.30.17", "mlly": "^1.7.4", "rollup": "^4.34.8" } }, "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg=="], + + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], + + "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], + + "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + + "framer-motion": ["framer-motion@12.23.22", "", { "dependencies": { "motion-dom": "^12.23.21", "motion-utils": "^12.23.6", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid"] }, "sha512-ZgGvdxXCw55ZYvhoZChTlG6pUuehecgvEAJz0BHoC5pQKW1EC5xf1Mul1ej5+ai+pVY0pylyFfdl45qnM1/GsA=="], + + "fs-extra": ["fs-extra@11.3.2", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + + "gh-pages": ["gh-pages@6.3.0", "", { "dependencies": { "async": "^3.2.4", "commander": "^13.0.0", "email-addresses": "^5.0.0", "filenamify": "^4.3.0", "find-cache-dir": "^3.3.1", "fs-extra": "^11.1.1", "globby": "^11.1.0" }, "bin": { "gh-pages": "bin/gh-pages.js", "gh-pages-clean": "bin/gh-pages-clean.js" } }, "sha512-Ot5lU6jK0Eb+sszG8pciXdjMXdBJ5wODvgjR+imihTqsUWF2K6dJ9HST55lgqcs8wWcw6o6wAsUzfcYRhJPXbA=="], + + "glob": ["glob@10.4.5", "", { "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": "dist/esm/bin.mjs" }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], + + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "globals": ["globals@16.4.0", "", {}, "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw=="], + + "globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="], + + "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], + + "is-docker": ["is-docker@2.2.1", "", { "bin": "cli.js" }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], + + "jiti": ["jiti@2.6.1", "", { "bin": "lib/jiti-cli.mjs" }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + + "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": "bin/js-yaml.js" }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": "bin/jsesc" }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + + "json5": ["json5@2.2.3", "", { "bin": "lib/cli.js" }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + + "lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.1", "", { "os": "linux", "cpu": "arm" }, "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.1", "", { "os": "win32", "cpu": "x64" }, "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg=="], + + "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], + + "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], + + "load-tsconfig": ["load-tsconfig@0.2.5", "", {}, "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg=="], + + "locate-path": ["locate-path@7.2.0", "", { "dependencies": { "p-locate": "^6.0.0" } }, "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA=="], + + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + + "lodash.sortby": ["lodash.sortby@4.7.0", "", {}, "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA=="], + + "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="], + + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "lucide-react": ["lucide-react@0.544.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-t5tS44bqd825zAW45UQxpG2CvcC4urOwn2TrwSH8u+MjeE+1NnWl6QqeQ/6NdjMqdOygyiT9p3Ev0p1NJykxjw=="], + + "lz-string": ["lz-string@1.5.0", "", { "bin": "bin/bin.js" }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="], + + "magic-string": ["magic-string@0.30.19", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw=="], + + "make-dir": ["make-dir@3.1.0", "", { "dependencies": { "semver": "^6.0.0" } }, "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw=="], + + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "min-indent": ["min-indent@1.0.1", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="], + + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + + "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + + "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="], + + "mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="], + + "motion-dom": ["motion-dom@12.23.21", "", { "dependencies": { "motion-utils": "^12.23.6" } }, "sha512-5xDXx/AbhrfgsQmSE7YESMn4Dpo6x5/DTZ4Iyy4xqDvVHWvFVoV+V2Ri2S/ksx+D40wrZ7gPYiMWshkdoqNgNQ=="], + + "motion-utils": ["motion-utils@12.23.6", "", {}, "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": "bin/nanoid.cjs" }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + + "node-releases": ["node-releases@2.0.23", "", {}, "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg=="], + + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + + "open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="], + + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + + "p-limit": ["p-limit@4.0.0", "", { "dependencies": { "yocto-queue": "^1.0.0" } }, "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ=="], + + "p-locate": ["p-locate@6.0.0", "", { "dependencies": { "p-limit": "^4.0.0" } }, "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw=="], + + "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="], + + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "path-exists": ["path-exists@5.0.0", "", {}, "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], + + "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + + "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + + "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], + + "pkg-dir": ["pkg-dir@4.2.0", "", { "dependencies": { "find-up": "^4.0.0" } }, "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ=="], + + "pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], + + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + + "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="], + + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + + "prettier": ["prettier@3.6.2", "", { "bin": "bin/prettier.cjs" }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="], + + "pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="], + + "react-docgen": ["react-docgen@8.0.1", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.2", "@types/babel__core": "^7.20.5", "@types/babel__traverse": "^7.20.7", "@types/doctrine": "^0.0.9", "@types/resolve": "^1.20.2", "doctrine": "^3.0.0", "resolve": "^1.22.1", "strip-indent": "^4.0.0" } }, "sha512-kQKsqPLplY3Hx4jGnM3jpQcG3FQDt7ySz32uTHt3C9HAe45kNXG+3o16Eqn3Fw1GtMfHoN3b4J/z2e6cZJCmqQ=="], + + "react-docgen-typescript": ["react-docgen-typescript@2.4.0", "", { "peerDependencies": { "typescript": ">= 4.3.x" } }, "sha512-ZtAp5XTO5HRzQctjPU0ybY0RRCQO19X/8fxn3w7y2VVTUbGHDKULPTL4ky3vB05euSgG5NpALhEhDPvQ56wvXg=="], + + "react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="], + + "react-icons": ["react-icons@5.5.0", "", { "peerDependencies": { "react": "*" } }, "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw=="], + + "react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="], + + "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + + "recast": ["recast@0.23.11", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA=="], + + "redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="], + + "resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": "bin/resolve" }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="], + + "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], + + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + + "rollup": ["rollup@4.52.4", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.52.4", "@rollup/rollup-android-arm64": "4.52.4", "@rollup/rollup-darwin-arm64": "4.52.4", "@rollup/rollup-darwin-x64": "4.52.4", "@rollup/rollup-freebsd-arm64": "4.52.4", "@rollup/rollup-freebsd-x64": "4.52.4", "@rollup/rollup-linux-arm-gnueabihf": "4.52.4", "@rollup/rollup-linux-arm-musleabihf": "4.52.4", "@rollup/rollup-linux-arm64-gnu": "4.52.4", "@rollup/rollup-linux-arm64-musl": "4.52.4", "@rollup/rollup-linux-loong64-gnu": "4.52.4", "@rollup/rollup-linux-ppc64-gnu": "4.52.4", "@rollup/rollup-linux-riscv64-gnu": "4.52.4", "@rollup/rollup-linux-riscv64-musl": "4.52.4", "@rollup/rollup-linux-s390x-gnu": "4.52.4", "@rollup/rollup-linux-x64-gnu": "4.52.4", "@rollup/rollup-linux-x64-musl": "4.52.4", "@rollup/rollup-openharmony-arm64": "4.52.4", "@rollup/rollup-win32-arm64-msvc": "4.52.4", "@rollup/rollup-win32-ia32-msvc": "4.52.4", "@rollup/rollup-win32-x64-gnu": "4.52.4", "@rollup/rollup-win32-x64-msvc": "4.52.4", "fsevents": "~2.3.2" }, "bin": "dist/bin/rollup" }, "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ=="], + + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], + + "semver": ["semver@7.7.3", "", { "bin": "bin/semver.js" }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], + + "source-map": ["source-map@0.8.0-beta.0", "", { "dependencies": { "whatwg-url": "^7.0.0" } }, "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "storybook": ["storybook@9.1.10", "", { "dependencies": { "@storybook/global": "^5.0.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/mocker": "3.2.4", "@vitest/spy": "3.2.4", "better-opn": "^3.0.2", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0", "esbuild-register": "^3.5.0", "recast": "^0.23.5", "semver": "^7.6.2", "ws": "^8.18.0" }, "peerDependencies": { "prettier": "^2 || ^3" }, "bin": "bin/index.cjs" }, "sha512-4+U7gF9hMpGilQmdVJwQaVZZEkD7XwC4ZDmBa51mobaPYelELEMoMfNM2hLyvB2x12gk1IJui1DnwOE4t+MXhw=="], + + "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + + "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="], + + "strip-indent": ["strip-indent@4.1.0", "", {}, "sha512-OA95x+JPmL7kc7zCu+e+TeYxEiaIyndRx0OrBcK2QPPH09oAndr2ALvymxWA+Lx1PYYvFUm4O63pRkdJAaW96w=="], + + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + + "strip-outer": ["strip-outer@1.0.1", "", { "dependencies": { "escape-string-regexp": "^1.0.2" } }, "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg=="], + + "sucrase": ["sucrase@3.35.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], + + "tailwind-merge": ["tailwind-merge@3.3.1", "", {}, "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g=="], + + "tailwindcss": ["tailwindcss@4.1.14", "", {}, "sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA=="], + + "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], + + "tar": ["tar@7.5.1", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g=="], + + "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], + + "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], + + "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], + + "tinycolor2": ["tinycolor2@1.6.0", "", {}, "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="], + + "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], + + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + + "tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], + + "tinyspy": ["tinyspy@4.0.4", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "tr46": ["tr46@1.0.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA=="], + + "tree-kill": ["tree-kill@1.2.2", "", { "bin": "cli.js" }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], + + "trim-repeated": ["trim-repeated@1.0.0", "", { "dependencies": { "escape-string-regexp": "^1.0.2" } }, "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg=="], + + "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="], + + "ts-dedent": ["ts-dedent@2.2.0", "", {}, "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ=="], + + "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], + + "tsconfig-paths": ["tsconfig-paths@4.2.0", "", { "dependencies": { "json5": "^2.2.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "tsup": ["tsup@8.5.0", "", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.25.0", "fix-dts-default-cjs-exports": "^1.0.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "0.8.0-beta.0", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-VmBp77lWNQq6PfuMqCHD3xWl22vEoWsKajkF8t+yMBawlUS8JzEI+vOVMeuNZIuMML8qXRizFKi9oD5glKQVcQ=="], + + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "typescript-eslint": ["typescript-eslint@8.46.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.46.0", "@typescript-eslint/parser": "8.46.0", "@typescript-eslint/typescript-estree": "8.46.0", "@typescript-eslint/utils": "8.46.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-6+ZrB6y2bT2DX3K+Qd9vn7OFOJR+xSLDj+Aw/N3zBwUt27uTw2sw2TE2+UcY1RiyBZkaGbTkVg9SSdPNUG6aUw=="], + + "ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="], + + "undici-types": ["undici-types@7.14.0", "", {}, "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA=="], + + "unicorn-magic": ["unicorn-magic@0.1.0", "", {}, "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ=="], + + "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], + + "unplugin": ["unplugin@1.16.1", "", { "dependencies": { "acorn": "^8.14.0", "webpack-virtual-modules": "^0.6.2" } }, "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w=="], + + "update-browserslist-db": ["update-browserslist-db@1.1.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": "cli.js" }, "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw=="], + + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + + "vite": ["vite@7.1.9", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": "bin/vite.js" }, "sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg=="], + + "webidl-conversions": ["webidl-conversions@4.0.2", "", {}, "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="], + + "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], + + "whatwg-url": ["whatwg-url@7.1.0", "", { "dependencies": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", "webidl-conversions": "^4.0.2" } }, "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + + "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + + "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], + + "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], + + "yocto-queue": ["yocto-queue@1.2.1", "", {}, "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg=="], + + "zod": ["zod@4.1.12", "", {}, "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ=="], + + "zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="], + + "@babel/core/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], + + "@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="], + + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "@vitest/mocker/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "eslint/find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "make-dir/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "pkg-dir/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], + + "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], + + "recast/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "redent/strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="], + + "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "strip-outer/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="], + + "sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], + + "trim-repeated/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="], + + "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "eslint/find-up/locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + + "eslint/find-up/path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "pkg-dir/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], + + "pkg-dir/find-up/path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "eslint/find-up/locate-path/p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + + "pkg-dir/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], + + "eslint/find-up/locate-path/p-locate/p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "pkg-dir/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], + + "eslint/find-up/locate-path/p-locate/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + } +} diff --git a/package-lock.json b/package-lock.json index 350ed63..b4c8dc2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@flowscape-ui/design-system-kit", - "version": "1.0.0", + "version": "1.3.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@flowscape-ui/design-system-kit", - "version": "1.0.0", + "version": "1.3.2", "license": "MIT", "dependencies": { "clsx": "^2.1.1", @@ -27,7 +27,6 @@ "eslint-plugin-react-hooks": "^6.1.1", "gh-pages": "^6.3.0", "globals": "^16.4.0", - "husky": "^9.1.7", "lucide-react": "^0.544.0", "prettier": "^3.6.2", "react": ">=18", @@ -3903,22 +3902,6 @@ "node": ">= 0.4" } }, - "node_modules/husky": { - "version": "9.1.7", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", - "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", - "dev": true, - "license": "MIT", - "bin": { - "husky": "bin.js" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/typicode" - } - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", diff --git a/package.json b/package.json index d703825..dc9397f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@flowscape-ui/design-system-kit", - "version": "1.3.0", + "version": "1.3.2", "description": "Professional React UI components for color management and design systems. Features ColorPicker with gradients, InputColorPicker, InputHex with alpha channel, InputNumberSelect, and more. Built with TypeScript.", "type": "module", "main": "./dist/index.cjs", @@ -47,6 +47,11 @@ "types": "./dist/input-hex-with-preview/index.d.ts", "import": "./dist/input-hex-with-preview/index.js", "require": "./dist/input-hex-with-preview/index.cjs" + }, + "./input-image-select": { + "types": "./dist/input-image-select/index.d.ts", + "import": "./dist/input-image-select/index.js", + "require": "./dist/input-image-select/index.cjs" } }, "files": [ @@ -95,19 +100,8 @@ "lint": "eslint . --ext .ts,.tsx --max-warnings 0", "lint:ts": "tsc --noEmit", "format": "prettier --write \"src/**/*.{ts,tsx,json,md}\"", - "format:check": "prettier --check \"src/**/*.{ts,tsx,json,md}\"", - "prepublishOnly": "npm run build", - "storybook": "storybook dev -p 6006", "build-storybook": "storybook build", - "build-storybook:gh-pages": "STORYBOOK_BASE_HREF=/design-system-kit/ storybook build", - "deploy:storybook": "npm run build-storybook:gh-pages && touch storybook-static/.nojekyll && gh-pages -d storybook-static", - "prepare": "husky", - "version:patch": "npm version patch -m 'chore(release): %s'", - "version:minor": "npm version minor -m 'chore(release): %s'", - "version:major": "npm version major -m 'chore(release): %s'", - "release:patch": "npm run version:patch && git push --follow-tags", - "release:minor": "npm run version:minor && git push --follow-tags", - "release:major": "npm run version:major && git push --follow-tags" + "build-storybook:gh-pages": "STORYBOOK_BASE_HREF=/design-system-kit/ storybook build" }, "dependencies": { "clsx": "^2.1.1", @@ -128,7 +122,6 @@ "eslint-plugin-react-hooks": "^6.1.1", "gh-pages": "^6.3.0", "globals": "^16.4.0", - "husky": "^9.1.7", "lucide-react": "^0.544.0", "prettier": "^3.6.2", "react": ">=18", diff --git a/src/color-picker/components/Picker.tsx b/src/color-picker/components/Picker.tsx index 3d33f36..8610515 100644 --- a/src/color-picker/components/Picker.tsx +++ b/src/color-picker/components/Picker.tsx @@ -31,6 +31,7 @@ const Picker = ({ return (
diff --git a/src/color-picker/components/opacity.tsx b/src/color-picker/components/opacity.tsx index 7ecbabd..494422d 100644 --- a/src/color-picker/components/opacity.tsx +++ b/src/color-picker/components/opacity.tsx @@ -36,7 +36,7 @@ const Opacity = () => { x = Math.max(0, Math.min(x, boundingBox.current.width)) const newO = x / boundingBox.current.width - const newColor = `rgba(${r}, ${g}, ${b}, ${newO})` + const newColor = `rgba(${r}, ${g}, ${b}, ${newO.toFixed(2)})` handleChange(newColor) const handleLeft = Math.max(0, Math.min(x - 9, squareWidth - 18)) diff --git a/src/index.ts b/src/index.ts index 9350a5d..2887f2c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ export * from './input' export * from './input-color-picker' export * from './input-hex' export * from './input-hex-with-preview' +export * from './input-image-select' export * from './input-number-select' export * from './shared' diff --git a/src/input-color-picker/hooks/index.ts b/src/input-color-picker/hooks/index.ts index 906396d..24903e9 100644 --- a/src/input-color-picker/hooks/index.ts +++ b/src/input-color-picker/hooks/index.ts @@ -1,2 +1 @@ export * from './use-color-picker-state' -export * from './use-draggable' diff --git a/src/input-color-picker/hooks/use-color-picker-state.ts b/src/input-color-picker/hooks/use-color-picker-state.ts index 01f3b3b..50a9cbb 100644 --- a/src/input-color-picker/hooks/use-color-picker-state.ts +++ b/src/input-color-picker/hooks/use-color-picker-state.ts @@ -4,7 +4,7 @@ import { hexToRgb, parseColor, rgbToHex, -} from '../utils/color-utils' +} from '../../shared/utils/color-utils' interface UseColorPickerStateProps { value: string diff --git a/src/input-color-picker/index.tsx b/src/input-color-picker/index.tsx index 3aa6ce5..75fcf7c 100644 --- a/src/input-color-picker/index.tsx +++ b/src/input-color-picker/index.tsx @@ -1,4 +1,2 @@ export { InputColorPicker } from './input-color-picker' export type { InputColorPickerProps } from './types' -export * from './utils' -export * from './hooks' diff --git a/src/input-color-picker/input-color-picker.tsx b/src/input-color-picker/input-color-picker.tsx index 2fa71d9..6a5a63c 100644 --- a/src/input-color-picker/input-color-picker.tsx +++ b/src/input-color-picker/input-color-picker.tsx @@ -1,105 +1,17 @@ -import { Eye, EyeOff, Trash2, X } from 'lucide-react' import { useRef, useState } from 'react' import { ColorPicker } from '../color-picker' import { InputHex } from '../input-hex' +import { ActionButtons } from '../shared/components/action-buttons/action-buttons' +import { ColorPreview } from '../shared/components/color-preview/color-preview' +import { useImageActions } from '../shared/components/image-input' +import { InputSelectModal } from '../shared/components/input-select-modal/input-select-modal' +import { TextButton } from '../shared/components/text-button/text-button' +import { INPUT_THEME_CLASSES } from '../shared/constants/input-theme' +import { useDraggable } from '../shared/hooks/use-draggable' import { cn } from '../shared/utils/cn' +import { createDisplayColor, opacityToHex } from '../shared/utils/color-utils' import { useColorPickerState } from './hooks/use-color-picker-state' -import { useDraggable } from './hooks/use-draggable' import type { InputColorPickerProps } from './types' -import { createDisplayColor, opacityToHex } from './utils/color-utils' - -const THEME_CLASSES = { - light: { - container: 'bg-white border-gray-300 focus-within:ring-blue-500', - text: 'text-gray-900', - textMuted: 'text-gray-600', - input: 'text-gray-900', - icon: 'text-gray-600', - dragArea: 'bg-gray-100 hover:bg-gray-200', - preview: 'border-gray-300', - divider: 'bg-gray-300', - }, - dark: { - container: - 'dark:bg-gray-800 dark:border-gray-600 dark:focus-within:ring-blue-400', - text: 'dark:text-gray-200', - textMuted: 'dark:text-gray-400', - input: 'dark:text-gray-100', - icon: 'dark:text-gray-300', - dragArea: 'dark:bg-gray-700 dark:hover:bg-gray-600', - preview: 'dark:border-gray-600', - divider: 'dark:bg-gray-600', - }, -} as const - -interface OpacityDragControlProps { - opacity: number - onChange: (value: number) => void -} - -const OpacityDragControl = ({ opacity, onChange }: OpacityDragControlProps) => { - const [isDragging, setIsDragging] = useState(false) - - const handlePointerDown = (e: React.PointerEvent) => { - e.preventDefault() - const target = e.currentTarget - target.setPointerCapture(e.pointerId) - - const styleElement = document.createElement('style') - styleElement.id = 'opacity-dragging-cursor' - styleElement.innerHTML = ` - body, body * { - cursor: ew-resize !important; - user-select: none !important; - } - ` - document.head.appendChild(styleElement) - - const startX = e.clientX - const startOpacity = opacity - setIsDragging(true) - - const handlePointerMove = (event: PointerEvent) => { - const deltaX = event.clientX - startX - const step = 0.5 - let newOpacity = Math.round(startOpacity + deltaX * step) - newOpacity = Math.max(0, Math.min(100, newOpacity)) - onChange(newOpacity) - } - - const handlePointerUp = (event: PointerEvent) => { - target.releasePointerCapture(event.pointerId) - setIsDragging(false) - - const styleToRemove = document.getElementById('opacity-dragging-cursor') - if (styleToRemove) { - styleToRemove.remove() - } - - target.removeEventListener('pointermove', handlePointerMove) - document.removeEventListener('pointerup', handlePointerUp) - } - - target.addEventListener('pointermove', handlePointerMove) - document.addEventListener('pointerup', handlePointerUp) - } - - return ( - - ) -} export const InputColorPicker = ({ title = 'Background Color', @@ -116,9 +28,13 @@ export const InputColorPicker = ({ pickerSize = 250, }: InputColorPickerProps) => { const [isPickerOpen, setIsPickerOpen] = useState(false) - const [isBackgroundHidden, setIsBackgroundHidden] = useState(false) const pickerRef = useRef(null) + const { isHidden, handleToggleHide, handleDelete } = useImageActions( + onHideBackground, + onDeleteBackground + ) + const { hex, color, @@ -133,7 +49,7 @@ export const InputColorPicker = ({ handleColorChange, } = useColorPickerState({ value, onChange, onOpacityChange }) - const { position, handleDragStart, resetPosition } = useDraggable() + const { resetPosition } = useDraggable() const handleTogglePicker = () => { if (!isPickerOpen) { @@ -160,31 +76,14 @@ export const InputColorPicker = ({
{colorType === 'color' ? (
- + { @@ -232,124 +131,56 @@ export const InputColorPicker = ({ className="ml-1 bg-transparent border-none h-auto focus-within:ring-0 flex-1" classNameInput={cn( 'bg-transparent px-2 font-mono text-xs', - THEME_CLASSES.light.text, - THEME_CLASSES.dark.text + INPUT_THEME_CLASSES.light.text, + INPUT_THEME_CLASSES.dark.text )} - // disabled={hex === 'Mixed'} />
) : (
- - +
)} -
- -
- - -
+
{isPickerOpen && ( -
setIsPickerOpen(false)} + inputRef={pickerRef} > -
handleDragStart(e, pickerRef.current)} - > - - {title} - - -
- -
- -
-
+ + )}
) diff --git a/src/input-color-picker/utils/color-utils.ts b/src/input-color-picker/utils/color-utils.ts index 0c8810e..df6dca2 100644 --- a/src/input-color-picker/utils/color-utils.ts +++ b/src/input-color-picker/utils/color-utils.ts @@ -84,66 +84,72 @@ export const hexToRgb = (hexStr: string): RGB | null => { * ΠšΠΎΠ½Π²Π΅Ρ€Ρ‚ΠΈΡ€ΡƒΠ΅Ρ‚ ΠΏΡ€ΠΎΠ·Ρ€Π°Ρ‡Π½ΠΎΡΡ‚ΡŒ (0-100) Π² HEX (00-FF) */ export const opacityToHex = (opacity: number): string => { - if (opacity < 0 || opacity > 100) return 'ff' - const alpha = Math.round((opacity / 100) * 255) - return alpha.toString(16).padStart(2, '0') + if (opacity < 0 || opacity > 100) return 'ff' + const alpha = Math.round((opacity / 100) * 255) + return alpha.toString(16).padStart(2, '0') } /** * Π‘ΠΎΠ·Π΄Π°Π΅Ρ‚ display color с ΡƒΡ‡Π΅Ρ‚ΠΎΠΌ прозрачности */ export const createDisplayColor = ( - livePreviewColor: string, - opacityValue: number + livePreviewColor: string, + opacityValue: number ): string => { - let displayColor = - livePreviewColor && - (livePreviewColor.includes('gradient') || - livePreviewColor.startsWith('rgba') || - livePreviewColor.startsWith('rgb') || - livePreviewColor.startsWith('#')) - ? livePreviewColor - : '#FFFFFF' - - // ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° Π³Ρ€Π°Π΄ΠΈΠ΅Π½Ρ‚ΠΎΠ² - Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌ ΠΊΠ°ΠΊ Π΅ΡΡ‚ΡŒ - // Opacity для Π³Ρ€Π°Π΄ΠΈΠ΅Π½Ρ‚ΠΎΠ² примСняСтся Π½Π°ΠΏΡ€ΡΠΌΡƒΡŽ ΠΊ rgba Ρ†Π²Π΅Ρ‚Π°ΠΌ Π² handleOpacityChange - if (displayColor.includes('gradient')) { - return displayColor - } - - // Нормализация rgb/rgba -> rgba с ΡƒΡ‡Ρ‘Ρ‚ΠΎΠΌ Ρ‚Π΅ΠΊΡƒΡ‰Π΅ΠΉ opacityValue - if (displayColor.startsWith('rgb')) { - const match = displayColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/) - if (match) { - const r = parseInt(match[1], 10) - const g = parseInt(match[2], 10) - const b = parseInt(match[3], 10) - const a = match[4] !== undefined ? parseFloat(match[4]) : undefined - const finalA = isNaN(opacityValue) ? (typeof a === 'number' ? a : 1) : opacityValue / 100 - displayColor = `rgba(${r}, ${g}, ${b}, ${finalA})` - } - } - - // HEX -> HEXA с ΡƒΡ‡Ρ‘Ρ‚ΠΎΠΌ opacityValue - if (displayColor.startsWith('#')) { - let hexVal = displayColor.substring(1) - if (hexVal.length === 3 || hexVal.length === 4) { - hexVal = hexVal - .split('') - .map(char => char + char) - .join('') - } - const rgbHex = hexVal.substring(0, 6) - displayColor = `#${rgbHex}${opacityToHex(opacityValue)}` - } - - return displayColor + let displayColor = + livePreviewColor && + (livePreviewColor.includes('gradient') || + livePreviewColor.startsWith('rgba') || + livePreviewColor.startsWith('rgb') || + livePreviewColor.startsWith('#')) + ? livePreviewColor + : '#FFFFFF' + + // ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° Π³Ρ€Π°Π΄ΠΈΠ΅Π½Ρ‚ΠΎΠ² - Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌ ΠΊΠ°ΠΊ Π΅ΡΡ‚ΡŒ + // Opacity для Π³Ρ€Π°Π΄ΠΈΠ΅Π½Ρ‚ΠΎΠ² примСняСтся Π½Π°ΠΏΡ€ΡΠΌΡƒΡŽ ΠΊ rgba Ρ†Π²Π΅Ρ‚Π°ΠΌ Π² handleOpacityChange + if (displayColor.includes('gradient')) { + return displayColor + } + + // Нормализация rgb/rgba -> rgba с ΡƒΡ‡Ρ‘Ρ‚ΠΎΠΌ Ρ‚Π΅ΠΊΡƒΡ‰Π΅ΠΉ opacityValue + if (displayColor.startsWith('rgb')) { + const match = displayColor.match( + /rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/ + ) + if (match) { + const r = parseInt(match[1], 10) + const g = parseInt(match[2], 10) + const b = parseInt(match[3], 10) + const a = match[4] !== undefined ? parseFloat(match[4]) : undefined + const finalA = isNaN(opacityValue) + ? typeof a === 'number' + ? a + : 1 + : opacityValue / 100 + displayColor = `rgba(${r}, ${g}, ${b}, ${finalA})` + } + } + + // HEX -> HEXA с ΡƒΡ‡Ρ‘Ρ‚ΠΎΠΌ opacityValue + if (displayColor.startsWith('#')) { + let hexVal = displayColor.substring(1) + if (hexVal.length === 3 || hexVal.length === 4) { + hexVal = hexVal + .split('') + .map(char => char + char) + .join('') + } + const rgbHex = hexVal.substring(0, 6) + displayColor = `#${rgbHex}${opacityToHex(opacityValue)}` + } + + return displayColor } /** * Π€ΠΈΠ»ΡŒΡ‚Ρ€ΡƒΠ΅Ρ‚ Π²Π²ΠΎΠ΄ для HEX значСния */ export const filterHexInput = (value: string): string => { - const filteredValue = value.replace(/[^0-9a-fA-F]/g, '') - return filteredValue.substring(0, 6).toUpperCase() + const filteredValue = value.replace(/[^0-9a-fA-F]/g, '') + return filteredValue.substring(0, 6).toUpperCase() } diff --git a/src/input-hex-with-preview/input-hex-with-preview.tsx b/src/input-hex-with-preview/input-hex-with-preview.tsx index 57c608d..ba89908 100644 --- a/src/input-hex-with-preview/input-hex-with-preview.tsx +++ b/src/input-hex-with-preview/input-hex-with-preview.tsx @@ -1,7 +1,13 @@ -import React, { useEffect, useMemo, useRef, useState } from 'react' +import React, { useMemo } from 'react' import tc from 'tinycolor2' import { Input } from '../input' +import { DragButton } from '../shared/components/drag-button/drag-button' +import { InputContainer } from '../shared/components/input-container/input-container' +import { INPUT_THEME_CLASSES } from '../shared/constants/input-theme' +import { useDraggableInput } from '../shared/hooks/use-draggable-input' +import { useHexInput } from '../shared/hooks/use-hex-input' import { cn } from '../shared/utils/cn' +import { convertToHex8 } from '../shared/utils/color-utils' export interface InputHexWithPreviewProps extends Omit, 'onChange' | 'value'> { @@ -22,24 +28,6 @@ export interface InputHexWithPreviewProps showAlpha?: boolean } -const THEME_CLASSES = { - light: { - container: 'bg-white border-gray-300 focus-within:ring-blue-500', - input: 'text-gray-900', - icon: 'text-gray-600', - dragArea: 'bg-gray-100 hover:bg-gray-200', - preview: 'border-gray-300', - }, - dark: { - container: - 'dark:bg-gray-800 dark:border-gray-600 dark:focus-within:ring-blue-400', - input: 'dark:text-gray-100', - icon: 'dark:text-gray-300', - dragArea: 'dark:bg-gray-700 dark:hover:bg-gray-600', - preview: 'dark:border-gray-600', - }, -} as const - export const InputHexWithPreview = React.forwardRef< HTMLInputElement, InputHexWithPreviewProps @@ -62,90 +50,29 @@ export const InputHexWithPreview = React.forwardRef< }, ref ) => { - const [isEditing, setIsEditing] = useState(false) - - // Normalize input color through tinycolor2 - const color = tc(hexColor) - const hex = color.toHex() // 6 symbols without # - const alpha = color.getAlpha() // 0-1 - const alphaHex = Math.round(alpha * 255) - .toString(16) - .padStart(2, '0') - // Full value: if showAlpha and alpha < 1, add alpha - const hexFromProp = showAlpha && alpha < 1 ? hex + alphaHex : hex - - const [localHex, setLocalHex] = useState(hexFromProp) - const dragStartValue = useRef(0) - - useEffect(() => { - if (!isEditing) { - setLocalHex(hexFromProp) + // Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ Ρ…ΡƒΠΊ для управлСния hex input + const { localHex, handleHexInput, setIsEditing, setLocalHex } = useHexInput( + { + hexColor, + onChange: handleChange, + showAlpha, } - }, [hexColor, isEditing, hexFromProp]) - - // Helper: converts 8-symbol hex to format with alpha - const convertToHex8 = (hex8: string) => { - const base = hex8.slice(0, 6) - const alphaHex = hex8.slice(6, 8) - const alphaDecimal = parseInt(alphaHex, 16) / 255 - return tc(`#${base}`).setAlpha(alphaDecimal).toHex8String().toUpperCase() - } - - const handleHexInput = (e: React.ChangeEvent) => { - const maxLen = showAlpha ? 8 : 6 - // Filter only hex symbols, truncate by length - const filtered = e.target.value - .replace(/[^0-9a-fA-F]/g, '') - .slice(0, maxLen) - .toUpperCase() - - setLocalHex(filtered) + ) - // Emit change only when full length - if (filtered.length === 6) { - handleChange(`#${filtered}`) - } else if (filtered.length === 8 && showAlpha) { - handleChange(convertToHex8(filtered)) - } - } + // ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ hex для drag-Π»ΠΎΠ³ΠΈΠΊΠΈ + const color = tc(hexColor) + const hex = color.toHex() const handleIconClick = () => { onIconClick?.(localHex) } - const handlePointerDown = (e: React.PointerEvent) => { - if (disabled || isDisabledMouseEvent) return - e.preventDefault() - - onIconPointerDown?.(localHex) - - const target = e.currentTarget - target.setPointerCapture(e.pointerId) - - const styleElement = document.createElement('style') - styleElement.id = 'dragging-cursor-style' - styleElement.innerHTML = ` - body, body * { - cursor: ew-resize !important; - user-select: none !important; - } - ` - document.head.appendChild(styleElement) - - dragStartValue.current = parseInt(hex, 16) - const startX = e.clientX - setIsEditing(true) - - // Save initial alpha to preserve it during drag - const initialAlpha = - showAlpha && localHex.length >= 8 ? localHex.slice(6, 8) : '' - - const handlePointerMove = (event: PointerEvent) => { - const movementX = event.clientX - startX + // Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ Ρ…ΡƒΠΊ для drag-to-change Π»ΠΎΠ³ΠΈΠΊΠΈ + const { handlePointerDown: handleDrag } = useDraggableInput({ + orientation: 'horizontal', + onDragChange: (delta, startValue) => { const step = 55000 - let newhexColorInt = Math.round( - dragStartValue.current + movementX * step - ) + let newhexColorInt = Math.round(startValue + delta * step) newhexColorInt = Math.max(0, Math.min(0xffffff, newhexColorInt)) // Drag changes only base color (6 symbols) @@ -154,7 +81,11 @@ export const InputHexWithPreview = React.forwardRef< .padStart(6, '0') .toUpperCase() - // Update local state with new base color + preserved alpha + // Save initial alpha to preserve it during drag + const initialAlpha = + showAlpha && localHex.length >= 8 ? localHex.slice(6, 8) : '' + + // Update local state immediately for visual feedback setLocalHex(newBaseHex + initialAlpha) // Emit with alpha if it exists, otherwise just base color @@ -163,23 +94,20 @@ export const InputHexWithPreview = React.forwardRef< } else { handleChange(`#${newBaseHex}`) } - } - - const handlePointerUp = (event: PointerEvent) => { - target.releasePointerCapture(event.pointerId) + }, + onDragStart: () => { + setIsEditing(true) + onIconPointerDown?.(localHex) + }, + onDragEnd: () => { setIsEditing(false) onIconPointerUp?.(localHex) + }, + disabled: disabled || isDisabledMouseEvent, + }) - const styleToRemove = document.getElementById('dragging-cursor-style') - if (styleToRemove) { - styleToRemove.remove() - } - target.removeEventListener('pointermove', handlePointerMove) - document.removeEventListener('pointerup', handlePointerUp) - } - - target.addEventListener('pointermove', handlePointerMove) - document.addEventListener('pointerup', handlePointerUp) + const handlePointerDown = (e: React.PointerEvent) => { + handleDrag(e, parseInt(hex, 16)) } // Color for preview with opacity @@ -190,8 +118,8 @@ export const InputHexWithPreview = React.forwardRef<
- + # @@ -242,8 +155,8 @@ export const InputHexWithPreview = React.forwardRef< type="text" className={cn( 'w-full rounded-none border-none bg-transparent text-left px-2 py-0 h-full text-sm', - THEME_CLASSES.light.input, - THEME_CLASSES.dark.input, + INPUT_THEME_CLASSES.light.input, + INPUT_THEME_CLASSES.dark.input, classNameInput )} value={localHex.toLocaleUpperCase()} @@ -255,7 +168,7 @@ export const InputHexWithPreview = React.forwardRef< ref={ref} {...props} /> -
+ ) } ) diff --git a/src/input-hex/input-hex.tsx b/src/input-hex/input-hex.tsx index 5c81e92..01b9dc5 100644 --- a/src/input-hex/input-hex.tsx +++ b/src/input-hex/input-hex.tsx @@ -1,7 +1,13 @@ -import React, { useEffect, useRef, useState } from 'react' +import React from 'react' import tc from 'tinycolor2' import { Input } from '../input' +import { DragButton } from '../shared/components/drag-button/drag-button' +import { InputContainer } from '../shared/components/input-container/input-container' +import { INPUT_THEME_CLASSES } from '../shared/constants/input-theme' +import { useDraggableInput } from '../shared/hooks/use-draggable-input' +import { useHexInput } from '../shared/hooks/use-hex-input' import { cn } from '../shared/utils/cn' +import { convertToHex8 } from '../shared/utils/color-utils' export interface InputHexProps extends Omit, 'onChange' | 'value'> { @@ -21,22 +27,6 @@ export interface InputHexProps showAlpha?: boolean } -const THEME_CLASSES = { - light: { - container: 'bg-white border-gray-300 focus-within:ring-blue-500', - input: 'text-gray-900', - icon: 'text-gray-600', - dragArea: 'bg-gray-100 hover:bg-gray-200', - }, - dark: { - container: - 'dark:bg-gray-800 dark:border-gray-600 dark:focus-within:ring-blue-400', - input: 'dark:text-gray-100', - icon: 'dark:text-gray-300', - dragArea: 'dark:bg-gray-700 dark:hover:bg-gray-600', - }, -} as const - export const InputHex = React.forwardRef( ( { @@ -55,90 +45,29 @@ export const InputHex = React.forwardRef( }, ref ) => { - const [isEditing, setIsEditing] = useState(false) - - // Normalize input color through tinycolor2 - const color = tc(hexColor) - const hex = color.toHex() // 6 symbols without # - const alpha = color.getAlpha() // 0-1 - const alphaHex = Math.round(alpha * 255) - .toString(16) - .padStart(2, '0') - // Full value: if showAlpha and alpha < 1, add alpha - const hexFromProp = showAlpha && alpha < 1 ? hex + alphaHex : hex - - const [localHex, setLocalHex] = useState(hexFromProp) - const dragStartValue = useRef(0) - - useEffect(() => { - if (!isEditing) { - setLocalHex(hexFromProp) + // Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ Ρ…ΡƒΠΊ для управлСния hex input + const { localHex, handleHexInput, setIsEditing, setLocalHex } = useHexInput( + { + hexColor, + onChange: handleChange, + showAlpha, } - }, [hexColor, isEditing, hexFromProp]) - - // Helper: converts 8-symbol hex to format with alpha - const convertToHex8 = (hex8: string) => { - const base = hex8.slice(0, 6) - const alphaHex = hex8.slice(6, 8) - const alphaDecimal = parseInt(alphaHex, 16) / 255 - return tc(`#${base}`).setAlpha(alphaDecimal).toHex8String().toUpperCase() - } - - const handleHexInput = (e: React.ChangeEvent) => { - const maxLen = showAlpha ? 8 : 6 - // Filter only hex symbols, truncate by length - const filtered = e.target.value - .replace(/[^0-9a-fA-F]/g, '') - .slice(0, maxLen) - .toUpperCase() - - setLocalHex(filtered) + ) - // Emit change only when full length - if (filtered.length === 6) { - handleChange(`#${filtered}`) - } else if (filtered.length === 8 && showAlpha) { - handleChange(convertToHex8(filtered)) - } - } + // ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ hex для drag-Π»ΠΎΠ³ΠΈΠΊΠΈ + const color = tc(hexColor) + const hex = color.toHex() const handleIconClick = () => { onIconClick?.(localHex) } - const handlePointerDown = (e: React.PointerEvent) => { - if (disabled || isDisabledMouseEvent) return - e.preventDefault() - - onIconPointerDown?.(localHex) - - const target = e.currentTarget - target.setPointerCapture(e.pointerId) - - const styleElement = document.createElement('style') - styleElement.id = 'dragging-cursor-style' - styleElement.innerHTML = ` - body, body * { - cursor: ew-resize !important; - user-select: none !important; - } - ` - document.head.appendChild(styleElement) - - dragStartValue.current = parseInt(hex, 16) - const startX = e.clientX - setIsEditing(true) - - // Save initial alpha to preserve it during drag - const initialAlpha = - showAlpha && localHex.length >= 8 ? localHex.slice(6, 8) : '' - - const handlePointerMove = (event: PointerEvent) => { - const movementX = event.clientX - startX + // Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ Ρ…ΡƒΠΊ для drag-to-change Π»ΠΎΠ³ΠΈΠΊΠΈ + const { handlePointerDown: handleDrag } = useDraggableInput({ + orientation: 'horizontal', + onDragChange: (delta, startValue) => { const step = 55000 - let newhexColorInt = Math.round( - dragStartValue.current + movementX * step - ) + let newhexColorInt = Math.round(startValue + delta * step) newhexColorInt = Math.max(0, Math.min(0xffffff, newhexColorInt)) // Drag changes only base color (6 symbols) @@ -147,7 +76,11 @@ export const InputHex = React.forwardRef( .padStart(6, '0') .toUpperCase() - // Update local state with new base color + preserved alpha + // Save initial alpha to preserve it during drag + const initialAlpha = + showAlpha && localHex.length >= 8 ? localHex.slice(6, 8) : '' + + // Update local state immediately for visual feedback setLocalHex(newBaseHex + initialAlpha) // Emit with alpha if it exists, otherwise just base color @@ -156,61 +89,46 @@ export const InputHex = React.forwardRef( } else { handleChange(`#${newBaseHex}`) } - } - - const handlePointerUp = (event: PointerEvent) => { - target.releasePointerCapture(event.pointerId) + }, + onDragStart: () => { + setIsEditing(true) + onIconPointerDown?.(localHex) + }, + onDragEnd: () => { setIsEditing(false) onIconPointerUp?.(localHex) + }, + disabled: disabled || isDisabledMouseEvent, + }) - const styleToRemove = document.getElementById('dragging-cursor-style') - if (styleToRemove) { - styleToRemove.remove() - } - target.removeEventListener('pointermove', handlePointerMove) - document.removeEventListener('pointerup', handlePointerUp) - } - - target.addEventListener('pointermove', handlePointerMove) - document.addEventListener('pointerup', handlePointerUp) + const handlePointerDown = (e: React.PointerEvent) => { + handleDrag(e, parseInt(hex, 16)) } return ( -
- + ( ref={ref} {...props} /> -
+ ) } ) diff --git a/src/input-image-select/components/image-picker-modal.tsx b/src/input-image-select/components/image-picker-modal.tsx new file mode 100644 index 0000000..48b3bc5 --- /dev/null +++ b/src/input-image-select/components/image-picker-modal.tsx @@ -0,0 +1,489 @@ +import { RotateCw, Upload } from 'lucide-react' +import { useEffect, useRef, useState, type CSSProperties } from 'react' +import { InputSelectModal } from '../../shared/components/input-select-modal/input-select-modal' +import { cn } from '../../shared/utils/cn' + +const THEME_CLASSES = { + light: { + container: 'bg-white', + text: 'text-gray-900', + textMuted: 'text-gray-500', + button: 'bg-blue-600 hover:bg-blue-700', + overlay: 'bg-black/50', + placeholder: 'bg-gray-200 text-gray-500', + }, + dark: { + container: 'dark:bg-gray-800', + text: 'dark:text-gray-200', + textMuted: 'dark:text-gray-400', + button: 'dark:bg-blue-600 dark:hover:bg-blue-700', + overlay: 'dark:bg-black/60', + placeholder: 'dark:bg-gray-700 dark:text-gray-400', + }, +} as const + +export interface ImageFilters { + exposure: number + contrast: number + saturation: number + temperature: number + tint: number + highlights: number + shadows: number +} + +interface ImagePickerModalProps { + isOpen: boolean + isRotateButtonActive: boolean + onClose: () => void + title: string + imageUrl?: string + opacityImage?: number + imageHidden?: boolean + onImageChange?: (file: File | null) => void + filters?: ImageFilters + onFiltersChange?: (filters: ImageFilters) => void + rotation?: number + onRotationChange?: (rotation: number) => void + className?: string + classNameContainer?: string + classNameButton?: string + classNameOverlay?: string + classNamePlaceholder?: string + classNameModal?: string + classNameModalHeader?: string + classNameModalTitle?: string + classNameModalCloseButton?: string + classNameModalContent?: string +} + +export const ImagePickerModal = ({ + isOpen, + isRotateButtonActive, + onClose, + title, + imageUrl, + opacityImage = 100, + imageHidden, + onImageChange, + filters, + onFiltersChange, + rotation = 0, + onRotationChange, + className, + classNameContainer, + classNameButton, + classNameOverlay, + classNamePlaceholder, + classNameModal, + classNameModalHeader, + classNameModalTitle, + classNameModalCloseButton, + classNameModalContent, +}: ImagePickerModalProps) => { + const [previewUrl, setPreviewUrl] = useState(imageUrl) + const [localFilters, setLocalFilters] = useState( + filters || { + exposure: 0, + contrast: 0, + saturation: 0, + temperature: 0, + tint: 0, + highlights: 0, + shadows: 0, + } + ) + const fileInputRef = useRef(null) + const modalRef = useRef(null) + + const clamp = (value: number, min: number, max: number) => + Math.min(Math.max(value, min), max) + const [rotationAngle, setRotationAngle] = useState(rotation) + + useEffect(() => { + setPreviewUrl(imageUrl) + }, [imageUrl]) + + useEffect(() => { + if (filters) { + setLocalFilters(filters) + } + }, [filters]) + + useEffect(() => { + setRotationAngle(rotation) + }, [rotation]) + + const handleFilterChange = (key: keyof ImageFilters, value: number) => { + const newFilters = { ...localFilters, [key]: value } + setLocalFilters(newFilters) + onFiltersChange?.(newFilters) + } + + const handleRotate = () => { + const nextRotation = (rotationAngle + 90) % 360 + setRotationAngle(nextRotation) + onRotationChange?.(nextRotation) + } + + const getFilterStyle = () => { + const highlightsBoost = localFilters.highlights / 100 + const shadowsBoost = localFilters.shadows / 100 + + const brightness = clamp( + 1 + + localFilters.exposure / 100 + + Math.max(highlightsBoost, 0) * 0.4 - + Math.max(shadowsBoost, 0) * 0.5 + + Math.max(-shadowsBoost, 0) * 0.3 - + Math.max(-highlightsBoost, 0) * 0.3, + 0.1, + 3 + ) + const contrast = clamp( + 1 + + localFilters.contrast / 100 + + Math.max(highlightsBoost, 0) * 0.3 + + Math.max(shadowsBoost, 0) * 0.4 - + Math.max(-highlightsBoost, 0) * 0.3 - + Math.max(-shadowsBoost, 0) * 0.4, + 0.1, + 4 + ) + const saturate = clamp(1 + localFilters.saturation / 100, 0, 4) + const hueRotate = localFilters.temperature * 0.6 + + return { + filter: `brightness(${brightness}) contrast(${contrast}) saturate(${saturate}) hue-rotate(${hueRotate}deg)`, + } + } + + const getTintOverlayStyle = (): CSSProperties | null => { + if (!localFilters.tint) return null + const intensity = Math.min(Math.abs(localFilters.tint) / 100, 1) + const hue = localFilters.tint >= 0 ? 120 : 300 + return { + backgroundColor: `hsla(${hue}, 100%, 50%, ${intensity * 0.5})`, + mixBlendMode: 'color', + opacity: intensity * 0.6, + } + } + + const getHighlightsOverlayStyle = (): CSSProperties | null => { + if (!localFilters.highlights) return null + const value = localFilters.highlights + const intensity = Math.min(Math.abs(value) / 100, 1) + if (value > 0) { + return { + backgroundColor: 'rgba(255, 255, 255, 1)', + mixBlendMode: 'screen', + opacity: intensity * 0.5, + } + } + return { + backgroundColor: 'rgba(0, 0, 0, 1)', + mixBlendMode: 'multiply', + opacity: intensity * 0.4, + } + } + + const getShadowsOverlayStyle = (): CSSProperties | null => { + if (!localFilters.shadows) return null + const value = localFilters.shadows + const intensity = Math.min(Math.abs(value) / 100, 1) + if (value > 0) { + return { + backgroundColor: 'rgba(0, 0, 0, 1)', + mixBlendMode: 'multiply', + opacity: intensity * 0.6, + } + } + return { + backgroundColor: 'rgba(255, 255, 255, 1)', + mixBlendMode: 'screen', + opacity: intensity * 0.4, + } + } + + const tintOverlayStyle = getTintOverlayStyle() + const highlightsOverlayStyle = getHighlightsOverlayStyle() + const shadowsOverlayStyle = getShadowsOverlayStyle() + + const handleFileChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0] + if (file) { + const reader = new FileReader() + reader.onloadend = () => { + setPreviewUrl(reader.result as string) + } + reader.readAsDataURL(file) + onImageChange?.(file) + } + } + + const handleUploadClick = () => { + fileInputRef.current?.click() + } + + if (!isOpen) return null + + return ( + +
+
+ {isRotateButtonActive && imageUrl && ( + + )} +
+
+ {previewUrl && ( + <> + {imageHidden ? ( +
+ Image Hidden +
+ ) : ( + <> +
+ Preview + {tintOverlayStyle && ( +
+ )} + {highlightsOverlayStyle && ( +
+ )} + {shadowsOverlayStyle && ( +
+ )} +
+
+ +
+ + )} + + )} + {!previewUrl && ( +
+ +
+ )} +
+
+ {previewUrl && !imageHidden && ( + + )} + + ) +} + +const ImageFiltersPanel = ({ + filters, + onFilterChange, +}: { + filters: ImageFilters + onFilterChange: (key: keyof ImageFilters, value: number) => void +}) => { + const filterControls = [ + { + key: 'exposure' as keyof ImageFilters, + label: 'Exposure', + min: -100, + max: 100, + }, + { + key: 'contrast' as keyof ImageFilters, + label: 'Contrast', + min: -100, + max: 100, + }, + { + key: 'saturation' as keyof ImageFilters, + label: 'Saturation', + min: -100, + max: 100, + }, + { + key: 'temperature' as keyof ImageFilters, + label: 'Temperature', + min: -100, + max: 100, + }, + { key: 'tint' as keyof ImageFilters, label: 'Tint', min: -100, max: 100 }, + { + key: 'highlights' as keyof ImageFilters, + label: 'Highlights', + min: -100, + max: 100, + }, + { + key: 'shadows' as keyof ImageFilters, + label: 'Shadows', + min: -100, + max: 100, + }, + ] + + const handleSliderChange = ( + key: keyof ImageFilters, + rawValue: number, + snapThreshold = 5 + ) => { + const snappedValue = Math.abs(rawValue) <= snapThreshold ? 0 : rawValue + onFilterChange(key, snappedValue) + } + + return ( +
+ {filterControls.map(({ key, label, min, max }) => ( +
+ + handleSliderChange(key, Number(e.target.value))} + className="flex-1 h-1 bg-gray-300 dark:bg-gray-600 rounded-lg appearance-none cursor-pointer slider" + style={{ + background: `linear-gradient(to right, #3b82f6 0%, #3b82f6 ${ + ((filters[key] - min) / (max - min)) * 100 + }%, #d1d5db ${((filters[key] - min) / (max - min)) * 100}%, #d1d5db 100%)`, + }} + /> +
+ ))} +
+ ) +} + +const UploadImageButton = ({ + handleUploadClick, + fileInputRef, + handleFileChange, + classNameButton, +}: { + handleUploadClick: () => void + fileInputRef: React.RefObject + handleFileChange: (e: React.ChangeEvent) => void + classNameButton?: string +}) => { + return ( + <> + + + + ) +} diff --git a/src/input-image-select/index.tsx b/src/input-image-select/index.tsx new file mode 100644 index 0000000..3ccb85d --- /dev/null +++ b/src/input-image-select/index.tsx @@ -0,0 +1,3 @@ +export type { ImageFilters } from './components/image-picker-modal' +export { InputImageSelect } from './input-image-select' +export type { InputImageSelectProps } from './input-image-select' diff --git a/src/input-image-select/input-image-select.tsx b/src/input-image-select/input-image-select.tsx new file mode 100644 index 0000000..08d4bf5 --- /dev/null +++ b/src/input-image-select/input-image-select.tsx @@ -0,0 +1,167 @@ +import { useEffect, useState } from 'react' +import { ActionButtons } from '../shared/components/action-buttons/action-buttons' +import { + IMAGE_INPUT_THEME_CLASSES, + useImageActions, + useImageState, +} from '../shared/components/image-input' +import { ImagePreview } from '../shared/components/image-preview/image-preview' +import { TextButton } from '../shared/components/text-button/text-button' +import { cn } from '../shared/utils/cn' +import { + ImagePickerModal, + type ImageFilters, +} from './components/image-picker-modal' + +export interface InputImageSelectProps { + title?: string + className?: string + imageUrl?: string + opacity?: number + onOpacityChange?: (opacity: number) => void + onHideImage?: (hidden: boolean) => void + onDeleteImage?: () => void + onImageChange?: (file: File | null) => void + filters?: ImageFilters + onFiltersChange?: (filters: ImageFilters) => void + rotation?: number + onRotationChange?: (rotation: number) => void + classNameModal?: string + classNameModalContainer?: string + classNameModalButton?: string + classNameModalOverlay?: string + classNameModalPlaceholder?: string + classNameModalWrapper?: string + classNameModalHeader?: string + classNameModalTitle?: string + classNameModalCloseButton?: string + classNameModalContent?: string +} + +export const InputImageSelect = ({ + title = 'Image', + className, + imageUrl, + opacity = 100, + onImageChange, + onOpacityChange, + onHideImage, + onDeleteImage, + filters, + onFiltersChange, + rotation = 0, + onRotationChange, + classNameModal, + classNameModalContainer, + classNameModalButton, + classNameModalOverlay, + classNameModalPlaceholder, + classNameModalWrapper, + classNameModalHeader, + classNameModalTitle, + classNameModalCloseButton, + classNameModalContent, +}: InputImageSelectProps) => { + // Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ Π½ΠΎΠ²Ρ‹Π΅ Ρ…ΡƒΠΊΠΈ ΠΈΠ· ΠΎΠ±Ρ‰Π΅ΠΉ инфраструктуры + const [isPickerOpen, setIsPickerOpen] = useState(false) + + const { + opacity: opacityValue, + rotation: rotationValue, + setOpacity: setOpacityValue, + setRotation: setRotationValue, + } = useImageState(imageUrl, opacity, rotation) + + const { + isHidden, + isRotateButtonActive, + handleToggleHide, + handleDelete, + setIsRotateButtonActive, + } = useImageActions(onHideImage, onDeleteImage) + + useEffect(() => { + if (imageUrl) { + setIsRotateButtonActive(true) + } + }, [imageUrl, setIsRotateButtonActive]) + + const handleTogglePicker = () => { + setIsPickerOpen(prev => !prev) + } + + const handleOpacityChange = (newOpacity: number) => { + setOpacityValue(newOpacity) + onOpacityChange?.(newOpacity) + } + + const handleRotationChange = (nextRotation: number) => { + setRotationValue(nextRotation) + onRotationChange?.(nextRotation) + } + + return ( +
+
+
+ + + Image + +
+ + +
+ + setIsPickerOpen(false)} + title={title} + imageUrl={imageUrl} + opacityImage={opacityValue} + imageHidden={isHidden} + onImageChange={onImageChange} + filters={filters} + onFiltersChange={onFiltersChange} + rotation={rotationValue} + onRotationChange={handleRotationChange} + className={classNameModalWrapper} + classNameContainer={classNameModalContainer} + classNameButton={classNameModalButton} + classNameOverlay={classNameModalOverlay} + classNamePlaceholder={classNameModalPlaceholder} + classNameModal={classNameModal} + classNameModalHeader={classNameModalHeader} + classNameModalTitle={classNameModalTitle} + classNameModalCloseButton={classNameModalCloseButton} + classNameModalContent={classNameModalContent} + /> +
+ ) +} diff --git a/src/input-image-select/utils/index.ts b/src/input-image-select/utils/index.ts new file mode 100644 index 0000000..2050266 --- /dev/null +++ b/src/input-image-select/utils/index.ts @@ -0,0 +1 @@ +export { rotateImageOnCanvas, rotateImage90Degrees } from './rotate-image' diff --git a/src/input-image-select/utils/rotate-image.ts b/src/input-image-select/utils/rotate-image.ts new file mode 100644 index 0000000..024d307 --- /dev/null +++ b/src/input-image-select/utils/rotate-image.ts @@ -0,0 +1,131 @@ +/** + * Rotates an image on canvas and returns the rotated image as a data URL. + * + * @param imageUrl - The source image URL (can be data URL or regular URL) + * @param rotation - Rotation angle in degrees (0, 90, 180, 270, etc.) + * @param quality - JPEG quality (0-1), default 0.95 + * @returns Promise that resolves to the rotated image data URL + */ +export const rotateImageOnCanvas = async ( + imageUrl: string, + rotation: number, + quality: number = 0.95 +): Promise => { + return new Promise((resolve, reject) => { + const img = new Image() + img.crossOrigin = 'anonymous' + + img.onload = () => { + try { + const canvas = document.createElement('canvas') + const ctx = canvas.getContext('2d') + + if (!ctx) { + reject(new Error('Failed to get canvas context')) + return + } + + // Normalize rotation to 0-360 range + const normalizedRotation = ((rotation % 360) + 360) % 360 + + // Calculate canvas dimensions based on rotation + const radians = (normalizedRotation * Math.PI) / 180 + const cos = Math.abs(Math.cos(radians)) + const sin = Math.abs(Math.sin(radians)) + + const newWidth = img.width * cos + img.height * sin + const newHeight = img.width * sin + img.height * cos + + canvas.width = newWidth + canvas.height = newHeight + + // Move to center of canvas + ctx.translate(newWidth / 2, newHeight / 2) + + // Rotate + ctx.rotate(radians) + + // Draw image centered + ctx.drawImage(img, -img.width / 2, -img.height / 2) + + // Convert to data URL + const dataUrl = canvas.toDataURL('image/jpeg', quality) + resolve(dataUrl) + } catch (error) { + reject(error) + } + } + + img.onerror = () => { + reject(new Error('Failed to load image')) + } + + img.src = imageUrl + }) +} + +/** + * Rotates an image by 90-degree increments only (optimized for common use case). + * This version maintains the exact dimensions for 90/270 rotations. + * + * @param imageUrl - The source image URL + * @param rotation - Rotation angle (should be 0, 90, 180, or 270) + * @param quality - JPEG quality (0-1), default 0.95 + * @returns Promise that resolves to the rotated image data URL + */ +export const rotateImage90Degrees = async ( + imageUrl: string, + rotation: number, + quality: number = 0.95 +): Promise => { + return new Promise((resolve, reject) => { + const img = new Image() + img.crossOrigin = 'anonymous' + + img.onload = () => { + try { + const canvas = document.createElement('canvas') + const ctx = canvas.getContext('2d') + + if (!ctx) { + reject(new Error('Failed to get canvas context')) + return + } + + // Normalize rotation to 0, 90, 180, or 270 + const normalizedRotation = ((rotation % 360) + 360) % 360 + const steps = Math.round(normalizedRotation / 90) % 4 + + // Set canvas dimensions based on rotation + if (steps === 1 || steps === 3) { + // 90 or 270 degrees - swap width and height + canvas.width = img.height + canvas.height = img.width + } else { + // 0 or 180 degrees - keep original dimensions + canvas.width = img.width + canvas.height = img.height + } + + // Move to center and rotate + ctx.translate(canvas.width / 2, canvas.height / 2) + ctx.rotate((steps * Math.PI) / 2) + + // Draw image centered + ctx.drawImage(img, -img.width / 2, -img.height / 2) + + // Convert to data URL + const dataUrl = canvas.toDataURL('image/jpeg', quality) + resolve(dataUrl) + } catch (error) { + reject(error) + } + } + + img.onerror = () => { + reject(new Error('Failed to load image')) + } + + img.src = imageUrl + }) +} diff --git a/src/input-number-select/input-number-select.tsx b/src/input-number-select/input-number-select.tsx index f101a6a..8a9d036 100644 --- a/src/input-number-select/input-number-select.tsx +++ b/src/input-number-select/input-number-select.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useMemo, useRef, useState } from 'react' import { FaRegDotCircle } from 'react-icons/fa' import { Input } from '../input' +import { INPUT_THEME_CLASSES } from '../shared/constants/input-theme' import { cn } from '../shared/utils/cn' import { removeTrailingZeros } from '../shared/utils/remove-trailing-zeros' @@ -37,22 +38,6 @@ export interface InputNumberSelectProps isDisabledMouseEvent?: boolean } -const THEME_CLASSES = { - light: { - container: 'bg-white border-gray-300 focus-within:ring-blue-500', - input: 'text-gray-900', - icon: 'text-gray-600', - dragArea: 'bg-gray-100 hover:bg-gray-200', - }, - dark: { - container: - 'dark:bg-gray-800 dark:border-gray-600 dark:focus-within:ring-blue-400', - input: 'dark:text-gray-100', - icon: 'dark:text-gray-300', - dragArea: 'dark:bg-gray-700 dark:hover:bg-gray-600', - }, -} as const - export const InputNumberSelect = React.forwardRef< HTMLInputElement, InputNumberSelectProps @@ -343,8 +328,8 @@ export const InputNumberSelect = React.forwardRef< {icon} @@ -353,7 +338,10 @@ export const InputNumberSelect = React.forwardRef< } return ( ) }, [icon]) @@ -362,8 +350,8 @@ export const InputNumberSelect = React.forwardRef<
{unit} diff --git a/src/input-upload-image/index.tsx b/src/input-upload-image/index.tsx new file mode 100644 index 0000000..2387b5f --- /dev/null +++ b/src/input-upload-image/index.tsx @@ -0,0 +1,2 @@ +export { InputUploadImage } from './input-upload-image' +export type { InputUploadImageProps } from './input-upload-image' diff --git a/src/input-upload-image/input-upload-image.tsx b/src/input-upload-image/input-upload-image.tsx new file mode 100644 index 0000000..57afc4e --- /dev/null +++ b/src/input-upload-image/input-upload-image.tsx @@ -0,0 +1,96 @@ +import { ActionButtons } from '../shared/components/action-buttons/action-buttons' +import { ImagePreview } from '../shared/components/image-preview/image-preview' +import { TextButton } from '../shared/components/text-button/text-button' +import { + IMAGE_INPUT_THEME_CLASSES, + useFileUpload, + useImageActions, + getFileNameDisplay, +} from '../shared/components/image-input' +import { cn } from '../shared/utils/cn' + +export interface InputUploadImageProps { + title?: string + className?: string + imageUrl?: string + fileName?: string + onImageChange?: (file: File | null) => void + onHideImage?: (hidden: boolean) => void + onDeleteImage?: () => void +} + +export const InputUploadImage = ({ + title = 'Upload image', + className, + imageUrl, + fileName, + onImageChange, + onHideImage, + onDeleteImage, +}: InputUploadImageProps) => { + // Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ Π½ΠΎΠ²Ρ‹Π΅ Ρ…ΡƒΠΊΠΈ ΠΈΠ· ΠΎΠ±Ρ‰Π΅ΠΉ инфраструктуры + const { + previewUrl, + fileName: currentFileName, + fileInputRef, + handleFileChange, + handleClick, + clearFile, + } = useFileUpload(onImageChange, imageUrl, fileName) + + const { isHidden, handleToggleHide, handleDelete } = useImageActions( + onHideImage, + onDeleteImage, + clearFile + ) + + const displayFileName = getFileNameDisplay( + currentFileName, + fileName, + imageUrl, + title + ) + + return ( +
+
+
+ + + {displayFileName} + +
+ + +
+ + +
+ ) +} diff --git a/src/input/input.tsx b/src/input/input.tsx index 7db8cfc..ec8b201 100644 --- a/src/input/input.tsx +++ b/src/input/input.tsx @@ -1,21 +1,20 @@ import * as React from 'react' import { cn } from '../shared/utils/cn' -const Input = React.forwardRef>( - ({ className, type, ...props }, ref) => { - return ( - - ) - } -) +export const Input = React.forwardRef< + HTMLInputElement, + React.ComponentProps<'input'> +>(({ className, type, ...props }, ref) => { + return ( + + ) +}) Input.displayName = 'Input' - -export { Input } diff --git a/src/shared/components/action-buttons/action-buttons.tsx b/src/shared/components/action-buttons/action-buttons.tsx new file mode 100644 index 0000000..7e89dc9 --- /dev/null +++ b/src/shared/components/action-buttons/action-buttons.tsx @@ -0,0 +1,90 @@ +import { Eye, EyeOff, Trash2 } from 'lucide-react' +import React from 'react' +import { cn } from '../../utils/cn' +import { Divider } from '../divider/divider' +import { IconButton } from '../icon-button/icon-button' +import { OpacityDragControl } from '../opacity-drag-control/opacity-drag-control' + +export interface ActionButtonsProps { + /** + * ΠŸΠΎΠΊΠ°Π·Ρ‹Π²Π°Ρ‚ΡŒ ΠΊΠ½ΠΎΠΏΠΊΡƒ видимости + */ + showVisibility?: boolean + /** + * ΠŸΠΎΠΊΠ°Π·Ρ‹Π²Π°Ρ‚ΡŒ ΠΊΠ½ΠΎΠΏΠΊΡƒ удалСния + */ + showDelete?: boolean + /** + * ΠŸΠΎΠΊΠ°Π·Ρ‹Π²Π°Ρ‚ΡŒ opacity control + */ + showOpacity?: boolean + /** + * БостояниС видимости (для ΠΊΠ½ΠΎΠΏΠΊΠΈ Eye/EyeOff) + */ + isHidden?: boolean + /** + * Π—Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ opacity (0-100) + */ + opacity?: number + /** + * ΠžΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ измСнСния видимости + */ + onToggleVisibility?: () => void + /** + * ΠžΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ удалСния + */ + onDelete?: () => void + /** + * ΠžΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ измСнСния opacity + */ + onOpacityChange?: (opacity: number) => void + /** + * Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ классы для divider + */ + dividerClassName?: string + /** + * Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ классы для ΠΊΠΎΠ½Ρ‚Π΅ΠΉΠ½Π΅Ρ€Π° + */ + className?: string +} + +/** + * Π“Ρ€ΡƒΠΏΠΏΠ° action buttons для input ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ΠΎΠ² + * Π’ΠΊΠ»ΡŽΡ‡Π°Π΅Ρ‚: OpacityDragControl, Eye/EyeOff, Trash + */ +export const ActionButtons: React.FC = ({ + showVisibility = true, + showDelete = true, + showOpacity = true, + isHidden = false, + opacity = 100, + onToggleVisibility, + onDelete, + onOpacityChange, + dividerClassName, + className, +}) => { + return ( +
+ {showOpacity && onOpacityChange && ( + + )} + + {(showOpacity || showVisibility || showDelete) && ( + + )} + + {showVisibility && onToggleVisibility && ( + : } + variant="muted" + /> + )} + + {showDelete && onDelete && ( + } variant="muted" /> + )} +
+ ) +} diff --git a/src/shared/components/color-preview/color-preview.tsx b/src/shared/components/color-preview/color-preview.tsx new file mode 100644 index 0000000..a0aebfb --- /dev/null +++ b/src/shared/components/color-preview/color-preview.tsx @@ -0,0 +1,101 @@ +import React from 'react' +import { cn } from '../../utils/cn' + +export interface ColorPreviewProps { + /** + * Π¦Π²Π΅Ρ‚ для отобраТСния (любой Π²Π°Π»ΠΈΠ΄Π½Ρ‹ΠΉ CSS color) + */ + color: string + /** + * ΠŸΠΎΠΊΠ°Π·Ρ‹Π²Π°Ρ‚ΡŒ checkerboard Ρ„ΠΎΠ½ для прозрачности + */ + showCheckerboard?: boolean + /** + * Π Π°Π·ΠΌΠ΅Ρ€ preview (ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ 'size-5') + */ + size?: 'size-4' | 'size-5' | 'size-6' | 'size-8' + /** + * Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ классы + */ + className?: string + /** + * ΠžΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ ΠΊΠ»ΠΈΠΊΠ° + */ + onClick?: () => void + /** + * Disabled состояниС + */ + disabled?: boolean +} + +const CHECKERBOARD_PATTERN = + "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3E%3Cg fill='%23d1d1d1'%3E%3Cpath fill-rule='evenodd' d='M0 0h4v4H0V0zm4 4h4v4H4V4z'/%3E%3C/g%3E%3C/svg%3E\")" + +/** + * ΠšΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ для отобраТСния Ρ†Π²Π΅Ρ‚ΠΎΠ²ΠΎΠ³ΠΎ preview с ΠΎΠΏΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½Ρ‹ΠΌ checkerboard Ρ„ΠΎΠ½ΠΎΠΌ + */ +export const ColorPreview = React.forwardRef< + HTMLButtonElement, + ColorPreviewProps +>( + ( + { + color, + showCheckerboard = true, + size = 'size-5', + className, + onClick, + disabled = false, + }, + ref + ) => { + const content = ( + <> + {/* Checkerboard background для прозрачности */} + {showCheckerboard && ( +
+ )} + + {/* Color overlay */} +
+ + ) + + const baseClasses = cn( + 'relative rounded-sm overflow-hidden bg-white flex-shrink-0', + size, + className + ) + + if (onClick) { + return ( + + ) + } + + return
{content}
+ } +) + +ColorPreview.displayName = 'ColorPreview' diff --git a/src/shared/components/divider/divider.tsx b/src/shared/components/divider/divider.tsx new file mode 100644 index 0000000..a0e785b --- /dev/null +++ b/src/shared/components/divider/divider.tsx @@ -0,0 +1,5 @@ +import { cn } from '../../utils/cn' + +export const Divider = ({ className }: { className?: string }) => { + return
+} diff --git a/src/shared/components/drag-button/drag-button.tsx b/src/shared/components/drag-button/drag-button.tsx new file mode 100644 index 0000000..59afcbb --- /dev/null +++ b/src/shared/components/drag-button/drag-button.tsx @@ -0,0 +1,67 @@ +import React from 'react' +import { cn } from '../../utils/cn' +import { INPUT_THEME_CLASSES } from '../../constants/input-theme' + +export interface DragButtonProps + extends Omit, 'children'> { + /** + * Π‘ΠΎΠ΄Π΅Ρ€ΠΆΠΈΠΌΠΎΠ΅ ΠΊΠ½ΠΎΠΏΠΊΠΈ (ΠΈΠΊΠΎΠ½ΠΊΠ°, тСкст, элСмСнт) + */ + children: React.ReactNode + /** + * Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ классы + */ + className?: string + /** + * Disabled состояниС для mouse events + */ + isDisabledMouseEvent?: boolean + /** + * ΠžΡ€ΠΈΠ΅Π½Ρ‚Π°Ρ†ΠΈΡ курсора ΠΏΡ€ΠΈ drag + */ + dragOrientation?: 'horizontal' | 'vertical' +} + +/** + * Кнопка с drag-to-change Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎΡΡ‚ΡŒΡŽ + * Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ Π² InputHex, InputHexWithPreview, InputNumberSelect + */ +export const DragButton = React.forwardRef( + ( + { + children, + className, + disabled = false, + isDisabledMouseEvent = false, + dragOrientation = 'horizontal', + ...props + }, + ref + ) => { + const cursorClass = + dragOrientation === 'horizontal' ? 'cursor-ew-resize' : 'cursor-ns-resize' + + return ( + + ) + } +) + +DragButton.displayName = 'DragButton' diff --git a/src/shared/components/icon-button/icon-button.tsx b/src/shared/components/icon-button/icon-button.tsx new file mode 100644 index 0000000..7e9ed9e --- /dev/null +++ b/src/shared/components/icon-button/icon-button.tsx @@ -0,0 +1,76 @@ +import React from 'react' +import { cn } from '../../utils/cn' + +export interface IconButtonProps + extends Omit, 'children'> { + /** + * Иконка для отобраТСния (React element ΠΈΠ»ΠΈ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚) + */ + icon: React.ReactNode + /** + * Π Π°Π·ΠΌΠ΅Ρ€ ΠΈΠΊΠΎΠ½ΠΊΠΈ (ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ 14) + */ + iconSize?: number + /** + * Π’Π°Ρ€ΠΈΠ°Π½Ρ‚ стиля + */ + variant?: 'default' | 'muted' | 'danger' + /** + * Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ классы + */ + className?: string +} + +/** + * ΠšΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ ΠΊΠ½ΠΎΠΏΠΊΠΈ с ΠΈΠΊΠΎΠ½ΠΊΠΎΠΉ для action buttons + */ +export const IconButton = React.forwardRef( + ( + { + icon, + iconSize = 14, + variant = 'muted', + className, + disabled = false, + ...props + }, + ref + ) => { + // ΠšΠ»ΠΎΠ½ΠΈΡ€ΡƒΠ΅ΠΌ ΠΈΠΊΠΎΠ½ΠΊΡƒ с Π½ΡƒΠΆΠ½Ρ‹ΠΌ Ρ€Π°Π·ΠΌΠ΅Ρ€ΠΎΠΌ, Ссли это React element + const iconElement = React.isValidElement(icon) + ? React.cloneElement(icon as React.ReactElement<{ size?: number }>, { + size: iconSize, + }) + : icon + + return ( + + ) + } +) + +IconButton.displayName = 'IconButton' diff --git a/src/shared/components/image-input/constants/theme.ts b/src/shared/components/image-input/constants/theme.ts new file mode 100644 index 0000000..5ad0da4 --- /dev/null +++ b/src/shared/components/image-input/constants/theme.ts @@ -0,0 +1,23 @@ +/** + * ΠžΠ±Ρ‰ΠΈΠ΅ theme классы для image input ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ΠΎΠ² + * Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ Π² InputImageSelect, InputUploadImage ΠΈ связанных ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Π°Ρ… + */ +export const IMAGE_INPUT_THEME_CLASSES = { + light: { + container: 'bg-white border-gray-300 focus-within:ring-blue-500', + text: 'text-gray-900', + textMuted: 'text-gray-600', + icon: 'text-gray-600', + preview: 'border-gray-300', + divider: 'bg-gray-300', + }, + dark: { + container: + 'dark:bg-gray-800 dark:border-gray-600 dark:focus-within:ring-blue-400', + text: 'dark:text-gray-200', + textMuted: 'dark:text-gray-400', + icon: 'dark:text-gray-300', + preview: 'dark:border-gray-600', + divider: 'dark:bg-gray-600', + }, +} as const diff --git a/src/shared/components/image-input/hooks/use-file-upload.ts b/src/shared/components/image-input/hooks/use-file-upload.ts new file mode 100644 index 0000000..1c5d659 --- /dev/null +++ b/src/shared/components/image-input/hooks/use-file-upload.ts @@ -0,0 +1,61 @@ +import { useRef, useState } from 'react' +import type { UseFileUploadReturn } from '../types' +import { readFileAsDataURL } from '../utils/file-reader' + +/** + * Π₯ΡƒΠΊ для управлСния Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΎΠΉ Ρ„Π°ΠΉΠ»ΠΎΠ² + * ΠžΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Π΅Ρ‚ Π²Ρ‹Π±ΠΎΡ€ Ρ„Π°ΠΉΠ»Π°, Ρ‡Ρ‚Π΅Π½ΠΈΠ΅ ΠΈ preview + * + * @param onImageChange - Callback ΠΏΡ€ΠΈ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΈ Ρ„Π°ΠΉΠ»Π° + * @param initialImageUrl - ΠΠ°Ρ‡Π°Π»ΡŒΠ½Ρ‹ΠΉ URL изобраТСния + * @param initialFileName - ΠΠ°Ρ‡Π°Π»ΡŒΠ½ΠΎΠ΅ имя Ρ„Π°ΠΉΠ»Π° + * @returns ΠžΠ±ΡŠΠ΅ΠΊΡ‚ с состояниСм ΠΈ ΠΌΠ΅Ρ‚ΠΎΠ΄Π°ΠΌΠΈ для Ρ€Π°Π±ΠΎΡ‚Ρ‹ с Ρ„Π°ΠΉΠ»Π°ΠΌΠΈ + */ +export const useFileUpload = ( + onImageChange?: (file: File | null) => void, + initialImageUrl?: string, + initialFileName?: string +): UseFileUploadReturn => { + const fileInputRef = useRef(null) + const [previewUrl, setPreviewUrl] = useState( + initialImageUrl + ) + const [fileName, setFileName] = useState(initialFileName) + + const handleFileChange = async (e: React.ChangeEvent) => { + const file = e.target.files?.[0] + + if (file) { + try { + const dataUrl = await readFileAsDataURL(file) + setPreviewUrl(dataUrl) + setFileName(file.name) + onImageChange?.(file) + } catch (error) { + console.error('Error reading file:', error) + onImageChange?.(null) + } + } + } + + const handleClick = () => { + fileInputRef.current?.click() + } + + const clearFile = () => { + setPreviewUrl(undefined) + setFileName(undefined) + if (fileInputRef.current) { + fileInputRef.current.value = '' + } + } + + return { + previewUrl, + fileName, + fileInputRef, + handleFileChange, + handleClick, + clearFile, + } +} diff --git a/src/shared/components/image-input/hooks/use-image-actions.ts b/src/shared/components/image-input/hooks/use-image-actions.ts new file mode 100644 index 0000000..ff95dc2 --- /dev/null +++ b/src/shared/components/image-input/hooks/use-image-actions.ts @@ -0,0 +1,47 @@ +import { useState } from 'react' +import type { UseImageActionsReturn } from '../types' + +/** + * Π₯ΡƒΠΊ для управлСния дСйствиями с ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ΠΌ (hide/delete) + * УправляСт состояниСм видимости ΠΈ ΠΎΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Π΅Ρ‚ ΡƒΠ΄Π°Π»Π΅Π½ΠΈΠ΅ + * + * @param onHideImage - Callback ΠΏΡ€ΠΈ скрытии/ΠΏΠΎΠΊΠ°Π·Π΅ изобраТСния + * @param onDeleteImage - Callback ΠΏΡ€ΠΈ ΡƒΠ΄Π°Π»Π΅Π½ΠΈΠΈ изобраТСния + * @param onClearState - Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ callback для очистки состояния (ΠΎΠΏΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎ) + * @returns ΠžΠ±ΡŠΠ΅ΠΊΡ‚ с состояниСм ΠΈ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠ°ΠΌΠΈ дСйствий + */ +export const useImageActions = ( + onHideImage?: (hidden: boolean) => void, + onDeleteImage?: () => void, + onClearState?: () => void +): UseImageActionsReturn => { + const [isHidden, setIsHidden] = useState(false) + const [isRotateButtonActive, setIsRotateButtonActive] = useState(true) + + const handleToggleHide = () => { + const newHiddenState = !isHidden + const newRotateButtonState = !isRotateButtonActive + setIsHidden(newHiddenState) + onHideImage?.(newHiddenState) + setIsRotateButtonActive(newRotateButtonState) + } + + const handleDelete = () => { + // БбрасываСм состояниС + setIsHidden(false) + setIsRotateButtonActive(false) + // Π’Ρ‹Π·Ρ‹Π²Π°Π΅ΠΌ Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½ΡƒΡŽ очистку состояния (Ссли ΠΏΠ΅Ρ€Π΅Π΄Π°Π½Π°) + onClearState?.() + + // Π’Ρ‹Π·Ρ‹Π²Π°Π΅ΠΌ внСшний callback + onDeleteImage?.() + } + + return { + isHidden, + handleToggleHide, + handleDelete, + isRotateButtonActive, + setIsRotateButtonActive, + } +} diff --git a/src/shared/components/image-input/hooks/use-image-state.ts b/src/shared/components/image-input/hooks/use-image-state.ts new file mode 100644 index 0000000..f734efa --- /dev/null +++ b/src/shared/components/image-input/hooks/use-image-state.ts @@ -0,0 +1,51 @@ +import { useEffect, useState } from 'react' +import type { UseImageStateReturn } from '../types' + +/** + * Π₯ΡƒΠΊ для управлСния состояниСм изобраТСния + * Π‘ΠΈΠ½Ρ…Ρ€ΠΎΠ½ΠΈΠ·ΠΈΡ€ΡƒΠ΅Ρ‚ Π²Π½ΡƒΡ‚Ρ€Π΅Π½Π½Π΅Π΅ состояниС с внСшними пропсами + * + * @param initialImageUrl - ΠΠ°Ρ‡Π°Π»ΡŒΠ½Ρ‹ΠΉ URL изобраТСния + * @param initialOpacity - ΠΠ°Ρ‡Π°Π»ΡŒΠ½Π°Ρ ΠΏΡ€ΠΎΠ·Ρ€Π°Ρ‡Π½ΠΎΡΡ‚ΡŒ (0-100) + * @param initialRotation - ΠΠ°Ρ‡Π°Π»ΡŒΠ½Ρ‹ΠΉ ΡƒΠ³ΠΎΠ» ΠΏΠΎΠ²ΠΎΡ€ΠΎΡ‚Π° (0-360) + * @returns ΠžΠ±ΡŠΠ΅ΠΊΡ‚ с состояниСм ΠΈ ΠΌΠ΅Ρ‚ΠΎΠ΄Π°ΠΌΠΈ управлСния + */ +export const useImageState = ( + initialImageUrl?: string, + initialOpacity: number = 100, + initialRotation: number = 0 +): UseImageStateReturn => { + const [imageUrl, setImageUrl] = useState(initialImageUrl) + const [isHidden, setIsHidden] = useState(false) + const [opacity, setOpacity] = useState(initialOpacity) + const [rotation, setRotation] = useState(initialRotation) + + // Бинхронизация с внСшними пропсами + useEffect(() => { + setImageUrl(initialImageUrl) + }, [initialImageUrl]) + + useEffect(() => { + setOpacity(initialOpacity) + }, [initialOpacity]) + + useEffect(() => { + setRotation(initialRotation) + }, [initialRotation]) + + const toggleHidden = () => { + setIsHidden(prev => !prev) + } + + return { + imageUrl, + isHidden, + opacity, + rotation, + setImageUrl, + setIsHidden, + setOpacity, + setRotation, + toggleHidden, + } +} diff --git a/src/shared/components/image-input/index.ts b/src/shared/components/image-input/index.ts new file mode 100644 index 0000000..6c673cc --- /dev/null +++ b/src/shared/components/image-input/index.ts @@ -0,0 +1,31 @@ +/** + * ΠžΠ±Ρ‰Π°Ρ инфраструктура для image input ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ΠΎΠ² + * Экспорт констант, Ρ‚ΠΈΠΏΠΎΠ², Ρ…ΡƒΠΊΠΎΠ² ΠΈ ΡƒΡ‚ΠΈΠ»ΠΈΡ‚ + */ + +// Constants +export { IMAGE_INPUT_THEME_CLASSES } from './constants/theme' + +// Types +export type { + BaseImageInputProps, + ImageInputWithOpacityProps, + ImageInputWithFileNameProps, + ImageState, + UseImageStateReturn, + UseFileUploadReturn, + UseImageActionsReturn, +} from './types' + +// Hooks +export { useImageState } from './hooks/use-image-state' +export { useFileUpload } from './hooks/use-file-upload' +export { useImageActions } from './hooks/use-image-actions' + +// Utils +export { + readFileAsDataURL, + isImageFile, + getFileExtension, +} from './utils/file-reader' +export { getFileNameDisplay, truncateFileName } from './utils/file-name' diff --git a/src/shared/components/image-input/types/index.ts b/src/shared/components/image-input/types/index.ts new file mode 100644 index 0000000..03b0cb2 --- /dev/null +++ b/src/shared/components/image-input/types/index.ts @@ -0,0 +1,78 @@ +/** + * Π‘Π°Π·ΠΎΠ²Ρ‹Π΅ Ρ‚ΠΈΠΏΡ‹ ΠΈ интСрфСйсы для image input ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ΠΎΠ² + */ + +/** + * Π‘Π°Π·ΠΎΠ²Ρ‹Π΅ пропсы для всСх image input ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ΠΎΠ² + */ +export interface BaseImageInputProps { + title?: string + className?: string + imageUrl?: string + onImageChange?: (file: File | null) => void + onHideImage?: (hidden: boolean) => void + onDeleteImage?: () => void +} + +/** + * ΠŸΡ€ΠΎΠΏΡΡ‹ для ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ΠΎΠ² с opacity ΠΊΠΎΠ½Ρ‚Ρ€ΠΎΠ»ΠΎΠΌ + */ +export interface ImageInputWithOpacityProps extends BaseImageInputProps { + opacity?: number + onOpacityChange?: (opacity: number) => void +} + +/** + * ΠŸΡ€ΠΎΠΏΡΡ‹ для ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ΠΎΠ² с ΠΎΡ‚ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ΠΌ ΠΈΠΌΠ΅Π½ΠΈ Ρ„Π°ΠΉΠ»Π° + */ +export interface ImageInputWithFileNameProps extends BaseImageInputProps { + fileName?: string +} + +/** + * БостояниС изобраТСния + */ +export interface ImageState { + url?: string + isHidden: boolean + opacity: number + rotation: number +} + +/** + * Π Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ Ρ…ΡƒΠΊΠ° useImageState + */ +export interface UseImageStateReturn { + imageUrl?: string + isHidden: boolean + opacity: number + rotation: number + setImageUrl: (url?: string) => void + setIsHidden: (hidden: boolean) => void + setOpacity: (opacity: number) => void + setRotation: (rotation: number) => void + toggleHidden: () => void +} + +/** + * Π Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ Ρ…ΡƒΠΊΠ° useFileUpload + */ +export interface UseFileUploadReturn { + previewUrl?: string + fileName?: string + fileInputRef: React.RefObject + handleFileChange: (e: React.ChangeEvent) => void + handleClick: () => void + clearFile: () => void +} + +/** + * Π Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ Ρ…ΡƒΠΊΠ° useImageActions + */ +export interface UseImageActionsReturn { + isHidden: boolean + handleToggleHide: () => void + handleDelete: () => void + isRotateButtonActive: boolean + setIsRotateButtonActive: (active: boolean) => void +} diff --git a/src/shared/components/image-input/utils/file-name.ts b/src/shared/components/image-input/utils/file-name.ts new file mode 100644 index 0000000..4024ad9 --- /dev/null +++ b/src/shared/components/image-input/utils/file-name.ts @@ -0,0 +1,50 @@ +/** + * Π£Ρ‚ΠΈΠ»ΠΈΡ‚Ρ‹ для Ρ€Π°Π±ΠΎΡ‚Ρ‹ с ΠΈΠΌΠ΅Π½Π°ΠΌΠΈ Ρ„Π°ΠΉΠ»ΠΎΠ² + */ + +/** + * ΠžΠΏΡ€Π΅Π΄Π΅Π»ΡΠ΅Ρ‚, ΠΊΠ°ΠΊΠΎΠ΅ имя Ρ„Π°ΠΉΠ»Π° ΠΎΡ‚ΠΎΠ±Ρ€Π°ΠΆΠ°Ρ‚ΡŒ + * @param currentFileName - Π’Π΅ΠΊΡƒΡ‰Π΅Π΅ имя Ρ„Π°ΠΉΠ»Π° (ΠΈΠ· состояния) + * @param initialFileName - ΠΠ°Ρ‡Π°Π»ΡŒΠ½ΠΎΠ΅ имя Ρ„Π°ΠΉΠ»Π° (ΠΈΠ· пропсов) + * @param imageUrl - URL изобраТСния + * @param fallbackTitle - Fallback тСкст, Ссли Π½Π΅Ρ‚ Ρ„Π°ΠΉΠ»Π° + * @returns Π‘Ρ‚Ρ€ΠΎΠΊΠ° для отобраТСния + */ +export const getFileNameDisplay = ( + currentFileName?: string, + initialFileName?: string, + imageUrl?: string, + fallbackTitle: string = 'Upload image' +): string => { + if (currentFileName) { + return currentFileName + } + if (imageUrl && initialFileName) { + return initialFileName + } + return fallbackTitle +} + +/** + * ΠžΠ±Ρ€Π΅Π·Π°Π΅Ρ‚ Π΄Π»ΠΈΠ½Π½ΠΎΠ΅ имя Ρ„Π°ΠΉΠ»Π° для отобраТСния + * @param fileName - Имя Ρ„Π°ΠΉΠ»Π° + * @param maxLength - Максимальная Π΄Π»ΠΈΠ½Π° + * @returns ΠžΠ±Ρ€Π΅Π·Π°Π½Π½ΠΎΠ΅ имя Ρ„Π°ΠΉΠ»Π° + */ +export const truncateFileName = ( + fileName: string, + maxLength: number = 30 +): string => { + if (fileName.length <= maxLength) { + return fileName + } + + const extension = fileName.split('.').pop() || '' + const nameWithoutExt = fileName.slice(0, fileName.lastIndexOf('.')) + const truncatedName = nameWithoutExt.slice( + 0, + maxLength - extension.length - 4 + ) + + return `${truncatedName}...${extension}` +} diff --git a/src/shared/components/image-input/utils/file-reader.ts b/src/shared/components/image-input/utils/file-reader.ts new file mode 100644 index 0000000..ebb508b --- /dev/null +++ b/src/shared/components/image-input/utils/file-reader.ts @@ -0,0 +1,47 @@ +/** + * Π£Ρ‚ΠΈΠ»ΠΈΡ‚Ρ‹ для Ρ€Π°Π±ΠΎΡ‚Ρ‹ с Ρ„Π°ΠΉΠ»Π°ΠΌΠΈ + */ + +/** + * Π§ΠΈΡ‚Π°Π΅Ρ‚ Ρ„Π°ΠΉΠ» ΠΈ Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ Π΅Π³ΠΎ ΠΊΠ°ΠΊ Data URL + * @param file - Π€Π°ΠΉΠ» для чтСния + * @returns Promise с Data URL строкой + */ +export const readFileAsDataURL = (file: File): Promise => { + return new Promise((resolve, reject) => { + const reader = new FileReader() + + reader.onloadend = () => { + if (typeof reader.result === 'string') { + resolve(reader.result) + } else { + reject(new Error('Failed to read file as string')) + } + } + + reader.onerror = () => { + reject(new Error('Error reading file')) + } + + reader.readAsDataURL(file) + }) +} + +/** + * ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅Ρ‚, являСтся Π»ΠΈ Ρ„Π°ΠΉΠ» ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ΠΌ + * @param file - Π€Π°ΠΉΠ» для ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ + * @returns true Ссли Ρ„Π°ΠΉΠ» - ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ + */ +export const isImageFile = (file: File): boolean => { + return file.type.startsWith('image/') +} + +/** + * ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠ΅ Ρ„Π°ΠΉΠ»Π° + * @param fileName - Имя Ρ„Π°ΠΉΠ»Π° + * @returns Π Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠ΅ Ρ„Π°ΠΉΠ»Π° (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, 'jpg', 'png') + */ +export const getFileExtension = (fileName: string): string => { + const parts = fileName.split('.') + return parts.length > 1 ? parts[parts.length - 1].toLowerCase() : '' +} diff --git a/src/shared/components/image-preview/image-preview.tsx b/src/shared/components/image-preview/image-preview.tsx new file mode 100644 index 0000000..c24da0e --- /dev/null +++ b/src/shared/components/image-preview/image-preview.tsx @@ -0,0 +1,99 @@ +import React from 'react' +import { cn } from '../../utils/cn' + +export interface ImagePreviewProps { + /** + * URL изобраТСния для отобраТСния + */ + imageUrl?: string + /** + * Alt тСкст для изобраТСния + */ + alt?: string + /** + * ΠŸΡ€ΠΎΠ·Ρ€Π°Ρ‡Π½ΠΎΡΡ‚ΡŒ изобраТСния (0-100) + */ + opacity?: number + /** + * Π Π°Π·ΠΌΠ΅Ρ€ preview (ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ 'size-5') + */ + size?: 'size-4' | 'size-5' | 'size-6' | 'size-8' + /** + * Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ классы + */ + className?: string + /** + * ΠžΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ ΠΊΠ»ΠΈΠΊΠ° + */ + onClick?: () => void + /** + * Disabled состояниС + */ + disabled?: boolean + /** + * Π¦Π²Π΅Ρ‚ placeholder ΠΊΠΎΠ³Π΄Π° Π½Π΅Ρ‚ изобраТСния + */ + placeholderColor?: string +} + +/** + * ΠšΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ для отобраТСния preview изобраТСния с placeholder + */ +export const ImagePreview = React.forwardRef< + HTMLButtonElement, + ImagePreviewProps +>( + ( + { + imageUrl, + alt = 'Preview', + opacity = 100, + size = 'size-5', + className, + onClick, + disabled = false, + placeholderColor = 'bg-gray-200 dark:bg-gray-700', + }, + ref + ) => { + const content = imageUrl ? ( + {alt} + ) : ( +
+ ) + + const baseClasses = cn( + 'relative rounded-sm overflow-hidden bg-white flex-shrink-0', + size, + className + ) + + if (onClick) { + return ( + + ) + } + + return
{content}
+ } +) + +ImagePreview.displayName = 'ImagePreview' diff --git a/src/shared/components/input-container/input-container.tsx b/src/shared/components/input-container/input-container.tsx new file mode 100644 index 0000000..14c98de --- /dev/null +++ b/src/shared/components/input-container/input-container.tsx @@ -0,0 +1,67 @@ +import React from 'react' +import { cn } from '../../utils/cn' +import { INPUT_THEME_CLASSES } from '../../constants/input-theme' + +export interface InputContainerProps { + /** + * Π”ΠΎΡ‡Π΅Ρ€Π½ΠΈΠ΅ элСмСнты + */ + children: React.ReactNode + /** + * Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ классы + */ + className?: string + /** + * Высота ΠΊΠΎΠ½Ρ‚Π΅ΠΉΠ½Π΅Ρ€Π° (ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ h-8) + */ + height?: 'h-6' | 'h-8' | 'h-10' | 'h-12' + /** + * Π’Π°Ρ€ΠΈΠ°Π½Ρ‚ Π³Ρ€Π°Π½ΠΈΡ†Ρ‹ + */ + borderVariant?: 'default' | 'thick' + /** + * ΠŸΠΎΠΊΠ°Π·Ρ‹Π²Π°Ρ‚ΡŒ focus ring + */ + showFocusRing?: boolean +} + +/** + * Π£Π½ΠΈΠ²Π΅Ρ€ΡΠ°Π»ΡŒΠ½Ρ‹ΠΉ ΠΊΠΎΠ½Ρ‚Π΅ΠΉΠ½Π΅Ρ€ для input ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ΠΎΠ² с Ρ‚Π΅ΠΌΠΈΠ·Π°Ρ†ΠΈΠ΅ΠΉ + */ +export const InputContainer = React.forwardRef< + HTMLDivElement, + InputContainerProps +>( + ( + { + children, + className, + height = 'h-8', + borderVariant = 'thick', + showFocusRing = true, + }, + ref + ) => { + return ( +
+ {children} +
+ ) + } +) + +InputContainer.displayName = 'InputContainer' diff --git a/src/shared/components/input-select-modal/input-select-modal.tsx b/src/shared/components/input-select-modal/input-select-modal.tsx new file mode 100644 index 0000000..75314f8 --- /dev/null +++ b/src/shared/components/input-select-modal/input-select-modal.tsx @@ -0,0 +1,90 @@ +import { X } from 'lucide-react' +import { useDraggable } from '../../hooks/use-draggable' +import { cn } from '../../utils/cn' + +const THEME_CLASSES = { + light: { + modal: 'bg-white border-1 border-gray-300', + title: 'text-gray-900', + closeButton: 'text-gray-400 hover:text-gray-600', + }, + dark: { + modal: 'dark:bg-[#1e2939] dark:border-gray-600', + title: 'dark:text-gray-200', + closeButton: 'dark:text-gray-500 dark:hover:text-gray-200', + }, +} as const + +interface InputSelectModalProps { + title: string + onClose: () => void + children: React.ReactNode + inputRef: React.RefObject + className?: string + classNameHeader?: string + classNameTitle?: string + classNameCloseButton?: string + classNameContent?: string +} + +export function InputSelectModal({ + title, + onClose, + children, + inputRef, + className, + classNameHeader, + classNameTitle, + classNameCloseButton, + classNameContent, +}: InputSelectModalProps) { + const { position, handleDragStart } = useDraggable() + + return ( +
+
handleDragStart(e, inputRef.current)} + > + + {title} + + +
+ +
{children}
+
+ ) +} diff --git a/src/shared/components/opacity-drag-control/opacity-drag-control.tsx b/src/shared/components/opacity-drag-control/opacity-drag-control.tsx new file mode 100644 index 0000000..8972133 --- /dev/null +++ b/src/shared/components/opacity-drag-control/opacity-drag-control.tsx @@ -0,0 +1,77 @@ +import { useState } from 'react' +import { cn } from '../../utils/cn' + +interface OpacityDragControlProps { + opacity: number + onChange: (value: number) => void + className?: string +} + +export const OpacityDragControl = ({ + opacity, + onChange, + className, +}: OpacityDragControlProps) => { + const [isDragging, setIsDragging] = useState(false) + + const handlePointerDown = (e: React.PointerEvent) => { + e.preventDefault() + const target = e.currentTarget + target.setPointerCapture(e.pointerId) + + const styleElement = document.createElement('style') + styleElement.id = 'opacity-dragging-cursor' + styleElement.innerHTML = ` + body, body * { + cursor: ew-resize !important; + user-select: none !important; + } + ` + document.head.appendChild(styleElement) + + const startX = e.clientX + const startOpacity = opacity + setIsDragging(true) + + const handlePointerMove = (event: PointerEvent) => { + const deltaX = event.clientX - startX + const step = 0.5 + let newOpacity = Math.round(startOpacity + deltaX * step) + newOpacity = Math.max(0, Math.min(100, newOpacity)) + onChange(newOpacity) + } + + const handlePointerUp = (event: PointerEvent) => { + target.releasePointerCapture(event.pointerId) + setIsDragging(false) + + const styleToRemove = document.getElementById('opacity-dragging-cursor') + if (styleToRemove) { + styleToRemove.remove() + } + + target.removeEventListener('pointermove', handlePointerMove) + document.removeEventListener('pointerup', handlePointerUp) + } + + target.addEventListener('pointermove', handlePointerMove) + document.addEventListener('pointerup', handlePointerUp) + } + + return ( + + ) +} diff --git a/src/shared/components/text-button/text-button.tsx b/src/shared/components/text-button/text-button.tsx new file mode 100644 index 0000000..7855a5e --- /dev/null +++ b/src/shared/components/text-button/text-button.tsx @@ -0,0 +1,62 @@ +import React from 'react' +import { cn } from '../../utils/cn' + +export interface TextButtonProps + extends Omit, 'children'> { + /** + * ВСкст для отобраТСния + */ + children: React.ReactNode + /** + * Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ классы + */ + className?: string + /** + * ΠšΠ»Π°ΡΡΡ‹ для тСкста (light theme) + */ + textLightClass?: string + /** + * ΠšΠ»Π°ΡΡΡ‹ для тСкста (dark theme) + */ + textDarkClass?: string +} + +/** + * ВСкстовая ΠΊΠ½ΠΎΠΏΠΊΠ° для отобраТСния ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ с Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡ‚ΡŒΡŽ ΠΊΠ»ΠΈΠΊΠ° + * Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ для gradient text, filename, labels + */ +export const TextButton = React.forwardRef( + ( + { + children, + className, + textLightClass = 'text-gray-700', + textDarkClass = 'dark:text-gray-300', + disabled = false, + ...props + }, + ref + ) => { + return ( + + ) + } +) + +TextButton.displayName = 'TextButton' diff --git a/src/shared/constants/input-theme.ts b/src/shared/constants/input-theme.ts new file mode 100644 index 0000000..399c04e --- /dev/null +++ b/src/shared/constants/input-theme.ts @@ -0,0 +1,27 @@ +/** + * ΠžΠ±Ρ‰ΠΈΠ΅ theme классы для input ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ΠΎΠ² + * Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ Π² InputNumberSelect, InputHex, InputHexWithPreview, InputColorPicker + */ +export const INPUT_THEME_CLASSES = { + light: { + container: 'bg-white border-gray-300 focus-within:ring-blue-500', + input: 'text-gray-900', + text: 'text-gray-900', + textMuted: 'text-gray-600', + icon: 'bg-transparent hover:bg-transparent', + dragArea: 'bg-gray-100 hover:bg-gray-200', + preview: 'border-gray-300', + divider: 'bg-gray-300', + }, + dark: { + container: + 'dark:bg-gray-800 dark:border-gray-600 dark:focus-within:ring-blue-400', + input: 'dark:text-gray-100', + text: 'dark:text-gray-200', + textMuted: 'dark:text-gray-400', + icon: 'dark:text-gray-300 dark:bg-transparent dark:hover:bg-transparent', + dragArea: 'dark:bg-gray-700 dark:hover:bg-gray-600', + preview: 'dark:border-gray-600', + divider: 'dark:bg-gray-600', + }, +} as const diff --git a/src/shared/hooks/use-draggable-input.ts b/src/shared/hooks/use-draggable-input.ts new file mode 100644 index 0000000..a2ebe6a --- /dev/null +++ b/src/shared/hooks/use-draggable-input.ts @@ -0,0 +1,101 @@ +import { useRef } from 'react' + +export type DragOrientation = 'horizontal' | 'vertical' + +export interface UseDraggableInputOptions { + /** + * ΠžΡ€ΠΈΠ΅Π½Ρ‚Π°Ρ†ΠΈΡ Π΄Ρ€Π°Π³Π° (horizontal ΠΈΠ»ΠΈ vertical) + */ + orientation?: DragOrientation + /** + * Ѐункция для вычислСния Π½ΠΎΠ²ΠΎΠ³ΠΎ значСния Π½Π° основС двиТСния + */ + onDragChange: (delta: number, startValue: number) => void + /** + * Callback ΠΏΡ€ΠΈ Π½Π°Ρ‡Π°Π»Π΅ Π΄Ρ€Π°Π³Π° + */ + onDragStart?: () => void + /** + * Callback ΠΏΡ€ΠΈ ΠΎΠΊΠΎΠ½Ρ‡Π°Π½ΠΈΠΈ Π΄Ρ€Π°Π³Π° + */ + onDragEnd?: () => void + /** + * ΠžΡ‚ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ drag + */ + disabled?: boolean +} + +/** + * Π₯ΡƒΠΊ для Ρ€Π΅Π°Π»ΠΈΠ·Π°Ρ†ΠΈΠΈ drag-to-change Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΎΠ½Π°Π»Π° Π² input ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Π°Ρ… + * Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ Π² InputNumberSelect, InputHex, InputHexWithPreview + */ +export const useDraggableInput = ({ + orientation = 'horizontal', + onDragChange, + onDragStart, + onDragEnd, + disabled = false, +}: UseDraggableInputOptions) => { + const dragStartValueRef = useRef(0) + const startPositionRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 }) + + const handlePointerDown = ( + e: React.PointerEvent, + currentValue: number + ) => { + if (disabled) return + e.preventDefault() + + const target = e.currentTarget + target.setPointerCapture(e.pointerId) + + // Π‘ΠΎΠ·Π΄Π°Ρ‘ΠΌ динамичСский ΡΡ‚ΠΈΠ»ΡŒ для курсора + const styleElement = document.createElement('style') + styleElement.id = 'dragging-cursor-style' + const cursorType = orientation === 'vertical' ? 'ns-resize' : 'ew-resize' + styleElement.innerHTML = ` + body, body * { + cursor: ${cursorType} !important; + user-select: none !important; + } + ` + document.head.appendChild(styleElement) + + // БохраняСм Π½Π°Ρ‡Π°Π»ΡŒΠ½Ρ‹Π΅ значСния + dragStartValueRef.current = currentValue + startPositionRef.current = { x: e.clientX, y: e.clientY } + + onDragStart?.() + + const handlePointerMove = (event: PointerEvent) => { + const deltaX = event.clientX - startPositionRef.current.x + const deltaY = startPositionRef.current.y - event.clientY // Π˜Π½Π²Π΅Ρ€Ρ‚ΠΈΡ€ΡƒΠ΅ΠΌ для vertical + + const delta = orientation === 'vertical' ? deltaY : deltaX + + onDragChange(delta, dragStartValueRef.current) + } + + const handlePointerUp = (event: PointerEvent) => { + target.releasePointerCapture(event.pointerId) + + // УдаляСм ΡΡ‚ΠΈΠ»ΡŒ курсора + const styleToRemove = document.getElementById('dragging-cursor-style') + if (styleToRemove) { + styleToRemove.remove() + } + + target.removeEventListener('pointermove', handlePointerMove) + document.removeEventListener('pointerup', handlePointerUp) + + onDragEnd?.() + } + + target.addEventListener('pointermove', handlePointerMove) + document.addEventListener('pointerup', handlePointerUp) + } + + return { + handlePointerDown, + } +} diff --git a/src/input-color-picker/hooks/use-draggable.ts b/src/shared/hooks/use-draggable.ts similarity index 94% rename from src/input-color-picker/hooks/use-draggable.ts rename to src/shared/hooks/use-draggable.ts index f6fd022..fa666a6 100644 --- a/src/input-color-picker/hooks/use-draggable.ts +++ b/src/shared/hooks/use-draggable.ts @@ -1,5 +1,5 @@ import { useRef, useState } from 'react' -import type { DragStart, Position } from '../types' +import type { DragStart, Position } from '../../input-color-picker/types' export const useDraggable = () => { const [position, setPosition] = useState({ diff --git a/src/shared/hooks/use-hex-input.ts b/src/shared/hooks/use-hex-input.ts new file mode 100644 index 0000000..788c4dd --- /dev/null +++ b/src/shared/hooks/use-hex-input.ts @@ -0,0 +1,97 @@ +import { useEffect, useState } from 'react' +import tc from 'tinycolor2' +import { convertToHex8, filterHexInput } from '../utils/color-utils' + +export interface UseHexInputOptions { + /** + * Π’Ρ…ΠΎΠ΄Π½ΠΎΠ΅ hex Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ (ΠΌΠΎΠΆΠ΅Ρ‚ Π±Ρ‹Ρ‚ΡŒ с # ΠΈΠ»ΠΈ Π±Π΅Π·) + */ + hexColor: string + /** + * Callback ΠΏΡ€ΠΈ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΈ hex значСния + */ + onChange: (hex: string) => void + /** + * ΠŸΠΎΠΊΠ°Π·Ρ‹Π²Π°Ρ‚ΡŒ ΠΈ Ρ€Π°Π·Ρ€Π΅ΡˆΠ°Ρ‚ΡŒ Π²Π²ΠΎΠ΄ alpha ΠΊΠ°Π½Π°Π»Π° (Π΅Ρ‰Π΅ 2 hex символа) + * По ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ false (Ρ‚ΠΎΠ»ΡŒΠΊΠΎ 6 символов) + */ + showAlpha?: boolean +} + +export interface UseHexInputReturn { + /** + * Π›ΠΎΠΊΠ°Π»ΡŒΠ½ΠΎΠ΅ hex Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ (Π±Π΅Π· #) + */ + localHex: string + /** + * Π€Π»Π°Π³ рСдактирования + */ + isEditing: boolean + /** + * ΠžΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ измСнСния input + */ + handleHexInput: (e: React.ChangeEvent) => void + /** + * Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ Ρ„Π»Π°Π³ рСдактирования + */ + setIsEditing: (editing: boolean) => void + /** + * Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ локальноС hex Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ Π½Π°ΠΏΡ€ΡΠΌΡƒΡŽ (для drag-Π»ΠΎΠ³ΠΈΠΊΠΈ) + */ + setLocalHex: (hex: string) => void +} + +/** + * Π₯ΡƒΠΊ для управлСния hex input с ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠΎΠΉ alpha ΠΊΠ°Π½Π°Π»Π° + * Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ Π² InputHex ΠΈ InputHexWithPreview + */ +export const useHexInput = ({ + hexColor, + onChange, + showAlpha = false, +}: UseHexInputOptions): UseHexInputReturn => { + const [isEditing, setIsEditing] = useState(false) + + // Нормализация Π²Ρ…ΠΎΠ΄Π½ΠΎΠ³ΠΎ значСния Ρ‡Π΅Ρ€Π΅Π· tinycolor2 + const color = tc(hexColor) + const hex = color.toHex() // 6 символов Π±Π΅Π· # + const alpha = color.getAlpha() // 0-1 + const alphaHex = Math.round(alpha * 255) + .toString(16) + .padStart(2, '0') + + // ПолноС Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅: Ссли showAlpha ΠΈ alpha < 1, добавляСм alpha + const hexFromProp = showAlpha && alpha < 1 ? hex + alphaHex : hex + + const [localHex, setLocalHex] = useState(hexFromProp) + + // Бинхронизация с props, ΠΊΠΎΠ³Π΄Π° Π½Π΅ Ρ€Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΡƒΠ΅ΠΌ + useEffect(() => { + if (!isEditing) { + setLocalHex(hexFromProp) + } + }, [hexColor, isEditing, hexFromProp]) + + const handleHexInput = (e: React.ChangeEvent) => { + const maxLen = showAlpha ? 8 : 6 + // Π€ΠΈΠ»ΡŒΡ‚Ρ€ΡƒΠ΅ΠΌ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ hex символы, ΠΎΠ±Ρ€Π΅Π·Π°Π΅ΠΌ ΠΏΠΎ Π΄Π»ΠΈΠ½Π΅ + const filtered = filterHexInput(e.target.value, maxLen) + + setLocalHex(filtered) + + // ΠžΡ‚ΠΏΡ€Π°Π²Π»ΡΠ΅ΠΌ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠ΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΊΠΎΠ³Π΄Π° полная Π΄Π»ΠΈΠ½Π° + if (filtered.length === 6) { + onChange(`#${filtered}`) + } else if (filtered.length === 8 && showAlpha) { + onChange(convertToHex8(filtered)) + } + } + + return { + localHex, + isEditing, + handleHexInput, + setIsEditing, + setLocalHex, + } +} diff --git a/src/shared/index.ts b/src/shared/index.ts index 9e416a9..36a3da2 100644 --- a/src/shared/index.ts +++ b/src/shared/index.ts @@ -1,2 +1,53 @@ export { cn } from './utils/cn' export { removeTrailingZeros } from './utils/remove-trailing-zeros' +export { INPUT_THEME_CLASSES } from './constants/input-theme' +export { + ColorPreview, + type ColorPreviewProps, +} from './components/color-preview/color-preview' +export { + ImagePreview, + type ImagePreviewProps, +} from './components/image-preview/image-preview' +export { + IconButton, + type IconButtonProps, +} from './components/icon-button/icon-button' +export { + InputContainer, + type InputContainerProps, +} from './components/input-container/input-container' +export { + ActionButtons, + type ActionButtonsProps, +} from './components/action-buttons/action-buttons' +export { + DragButton, + type DragButtonProps, +} from './components/drag-button/drag-button' +export { + TextButton, + type TextButtonProps, +} from './components/text-button/text-button' +export { + useDraggableInput, + type DragOrientation, + type UseDraggableInputOptions, +} from './hooks/use-draggable-input' +export { + useHexInput, + type UseHexInputOptions, + type UseHexInputReturn, +} from './hooks/use-hex-input' +export { + convertToHex8, + opacityToHex, + alphaToHex, + filterHexInput, + parseColor, + rgbToHex, + hexToRgb, + createDisplayColor, + type RGB, + type ParsedColor, +} from './utils/color-utils' diff --git a/src/shared/utils/color-utils.ts b/src/shared/utils/color-utils.ts new file mode 100644 index 0000000..9917538 --- /dev/null +++ b/src/shared/utils/color-utils.ts @@ -0,0 +1,211 @@ +import tc from 'tinycolor2' + +export interface RGB { + r: number + g: number + b: number +} + +export interface ParsedColor { + hex: string + opacity: number +} + +/** + * ΠšΠΎΠ½Π²Π΅Ρ€Ρ‚ΠΈΡ€ΡƒΠ΅Ρ‚ 8-ΡΠΈΠΌΠ²ΠΎΠ»ΡŒΠ½Ρ‹ΠΉ hex (с alpha) Π² Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ с alpha ΠΊΠ°Π½Π°Π»ΠΎΠΌ + * @param hex8 - 8-ΡΠΈΠΌΠ²ΠΎΠ»ΡŒΠ½Ρ‹ΠΉ hex Π±Π΅Π· #, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€ "FF0000FF" + * @returns Hex8 строка Π² Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅ #RRGGBBAA + */ +export const convertToHex8 = (hex8: string): string => { + const base = hex8.slice(0, 6) + const alphaHex = hex8.slice(6, 8) + const alphaDecimal = parseInt(alphaHex, 16) / 255 + return tc(`#${base}`).setAlpha(alphaDecimal).toHex8String().toUpperCase() +} + +/** + * ΠŸΠ°Ρ€ΡΠΈΡ‚ строку Ρ†Π²Π΅Ρ‚Π° Π² HEX ΠΈ ΠΏΡ€ΠΎΠ·Ρ€Π°Ρ‡Π½ΠΎΡΡ‚ΡŒ + * @param colorStr - Π‘Ρ‚Ρ€ΠΎΠΊΠ° Ρ†Π²Π΅Ρ‚Π° (hex, rgb, rgba, gradient) + * @returns ΠžΠ±ΡŠΠ΅ΠΊΡ‚ с hex ΠΈ opacity + */ +export const parseColor = (colorStr: string): ParsedColor => { + if (!colorStr) return { hex: '------', opacity: 100 } + + // ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° Π³Ρ€Π°Π΄ΠΈΠ΅Π½Ρ‚ΠΎΠ² + if (colorStr.includes('gradient')) { + return { hex: 'Linear', opacity: 100 } + } + + // ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° HEX + if (colorStr.startsWith('#')) { + return { hex: colorStr.toUpperCase(), opacity: 100 } + } + + // ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° RGB/RGBA + const rgbaMatch = colorStr.match( + /rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/ + ) + if (rgbaMatch) { + const r = parseInt(rgbaMatch[1]) + const g = parseInt(rgbaMatch[2]) + const b = parseInt(rgbaMatch[3]) + const a = rgbaMatch[4] !== undefined ? parseFloat(rgbaMatch[4]) : 1 + const componentToHex = (c: number) => { + const hex = c.toString(16) + return hex.length == 1 ? '0' + hex : hex + } + const hex = '#' + componentToHex(r) + componentToHex(g) + componentToHex(b) + return { hex: hex.toUpperCase(), opacity: Math.round(a * 100) } + } + + // Fallback для Π½Π΅Π²Π°Π»ΠΈΠ΄Π½Ρ‹Ρ… Ρ†Π²Π΅Ρ‚ΠΎΠ² + return { hex: colorStr, opacity: 100 } +} + +/** + * ΠšΠΎΠ½Π²Π΅Ρ€Ρ‚ΠΈΡ€ΡƒΠ΅Ρ‚ RGB строку Π² HEX + * @param rgbStr - RGB/RGBA строка + * @returns HEX строка + */ +export const rgbToHex = (rgbStr: string): string => { + if (!rgbStr) return '#000000' + + // Если ΡƒΠΆΠ΅ HEX, Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌ ΠΊΠ°ΠΊ Π΅ΡΡ‚ΡŒ + if (rgbStr.startsWith('#')) return rgbStr.toUpperCase() + + // ΠŸΠ°Ρ€ΡΠΈΠΌ rgb(r,g,b) ΠΈΠ»ΠΈ rgba(r,g,b,a) + const match = rgbStr.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\)/) + + if (match) { + const r = parseInt(match[1], 10) + const g = parseInt(match[2], 10) + const b = parseInt(match[3], 10) + + const toHex = (c: number) => ('0' + c.toString(16)).slice(-2) + + return `#${toHex(r)}${toHex(g)}${toHex(b)}`.toUpperCase() + } + + return '#000000' +} + +/** + * ΠšΠΎΠ½Π²Π΅Ρ€Ρ‚ΠΈΡ€ΡƒΠ΅Ρ‚ HEX Π² RGB + * @param hexStr - HEX строка + * @returns RGB ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ ΠΈΠ»ΠΈ null + */ +export const hexToRgb = (hexStr: string): RGB | null => { + const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i + const fullHex = hexStr.replace( + shorthandRegex, + (_m, r, g, b) => r + r + g + g + b + b + ) + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(fullHex) + if (!result) return null + return { + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16), + } +} + +/** + * Π‘ΠΎΠ·Π΄Π°Π΅Ρ‚ display color с ΡƒΡ‡Π΅Ρ‚ΠΎΠΌ прозрачности + * @param livePreviewColor - Π¦Π²Π΅Ρ‚ для preview + * @param opacityValue - ΠŸΡ€ΠΎΠ·Ρ€Π°Ρ‡Π½ΠΎΡΡ‚ΡŒ 0-100 + * @returns Display color строка + */ +export const createDisplayColor = ( + livePreviewColor: string, + opacityValue: number +): string => { + let displayColor = + livePreviewColor && + (livePreviewColor.includes('gradient') || + livePreviewColor.startsWith('rgba') || + livePreviewColor.startsWith('rgb') || + livePreviewColor.startsWith('#')) + ? livePreviewColor + : '#FFFFFF' + + // ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° Π³Ρ€Π°Π΄ΠΈΠ΅Π½Ρ‚ΠΎΠ² - Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌ ΠΊΠ°ΠΊ Π΅ΡΡ‚ΡŒ + if (displayColor.includes('gradient')) { + return displayColor + } + + // Нормализация rgb/rgba -> rgba с ΡƒΡ‡Ρ‘Ρ‚ΠΎΠΌ Ρ‚Π΅ΠΊΡƒΡ‰Π΅ΠΉ opacityValue + if (displayColor.startsWith('rgb')) { + const match = displayColor.match( + /rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/ + ) + if (match) { + const r = parseInt(match[1], 10) + const g = parseInt(match[2], 10) + const b = parseInt(match[3], 10) + const a = match[4] !== undefined ? parseFloat(match[4]) : undefined + const finalA = isNaN(opacityValue) + ? typeof a === 'number' + ? a + : 1 + : opacityValue / 100 + displayColor = `rgba(${r}, ${g}, ${b}, ${finalA})` + } + } + + // HEX -> HEXA с ΡƒΡ‡Ρ‘Ρ‚ΠΎΠΌ opacityValue + if (displayColor.startsWith('#')) { + let hexVal = displayColor.substring(1) + if (hexVal.length === 3 || hexVal.length === 4) { + hexVal = hexVal + .split('') + .map(char => char + char) + .join('') + } + const rgbHex = hexVal.substring(0, 6) + displayColor = `#${rgbHex}${opacityToHex(opacityValue)}` + } + + return displayColor +} + +/** + * ΠšΠΎΠ½Π²Π΅Ρ€Ρ‚ΠΈΡ€ΡƒΠ΅Ρ‚ opacity (0-100) Π² hex Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ (00-FF) + * @param opacity - ΠŸΡ€ΠΎΠ·Ρ€Π°Ρ‡Π½ΠΎΡΡ‚ΡŒ ΠΎΡ‚ 0 Π΄ΠΎ 100 + * @returns Hex строка ΠΈΠ· 2 символов + */ +export const opacityToHex = (opacity: number): string => { + if (opacity < 0 || opacity > 100) return 'FF' + const alpha = Math.round((opacity / 100) * 255) + return alpha.toString(16).padStart(2, '0').toUpperCase() +} + +/** + * ΠšΠΎΠ½Π²Π΅Ρ€Ρ‚ΠΈΡ€ΡƒΠ΅Ρ‚ alpha (0-1) Π² hex Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ (00-FF) + * @param alpha - ΠŸΡ€ΠΎΠ·Ρ€Π°Ρ‡Π½ΠΎΡΡ‚ΡŒ ΠΎΡ‚ 0 Π΄ΠΎ 1 + * @returns Hex строка ΠΈΠ· 2 символов + */ +export const alphaToHex = (alpha: number): string => { + if (typeof alpha !== 'number') return 'FF' + if (alpha < 0) return '00' + if (alpha > 1) return 'FF' + return Math.round(alpha * 255) + .toString(16) + .padStart(2, '0') + .toUpperCase() +} + +/** + * Π€ΠΈΠ»ΡŒΡ‚Ρ€ΡƒΠ΅Ρ‚ Π²Π²ΠΎΠ΄ для hex значСния (удаляСт Π½Π΅Π²Π°Π»ΠΈΠ΄Π½Ρ‹Π΅ символы) + * @param value - Входная строка + * @param maxLength - Максимальная Π΄Π»ΠΈΠ½Π° (ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ 6) + * @returns ΠžΡ‚Ρ„ΠΈΠ»ΡŒΡ‚Ρ€ΠΎΠ²Π°Π½Π½Π°Ρ hex строка + */ +export const filterHexInput = ( + value: string, + maxLength: number = 6 +): string => { + return value + .replace(/[^0-9a-fA-F]/g, '') + .slice(0, maxLength) + .toUpperCase() +} diff --git a/src/stories/ColorPicker.stories.tsx b/src/stories/ColorPicker.stories.tsx index 159c0f5..54530f5 100644 --- a/src/stories/ColorPicker.stories.tsx +++ b/src/stories/ColorPicker.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from '@storybook/react' -import React, { useState } from 'react' +import { useState } from 'react' import { addons } from 'storybook/manager-api' import { themes } from 'storybook/theming' import { ColorPicker } from '../color-picker' @@ -74,19 +74,21 @@ export const Basic: Story = {
{/* ΠšΠ°Ρ€Ρ‚ΠΎΡ‡ΠΊΠ°-прСзСнтация */}
-

Basic Color Picker

+

+ Basic Color Picker +

Selected Color
-
{/* Π§Π΅ΠΊΠ΅Ρ€Π±ΠΎΡ€Π΄ + Ρ†Π²Π΅Ρ‚ ΠΏΠΎΠ²Π΅Ρ€Ρ… */}
-
+
-

+

Gradient Color Picker

@@ -295,10 +300,9 @@ export const WithGradient: Story = { />
-
+
- -// All Components Together -export const AllComponents: Story = { - render: () => { - const [color, setColor] = useState('rgba(175, 51, 242, 1)') - const [opacity, setOpacity] = useState(100) - const [hue, setHue] = useState(280) - - return ( -
-

- Color Picker Library - Complete Demo -

-

- This demo showcases all components working together to create a - comprehensive color selection experience. -

- -
- {/* Left Column - Main Color Picker */} -
-

Main Color Picker

- -
-

Selected Color: {color}

-
-
-
- - {/* Right Column - Controls */} -
-

Advanced Controls

- - {/* Opacity Control */} -
-

Opacity Control

- -

Opacity: {opacity}%

-
- - {/* Hue Control */} -
-

Hue Control

- -

Hue: {hue}Β°

-
-
-
- - {/* Input Color Picker Section */} -
-

Input Color Picker

- -
- - {/* Color Formats Comparison */} -
-

Color Formats

-
-
-

HEX

-

{color}

-
-
-

RGB

-

{color}

-
-
-

HSL

-

{color}

-
-
-
-
- ) - }, -} diff --git a/src/stories/InputImageSelect.stories.tsx b/src/stories/InputImageSelect.stories.tsx new file mode 100644 index 0000000..64475be --- /dev/null +++ b/src/stories/InputImageSelect.stories.tsx @@ -0,0 +1,348 @@ +import type { Meta, StoryObj } from '@storybook/react' +import { useEffect, useState } from 'react' +import { InputImageSelect, type ImageFilters } from '../input-image-select' +import { rotateImage90Degrees } from '../input-image-select/utils' + +const computeRotationScale = ( + containerWidth: number, + containerHeight: number, + imageWidth: number, + imageHeight: number, + rotation: number +) => { + if (!containerWidth || !containerHeight || !imageWidth || !imageHeight) { + return 1 + } + + const baseScale = Math.max( + containerWidth / imageWidth, + containerHeight / imageHeight + ) + const scaledWidth = imageWidth * baseScale + const scaledHeight = imageHeight * baseScale + const radians = (rotation * Math.PI) / 180 + const cos = Math.abs(Math.cos(radians)) + const sin = Math.abs(Math.sin(radians)) + const rotatedWidth = scaledWidth * cos + scaledHeight * sin + const rotatedHeight = scaledWidth * sin + scaledHeight * cos + const adjustScale = Math.max( + 1, + containerWidth / rotatedWidth, + containerHeight / rotatedHeight + ) + const scale = baseScale * adjustScale + return Number.isFinite(scale) ? scale : 1 +} + +const meta = { + title: 'Components/InputImageSelect', + component: InputImageSelect, + parameters: { + layout: 'centered', + docs: { + toc: { + title: 'Table of Contents', + }, + }, + backgrounds: { + default: 'light', + values: [ + { name: 'light', value: '#ffffff' }, + { name: 'dark', value: '#1a1a1a' }, + ], + }, + }, + tags: ['autodocs'], + argTypes: { + imageUrl: { + control: 'text', + description: 'Current image URL', + }, + onImageChange: { + description: 'Callback when image changes', + }, + onHideImage: { + description: 'Callback when image visibility toggles', + }, + onDeleteImage: { + description: 'Callback when image is deleted', + }, + className: { + control: 'text', + description: 'Custom classes for the container', + }, + title: { + control: 'text', + description: 'Title shown in picker header', + }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +/** + * Basic example of InputImageSelect component. + * Use Controls to change parameters. + * Switch background in toolbar to change theme (light/dark). + */ +export const Default: Story = { + args: { + title: 'Image', + }, + render: () => { + const [imageUrl, setImageUrl] = useState(undefined) + + const handleImageChange = (file: File | null) => { + if (file) { + const reader = new FileReader() + reader.onloadend = () => { + setImageUrl(reader.result as string) + } + reader.readAsDataURL(file) + } + } + + return ( +
+ +

+ {imageUrl ? 'Image selected' : 'No image selected'} +

+
+ ) + }, +} + +/** + * ## Background Image + * Component for controlling element background image. + */ +export const BackgroundImage: Story = { + args: { + title: 'Background Image', + }, + render: () => { + const [imageUrl, setImageUrl] = useState( + 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400&h=300&fit=crop' + ) + + const handleImageChange = (file: File | null) => { + if (file) { + const reader = new FileReader() + reader.onloadend = () => { + setImageUrl(reader.result as string) + } + reader.readAsDataURL(file) + } + } + + return ( +
+ +
+ {!imageUrl && 'No Background'} +
+
+ ) + }, +} + +/** + * ## With Actions + * Demonstrating all available actions: hide and delete. + */ +export const WithActions: Story = { + args: { + title: 'Image with Actions', + }, + render: () => { + const [imageUrl, setImageUrl] = useState( + 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400&h=300&fit=crop' + ) + const [isHidden, setIsHidden] = useState(false) + const [rotation, setRotation] = useState(0) + const [rotatedImageUrl, setRotatedImageUrl] = useState( + imageUrl + ) + const [isRotating, setIsRotating] = useState(false) + const [opacityValue, setOpacityValue] = useState(100) + + const handleImageChange = (file: File | null) => { + if (file) { + const reader = new FileReader() + reader.onloadend = () => { + const url = reader.result as string + setImageUrl(url) + setRotatedImageUrl(url) + setRotation(0) + } + reader.readAsDataURL(file) + } + } + + const handleDelete = () => { + setImageUrl(undefined) + setRotatedImageUrl(undefined) + setRotation(0) + } + + const handleHide = (hidden: boolean) => { + setIsHidden(hidden) + } + + useEffect(() => { + if (!imageUrl) { + setRotatedImageUrl(undefined) + return + } + + if (rotation === 0) { + setRotatedImageUrl(imageUrl) + return + } + + const applyRotation = async () => { + setIsRotating(true) + try { + const rotated = await rotateImage90Degrees(imageUrl, rotation) + setRotatedImageUrl(rotated) + } catch (error) { + console.error('Failed to rotate image:', error) + setRotatedImageUrl(imageUrl) + } finally { + setIsRotating(false) + } + } + + applyRotation() + }, [imageUrl, rotation]) + + return ( +
+ +
+ {isRotating ? ( + + Rotating... + + ) : rotatedImageUrl && !isHidden ? ( + Canvas preview + ) : ( + + {!imageUrl ? 'No Image' : 'Hidden'} + + )} +
+

+ Use eye icon to hide/show image, trash icon to delete it +

+
+ ) + }, +} + +/** + * ## With Image Filters + * Demonstrating image filters: exposure, contrast, saturation, temperature, tint, highlights, and shadows. + * Filters are applied in real-time and can be used to adjust the image appearance. + */ +export const WithImageFilters: Story = { + args: { + title: 'Image with Filters', + }, + render: () => { + const [imageUrl, setImageUrl] = useState( + 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400&h=300&fit=crop' + ) + const [filters, setFilters] = useState({ + exposure: 0, + contrast: 0, + saturation: 0, + temperature: 0, + tint: 0, + highlights: 0, + shadows: 0, + }) + + const handleImageChange = (file: File | null) => { + if (file) { + const reader = new FileReader() + reader.onloadend = () => { + setImageUrl(reader.result as string) + } + reader.readAsDataURL(file) + } + } + + const handleFiltersChange = (newFilters: ImageFilters) => { + setFilters(newFilters) + } + + const getFilterStyle = () => { + const brightness = 1 + filters.exposure / 100 + const contrast = 1 + filters.contrast / 100 + const saturate = 1 + filters.saturation / 100 + const hueRotate = filters.temperature * 1.8 + + return { + filter: `brightness(${brightness}) contrast(${contrast}) saturate(${saturate}) hue-rotate(${hueRotate}deg)`, + } + } + + return ( +
+ +
+ {!imageUrl && 'No Image'} +
+

+ Click on the image to open the modal and adjust filters using sliders +

+
+ ) + }, +} diff --git a/src/stories/InputUploadImage.stories.tsx b/src/stories/InputUploadImage.stories.tsx new file mode 100644 index 0000000..3281191 --- /dev/null +++ b/src/stories/InputUploadImage.stories.tsx @@ -0,0 +1,114 @@ +import type { Meta, StoryObj } from '@storybook/react' +import { useState } from 'react' +import { InputUploadImage } from '../input-upload-image' + +const meta = { + title: 'Components/InputUploadImage', + component: InputUploadImage, + parameters: { + layout: 'centered', + docs: { + toc: { + title: 'Table of Contents', + }, + }, + backgrounds: { + default: 'light', + values: [ + { name: 'light', value: '#ffffff' }, + { name: 'dark', value: '#1a1a1a' }, + ], + }, + }, + tags: ['autodocs'], +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: { + title: 'Upload Image', + }, +} + +export const Interactive: Story = { + args: { + title: 'Image Upload', + }, + render: () => { + const [imageUrl, setImageUrl] = useState(undefined) + const [fileName, setFileName] = useState(undefined) + const [isHidden, setIsHidden] = useState(false) + + const handleImageChange = (file: File | null) => { + if (file) { + const reader = new FileReader() + reader.onloadend = () => { + setImageUrl(reader.result as string) + setFileName(file.name) + } + reader.readAsDataURL(file) + } + } + + const handleDelete = () => { + setImageUrl(undefined) + setFileName(undefined) + setIsHidden(false) + } + + const handleHide = (hidden: boolean) => { + setIsHidden(hidden) + } + + return ( +
+ + + {imageUrl && ( +
+ {isHidden ? ( + + Image Hidden + + ) : ( + Preview + )} +
+ )} + + {fileName && ( +
+

+ Selected file: +

+

+ {fileName} +

+
+ )} +
+ ) + }, +} + +export const WithCustomTitle: Story = { + args: { + title: 'product-image.png', + imageUrl: + 'https://images.unsplash.com/photo-1523275335684-37898b6baf30?w=100&h=100&fit=crop', + fileName: 'product-image.png', + }, +} diff --git a/tsup.config.ts b/tsup.config.ts index 44b013b..c2823bb 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -15,13 +15,14 @@ export default defineConfig({ 'shared/index': 'src/shared/index.ts', }, format: ['esm', 'cjs'], - dts: true, - sourcemap: false, + dts: true, // Generate full TypeScript declarations + sourcemap: false, // No source maps in production clean: true, target: 'es2020', treeshake: true, - minify: false, // Don't minify - code should be readable + minify: true, // Minify JS for smaller bundle, types stay readable splitting: false, + keepNames: true, // Keep function/class names for better debugging external: [ 'react', 'react-dom',