diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml index a8ed3ee27a..26b4d67826 100644 --- a/.github/workflows/cleanup.yml +++ b/.github/workflows/cleanup.yml @@ -9,7 +9,7 @@ on: jobs: cleanup_release: - uses: blockscout/blockscout-ci-cd/.github/workflows/cleanup_helmfile.yaml@master + uses: blockscout/actions/.github/workflows/cleanup_helmfile.yaml@main with: appName: review-l2-$GITHUB_REF_NAME_SLUG globalEnv: review @@ -18,7 +18,7 @@ jobs: vaultRole: ci-dev secrets: inherit cleanup_l2_release: - uses: blockscout/blockscout-ci-cd/.github/workflows/cleanup_helmfile.yaml@master + uses: blockscout/actions/.github/workflows/cleanup_helmfile.yaml@main with: appName: review-$GITHUB_REF_NAME_SLUG globalEnv: review diff --git a/.github/workflows/deploy-review-l2.yml b/.github/workflows/deploy-review-l2.yml index a625a51b85..721274589a 100644 --- a/.github/workflows/deploy-review-l2.yml +++ b/.github/workflows/deploy-review-l2.yml @@ -21,6 +21,7 @@ on: - eth_sepolia - eth_goerli - filecoin + - immutable - neon_devnet - optimism - optimism_celestia @@ -62,7 +63,7 @@ jobs: deploy_review_l2: name: Deploy frontend (L2) needs: [ make_slug, publish_image ] - uses: blockscout/blockscout-ci-cd/.github/workflows/deploy_helmfile.yaml@master + uses: blockscout/actions/.github/workflows/deploy_helmfile.yaml@main with: appName: review-l2-${{ needs.make_slug.outputs.REF_SLUG }} globalEnv: review diff --git a/.github/workflows/deploy-review.yml b/.github/workflows/deploy-review.yml index fb5ae2abdd..7922332bf7 100644 --- a/.github/workflows/deploy-review.yml +++ b/.github/workflows/deploy-review.yml @@ -21,6 +21,7 @@ on: - eth_sepolia - eth_goerli - filecoin + - immutable - mekong - neon_devnet - optimism @@ -64,7 +65,7 @@ jobs: deploy_review: name: Deploy frontend needs: [ make_slug, publish_image ] - uses: blockscout/blockscout-ci-cd/.github/workflows/deploy_helmfile.yaml@master + uses: blockscout/actions/.github/workflows/deploy_helmfile.yaml@main with: appName: review-${{ needs.make_slug.outputs.REF_SLUG }} globalEnv: review diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 265f55ba60..b4ed448440 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -50,5 +50,27 @@ jobs: test: name: Run tests needs: deploy_e2e - uses: blockscout/blockscout-ci-cd/.github/workflows/e2e_new.yaml@master - secrets: inherit \ No newline at end of file + runs-on: ubuntu-latest + permissions: write-all + steps: + - name: Get Vault credentials + id: retrieve-vault-secrets + uses: hashicorp/vault-action@v2.4.1 + with: + url: https://vault.k8s.blockscout.com + role: ci-dev + path: github-jwt + method: jwt + tlsSkipVerify: false + exportToken: true + secrets: | + ci/data/dev/github token | WORKFLOW_TRIGGER_TOKEN ; + - name: Trigger tests + uses: convictional/trigger-workflow-and-wait@v1.6.1 + with: + owner: blockscout + repo: blockscout-ci-cd + github_token: ${{ env.WORKFLOW_TRIGGER_TOKEN }} + workflow_file_name: e2e_new.yaml + ref: master + wait_interval: 30 diff --git a/.gitignore b/.gitignore index d4459df0b2..e61963b528 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ /public/assets/configs /public/icons/sprite.svg /public/icons/sprite.*.svg +/public/icons/registry.json /public/icons/README.md /public/static/og_image.png /public/sitemap.xml diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 8cf832c905..16fcb132fc 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -362,20 +362,25 @@ "arbitrum", "arbitrum_sepolia", "base", + "blackfort_testnet", "celo_alfajores", "garnet", "gnosis", + "immutable", "eth", "eth_goerli", "eth_sepolia", "filecoin", "mekong", + "neon_devnet", "optimism", "optimism_celestia", + "optimism_interop_0", "optimism_sepolia", "polygon", "rari_testnet", "rootstock_testnet", + "scroll_sepolia", "shibarium", "stability_testnet", "zkevm", diff --git a/Dockerfile b/Dockerfile index 36eadc261a..beada8501c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,12 @@ RUN ln -sf /usr/bin/python3 /usr/bin/python ### APP # Install dependencies WORKDIR /app -COPY package.json yarn.lock ./ +COPY package.json yarn.lock tsconfig.json ./ +COPY types ./types +COPY lib ./lib +COPY configs/app ./configs/app +COPY toolkit/theme ./toolkit/theme +COPY ui/shared/forms/validators/url.ts ./ui/shared/forms/validators/url.ts RUN apk add git RUN yarn --frozen-lockfile --network-timeout 100000 @@ -44,7 +49,7 @@ RUN yarn --frozen-lockfile --network-timeout 100000 # ****** STAGE 2: Build ******* # ***************************** FROM node:22.11.0-alpine AS builder -RUN apk add --no-cache --upgrade libc6-compat bash +RUN apk add --no-cache --upgrade libc6-compat bash jq # pass build args to env variables ARG GIT_COMMIT_SHA diff --git a/configs/app/app.ts b/configs/app/app.ts index 8debdab642..f5cca629e7 100644 --- a/configs/app/app.ts +++ b/configs/app/app.ts @@ -10,10 +10,12 @@ const baseUrl = [ appPort && ':' + appPort, ].filter(Boolean).join(''); const isDev = getEnvValue('NEXT_PUBLIC_APP_ENV') === 'development'; +const isPw = getEnvValue('NEXT_PUBLIC_APP_INSTANCE') === 'pw'; const spriteHash = getEnvValue('NEXT_PUBLIC_ICON_SPRITE_HASH'); const app = Object.freeze({ isDev, + isPw, protocol: appSchema, host: appHost, port: appPort, diff --git a/configs/app/features/rollup.ts b/configs/app/features/rollup.ts index 6dc4b2e9d2..bdd46393aa 100644 --- a/configs/app/features/rollup.ts +++ b/configs/app/features/rollup.ts @@ -35,6 +35,7 @@ const config: Feature<{ type: RollupType; homepage: { showLatestBlocks: boolean }; outputRootsEnabled: boolean; + interopEnabled: boolean; L2WithdrawalUrl: string | undefined; parentChain: ParentChain; DA: { @@ -50,6 +51,7 @@ const config: Feature<{ type, L2WithdrawalUrl: type === 'optimistic' ? L2WithdrawalUrl : undefined, outputRootsEnabled: type === 'optimistic' && getEnvValue('NEXT_PUBLIC_ROLLUP_OUTPUT_ROOTS_ENABLED') === 'true', + interopEnabled: type === 'optimistic' && getEnvValue('NEXT_PUBLIC_INTEROP_ENABLED') === 'true', homepage: { showLatestBlocks: getEnvValue('NEXT_PUBLIC_ROLLUP_HOMEPAGE_SHOW_LATEST_BLOCKS') === 'true', }, diff --git a/configs/envs/.env.base b/configs/envs/.env.base index 8eab44c966..3081968dd9 100644 --- a/configs/envs/.env.base +++ b/configs/envs/.env.base @@ -10,37 +10,41 @@ NEXT_PUBLIC_APP_ENV=development NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws # Instance ENVs -NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP={ "id": "728301", "width": "728", "height": "90" } -NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE={ "id": "728302", "width": "320", "height": "100" } -NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER=adbutler NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com NEXT_PUBLIC_API_BASE_PATH=/ NEXT_PUBLIC_API_HOST=base.blockscout.com NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com -NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS=[{'text':'Swap','icon':'swap','dappId':'aerodrome'},{'text':'Payment link','icon':'payment_link','dappId':'peanut-protocol'},{'text':'Get gas','icon':'gas','dappId':'smol-refuel'}] +NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS=[{'text':'Swap','icon':'swap','dappId':'rubic'},{'text':'Disperse','icon':'txn_batches_slim','dappId':'smol'},{'text':'Payment link','icon':'payment_link','dappId':'peanut-protocol'},{'text':'Get gas','icon':'gas','dappId':'smol-refuel'}] +NEXT_PUBLIC_DEX_POOLS_ENABLED=true +NEXT_PUBLIC_FAULT_PROOF_ENABLED=true NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/base-mainnet.json NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/base-mainnet.json +NEXT_PUBLIC_GAME_BADGE_CLAIM_LINK=https://badges.blockscout.com/mint/sherblockHolmesBadge +NEXT_PUBLIC_GAS_REFUEL_PROVIDER_CONFIG={'name': 'Need gas?', 'url_template': 'https://smolrefuel.com/?outboundChain={chainId}&partner=blockscout&utm_source=blockscout&disableBridges=true', 'dapp_id': 'smol-refuel', 'logo': 'https://blockscout-content.s3.amazonaws.com/smolrefuel-logo-action-button.png'} NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xfd5c5dae7b69fe29e61d19b9943e688aa0f1be1e983c4fba8fe985f90ff69d5f +NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS=true NEXT_PUBLIC_HAS_USER_OPS=true NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS=true NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] -NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=linear-gradient(136.9deg,rgb(107 94 236) 1.5%,rgb(0 82 255) 56.84%,rgb(82 62 231) 98.54%) +NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG={'background':['linear-gradient(136.9deg,rgb(107 94 236) 1.5%,rgb(0 82 255) 56.84%,rgb(82 62 231) 98.54%)']} NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true NEXT_PUBLIC_LOGOUT_URL=https://basechain.us.auth0.com/v2/logout -NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL=https://gist.githubusercontent.com/maxaleks/0d18fc309ff499075127b364cc69306d/raw/2a51f961a8c1b9f884f2ab7eed79d4b69330e1ae/peanut_protocol_banner.html -NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL=https://base.blockscout.com/apps/peanut-protocol +NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE=

Joined recent campaigns? Mint your Merit Badge here

+NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL=https://gist.githubusercontent.com/maikReal/974c47f86a3158c1a86b092ae2f044b3/raw/abcc7e02150cd85d4974503a0357162c0a2c35a9/merits-banner.html +NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL=https://swap.blockscout.com?utm_source=blockscout&utm_medium=base NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json NEXT_PUBLIC_MARKETPLACE_ENABLED=true +NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_BASE_ID=appGkvtmKI7fXE4Vs NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-security-reports/default.json NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com NEXT_PUBLIC_METASUITES_ENABLED=true -NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG=[{'name': 'zerion', 'dapp_id': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview?utm_source=blockscout&utm_medium=address', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'}] +NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG=[{'name': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'},{'name': 'zapper', 'url_template': 'https://zapper.xyz/account/{address}', 'logo': 'https://blockscout-content.s3.amazonaws.com/zapper-icon.png'}] NEXT_PUBLIC_NAME_SERVICE_API_HOST=https://bens.services.blockscout.com -NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES=['/apps'] +NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES=['/pools'] NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH @@ -59,8 +63,10 @@ NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth.blockscout.com/ NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://bridge.base.org/withdraw NEXT_PUBLIC_ROLLUP_TYPE=optimistic NEXT_PUBLIC_SAFE_TX_SERVICE_URL=https://safe-transaction-base.safe.global -NEXT_PUBLIC_STATS_API_HOST=https://stats-l2-base-mainnet.k8s-prod-1.blockscout.com +NEXT_PUBLIC_STATS_API_HOST=https://stats-l2-base-mainnet.k8s-prod-2.blockscout.com NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE=gradient_avatar NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true -NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com \ No newline at end of file +NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES=[{'name':'OpenSea','collection_url':'https://opensea.io/assets/base/{hash}','instance_url':'https://opensea.io/assets/base/{hash}/{id}','logo_url':'https://opensea.io/static/images/logos/opensea-logo.svg'}] +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com +NEXT_PUBLIC_XSTAR_SCORE_URL=https://docs.xname.app/the-solution-adaptive-proof-of-humanity-on-blockchain/xhs-scoring-algorithm?utm_source=blockscout&utm_medium=address \ No newline at end of file diff --git a/configs/envs/.env.celo_alfajores b/configs/envs/.env.celo_alfajores index d93f9e36ae..e0f9fb1020 100644 --- a/configs/envs/.env.celo_alfajores +++ b/configs/envs/.env.celo_alfajores @@ -9,18 +9,21 @@ NEXT_PUBLIC_APP_PORT=3000 NEXT_PUBLIC_APP_ENV=development NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws -NEXT_PUBLIC_CELO_ENABLED=true -NEXT_PUBLIC_CELO_L2_UPGRADE_BLOCK=26369280 - # Instance ENVs +NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com NEXT_PUBLIC_API_BASE_PATH=/ NEXT_PUBLIC_API_HOST=celo-alfajores.blockscout.com NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml +NEXT_PUBLIC_CELO_ENABLED=true +NEXT_PUBLIC_CELO_L2_UPGRADE_BLOCK=26369280 NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] +NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com +NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/celo-alfajores-testnet.json +NEXT_PUBLIC_GAME_BADGE_CLAIM_LINK=https://badges.blockscout.com/mint/sherblockHolmesBadge NEXT_PUBLIC_GAS_TRACKER_ENABLED=false +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x9767ce30754afad2a3279b9df2d13257f467c3dad4e0e601271e66d16dfd1641 NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] -NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=rgba(252, 255, 82, 1) -NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=rgba(0, 0, 0, 1) +NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG={'background':['rgba(252, 255, 82, 1)'],'text_color':['rgba(0, 0, 0, 1)']} NEXT_PUBLIC_IS_TESTNET=true NEXT_PUBLIC_MARKETPLACE_ENABLED=false NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com @@ -32,12 +35,14 @@ NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/front NEXT_PUBLIC_NETWORK_ID=44787 NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/celo-logo-light.svg NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/celo-logo-dark.svg +NEXT_PUBLIC_NETWORK_MULTIPLE_GAS_CURRENCIES=true NEXT_PUBLIC_NETWORK_NAME=Celo Alfajores NEXT_PUBLIC_NETWORK_RPC_URL=https://alfajores-forno.celo-testnet.org NEXT_PUBLIC_NETWORK_SHORT_NAME=Alfajores NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/celo.png +NEXT_PUBLIC_STATS_API_HOST=https://stats-alfajores-testnet.k8s-prod-1.blockscout.com NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout +NEXT_PUBLIC_VIEWS_BLOCK_HIDDEN_FIELDS=['burnt_fees'] NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true -NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com -NEXT_PUBLIC_HOMEPAGE_STATS=['total_blocks','average_block_time','total_txs','wallet_addresses','gas_tracker','current_epoch'] \ No newline at end of file +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com \ No newline at end of file diff --git a/configs/envs/.env.eth b/configs/envs/.env.eth index 215b86e5d1..b1d1c3d658 100644 --- a/configs/envs/.env.eth +++ b/configs/envs/.env.eth @@ -10,9 +10,6 @@ NEXT_PUBLIC_APP_ENV=development NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws # Instance ENVs -NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP={ "id": "728471", "width": "728", "height": "90" } -NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE={ "id": "728470", "width": "320", "height": "100" } -NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER=adbutler NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com NEXT_PUBLIC_API_BASE_PATH=/ NEXT_PUBLIC_API_HOST=eth.blockscout.com @@ -20,9 +17,11 @@ NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED=true -NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS=[{'text':'Swapscout','icon':'swap','dappId':'swapscout'},{'text':'Disperse','icon':'txn_batches_slim','dappId':'smol'},{'text':'Payment link','icon':'payment_link','dappId':'peanut-protocol'},{'text':'Get gas','icon':'gas','dappId':'smol-refuel'}] +NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS=[{'text':'Swapscout','icon':'swap','dappId':'swapscout'},{'text':'Payment link','icon':'payment_link','dappId':'peanut-protocol'},{'text':'Get gas','icon':'gas','dappId':'smol-refuel'}] +NEXT_PUBLIC_DEX_POOLS_ENABLED=true NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/eth.json NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/eth-mainnet.json +NEXT_PUBLIC_GAME_BADGE_CLAIM_LINK=https://badges.blockscout.com/mint/sherblockHolmesBadge NEXT_PUBLIC_GAS_REFUEL_PROVIDER_CONFIG={'name': 'Need gas?', 'url_template': 'https://smolrefuel.com/?outboundChain={chainId}&partner=blockscout&utm_source=blockscout&disableBridges=true', 'dapp_id': 'smol-refuel', 'logo': 'https://blockscout-content.s3.amazonaws.com/smolrefuel-logo-action-button.png'} NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xd01175f1efa23f36c5579b3c13e2bbd0885017643a7efef5cbcb6b474384dfa8 NEXT_PUBLIC_HAS_BEACON_CHAIN=true @@ -32,7 +31,7 @@ NEXT_PUBLIC_HIDE_INDEXING_ALERT_BLOCKS=true NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs', 'coin_price', 'market_cap'] NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true NEXT_PUBLIC_LOGOUT_URL=https://ethereum-mainnet.us.auth0.com/v2/logout -NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE=

Participated in our recent Blockscout activities? Check your eligibility and claim your NFT Scout badges. More exciting things are coming soon!

+NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE=

Joined recent campaigns? Mint your Merit Badge here

NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL=https://gist.githubusercontent.com/maikReal/974c47f86a3158c1a86b092ae2f044b3/raw/abcc7e02150cd85d4974503a0357162c0a2c35a9/merits-banner.html NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL=https://swap.blockscout.com?utm_source=blockscout&utm_medium=eth NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json @@ -45,11 +44,11 @@ NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com NEXT_PUBLIC_METASUITES_ENABLED=true NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG=[{'name': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'},{'name': 'zapper', 'url_template': 'https://zapper.xyz/account/{address}', 'logo': 'https://blockscout-content.s3.amazonaws.com/zapper-icon.png'}] NEXT_PUBLIC_NAME_SERVICE_API_HOST=https://bens.services.blockscout.com -NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES=['/apps'] +NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES=['/pools'] NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH -NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'GeckoTerminal','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/geckoterminal.png','baseUrl':'https://www.geckoterminal.com/','paths':{'token':'/eth/pools'}},{'title':'Etherscan','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/etherscan.png','baseUrl':'https://etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}, {'title':'Blockchair','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/blockchair.png','baseUrl':'https://blockchair.com/','paths':{'tx':'/ethereum/transaction','address':'/ethereum/address','token':'/ethereum/erc-20/token','block':'/ethereum/block'}},{'title':'Sentio','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/sentio.png','baseUrl':'https://app.sentio.xyz/','paths':{'tx':'/tx/1','address':'/contract/1'}}, {'title':'Tenderly','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/tenderly.png','baseUrl':'https://dashboard.tenderly.co','paths':{'tx':'/tx/mainnet'}}, {'title':'0xPPL','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/0xPPL.png','baseUrl':'https://0xppl.com','paths':{'tx':'/Ethereum/tx','address':'/','token':'/c/Ethereum'}}, {'title':'3xpl','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/3xpl.png','baseUrl':'https://3xpl.com/','paths':{'tx':'/ethereum/transaction','address':'/ethereum/address'}} ] +NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Moralis','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/moralis.png','baseUrl':'https://moralis.com/','paths':{'token':'/chain/ethereum/token/price'}},{'title':'GeckoTerminal','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/geckoterminal.png','baseUrl':'https://www.geckoterminal.com/','paths':{'token':'/eth/pools'}},{'title':'Etherscan','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/etherscan.png','baseUrl':'https://etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}, {'title':'Blockchair','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/blockchair.png','baseUrl':'https://blockchair.com/','paths':{'tx':'/ethereum/transaction','address':'/ethereum/address','token':'/ethereum/erc-20/token','block':'/ethereum/block'}},{'title':'Sentio','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/sentio.png','baseUrl':'https://app.sentio.xyz/','paths':{'tx':'/tx/1','address':'/contract/1'}}, {'title':'Tenderly','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/tenderly.png','baseUrl':'https://dashboard.tenderly.co','paths':{'tx':'/tx/mainnet'}}, {'title':'0xPPL','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/0xPPL.png','baseUrl':'https://0xppl.com','paths':{'tx':'/Ethereum/tx','address':'/','token':'/c/Ethereum'}}, {'title':'3xpl','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/3xpl.png','baseUrl':'https://3xpl.com/','paths':{'tx':'/ethereum/transaction','address':'/ethereum/address'}} ] NEXT_PUBLIC_NETWORK_ID=1 NEXT_PUBLIC_NETWORK_NAME=Ethereum NEXT_PUBLIC_NETWORK_RPC_URL=https://eth.drpc.org @@ -61,10 +60,10 @@ NEXT_PUBLIC_OTHER_LINKS=[{'url':'https://eth.drpc.org?ref=559183','text':'Public NEXT_PUBLIC_REWARDS_SERVICE_API_HOST=https://merits.blockscout.com NEXT_PUBLIC_SAFE_TX_SERVICE_URL=https://safe-transaction-mainnet.safe.global NEXT_PUBLIC_SAVE_ON_GAS_ENABLED=true -NEXT_PUBLIC_SEO_ENHANCED_DATA_ENABLED=true NEXT_PUBLIC_STATS_API_HOST=https://stats-eth-main.k8s-prod-1.blockscout.com NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true +NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES=[{'name':'Rarible','collection_url':'https://rarible.com/collection/{hash}/items','instance_url':'https://rarible.com/token/{hash}:{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/rarible.png'},{'name':'Blur','collection_url':'https://blur.io/eth/collection/{hash}','instance_url':'https://blur.io/eth/asset/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/blur.png'},{'name':'Nftrade','collection_url':'https://nftrade.com/assets/eth/{hash}','instance_url':'https://nftrade.com/assets/eth/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/nftrade.png'},{'name':'MagicEden','collection_url':'https://magiceden.io/collections/ethereum/{hash}','instance_url':'https://magiceden.io/item-details/ethereum/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/magiceden.png'}] +NEXT_PUBLIC_VIEWS_TOKEN_SCAM_TOGGLE_ENABLED=true NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com -NEXT_PUBLIC_XSTAR_SCORE_URL=https://docs.xname.app/the-solution-adaptive-proof-of-humanity-on-blockchain/xhs-scoring-algorithm?utm_source=blockscout&utm_medium=address -NEXT_PUBLIC_GAME_BADGE_CLAIM_LINK=https://badges.blockscout.com/mint/hiddenBlockBadge \ No newline at end of file +NEXT_PUBLIC_XSTAR_SCORE_URL=https://docs.xname.app/the-solution-adaptive-proof-of-humanity-on-blockchain/xhs-scoring-algorithm?utm_source=blockscout&utm_medium=address \ No newline at end of file diff --git a/configs/envs/.env.eth_sepolia b/configs/envs/.env.eth_sepolia index 8f73a35219..91fac060df 100644 --- a/configs/envs/.env.eth_sepolia +++ b/configs/envs/.env.eth_sepolia @@ -68,4 +68,4 @@ NEXT_PUBLIC_STATS_API_HOST=https://stats-sepolia.k8s-prod-2.blockscout.com NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com -NEXT_PUBLIC_XSTAR_SCORE_URL=https://docs.xname.app/the-solution-adaptive-proof-of-humanity-on-blockchain/xhs-scoring-algorithm?utm_source=blockscout&utm_medium=address \ No newline at end of file +NEXT_PUBLIC_XSTAR_SCORE_URL=https://docs.xname.app/the-solution-adaptive-proof-of-humanity-on-blockchain/xhs-scoring-algorithm?utm_source=blockscout&utm_medium=address diff --git a/configs/envs/.env.immutable b/configs/envs/.env.immutable new file mode 100644 index 0000000000..544c3b7df4 --- /dev/null +++ b/configs/envs/.env.immutable @@ -0,0 +1,53 @@ +# Set of ENVs for Immutable network explorer +# https://explorer.immutable.com +# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=immutable" + +# Local ENVs +NEXT_PUBLIC_APP_PROTOCOL=http +NEXT_PUBLIC_APP_HOST=localhost +NEXT_PUBLIC_APP_PORT=3000 +NEXT_PUBLIC_APP_ENV=development +NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws + +# Instance ENVs +NEXT_PUBLIC_AD_BANNER_PROVIDER=none +NEXT_PUBLIC_AD_TEXT_PROVIDER=none +NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com +NEXT_PUBLIC_API_BASE_PATH=/ +NEXT_PUBLIC_API_HOST=immutable-mainnet.blockscout.com +NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml +NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] +NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com +NEXT_PUBLIC_DEX_POOLS_ENABLED=true +NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/immutable-mainnet.json +NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/immutable.json +NEXT_PUBLIC_GAME_BADGE_CLAIM_LINK=https://badges.blockscout.com/mint/sherblockHolmesBadge +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x6166cece570f4731ccc94c2d17d854ce88496cd3b48e03b537959992ab6685c8 +NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS=true +NEXT_PUBLIC_HELIA_VERIFIED_FETCH_ENABLED=false +NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] +NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG={'background':['no-repeat center/100% 100% url(https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-skins/immutable.jpg)'],'text_color':['rgba(19, 19, 19, 1)']} +NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true +NEXT_PUBLIC_LOGOUT_URL=https://blockscout-immutable.us.auth0.com/v2/logout +NEXT_PUBLIC_MARKETPLACE_ENABLED=false +NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com +NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES=['/pools'] +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=IMX +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=IMX +NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'GeckoTerminal','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/geckoterminal.png','baseUrl':'https://www.geckoterminal.com/','paths':{'token':'/immutable-zkevm/pools'}}] +NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/immutable-short.svg +NEXT_PUBLIC_NETWORK_ID=13371 +NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/immutable.svg +NEXT_PUBLIC_NETWORK_NAME=Immutable +NEXT_PUBLIC_NETWORK_RPC_URL=https://rpc.immutable.com/ +NEXT_PUBLIC_NETWORK_SHORT_NAME=Immutable +NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true +NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/immutable.png +NEXT_PUBLIC_STATS_API_HOST=https://stats-immutable-mainnet.k8s.blockscout.com +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout +NEXT_PUBLIC_VIEWS_BLOCK_HIDDEN_FIELDS=["miner"] +NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true +NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES=[{'name':'Rarible','collection_url':'https://rarible.com/collection/immutablex/{hash}/items','instance_url':'https://rarible.com/token/immutablex/{hash}:{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/rarible.png'}] +NEXT_PUBLIC_VIEWS_TOKEN_SCAM_TOGGLE_ENABLED=true +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com \ No newline at end of file diff --git a/configs/envs/.env.optimism b/configs/envs/.env.optimism index 8fd9911afc..04f2307aab 100644 --- a/configs/envs/.env.optimism +++ b/configs/envs/.env.optimism @@ -9,45 +9,49 @@ NEXT_PUBLIC_APP_PORT=3000 NEXT_PUBLIC_APP_ENV=development NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws +NEXT_PUBLIC_ROLLUP_OUTPUT_ROOTS_ENABLED=true + # Instance ENVs -NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP={ "id": "749780", "width": "728", "height": "90" } -NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE={ "id": "749779", "width": "320", "height": "100" } -NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER=adbutler NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com NEXT_PUBLIC_API_BASE_PATH=/ NEXT_PUBLIC_API_HOST=optimism.blockscout.com NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com -NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS=[{'text':'Swap','icon':'swap','dappId':'velodrome'},{'text':'Payment link','icon':'payment_link','dappId':'peanut-protocol'},{'text':'Get gas','icon':'gas','dappId':'smol-refuel'}] +NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS=[{'text':'Swap','icon':'swap','dappId':'rubic'},{'text':'Disperse','icon':'txn_batches_slim','dappId':'smol'},{'text':'Payment link','icon':'payment_link','dappId':'peanut-protocol'},{'text':'Get gas','icon':'gas','dappId':'smol-refuel'}] +NEXT_PUBLIC_DEX_POOLS_ENABLED=true NEXT_PUBLIC_FAULT_PROOF_ENABLED=true NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/optimism-mainnet.json NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/optimism.json +NEXT_PUBLIC_GAME_BADGE_CLAIM_LINK=https://badges.blockscout.com/mint/sherblockHolmesBadge +NEXT_PUBLIC_GAS_REFUEL_PROVIDER_CONFIG={'name': 'Need gas?', 'url_template': 'https://smolrefuel.com/?outboundChain={chainId}&partner=blockscout&utm_source=blockscout&disableBridges=true', 'dapp_id': 'smol-refuel', 'logo': 'https://blockscout-content.s3.amazonaws.com/smolrefuel-logo-action-button.png'} NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x97f34a4cf685e365460dd38dbe16e092d8e4cc4b6ac779e3abcf4c18df6b1329 +NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS=true NEXT_PUBLIC_HAS_USER_OPS=true NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs', 'coin_price', 'market_cap', 'secondary_coin_price'] -NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=linear-gradient(90deg, rgb(232, 52, 53) 0%, rgb(139, 28, 232) 100%) -NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=rgb(255, 255, 255) +NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG={'background':['linear-gradient(90deg, rgb(232, 52, 53) 0%, rgb(139, 28, 232) 100%)'],'text_color':['rgb(255, 255, 255)']} NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true NEXT_PUBLIC_LOGOUT_URL=https://optimism-goerli.us.auth0.com/v2/logout -NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL=https://gist.githubusercontent.com/maxaleks/0d18fc309ff499075127b364cc69306d/raw/2a51f961a8c1b9f884f2ab7eed79d4b69330e1ae/peanut_protocol_banner.html -NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL=https://optimism.blockscout.com/apps/peanut-protocol +NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE=

Joined recent campaigns? Mint your Merit Badge here

+NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL=https://gist.githubusercontent.com/maikReal/974c47f86a3158c1a86b092ae2f044b3/raw/abcc7e02150cd85d4974503a0357162c0a2c35a9/merits-banner.html +NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL=https://swap.blockscout.com?utm_source=blockscout&utm_medium=optimism NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json NEXT_PUBLIC_MARKETPLACE_ENABLED=true +NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_BASE_ID=appGkvtmKI7fXE4Vs NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-security-reports/default.json NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com NEXT_PUBLIC_METASUITES_ENABLED=true -NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG=[{'name': 'zerion', 'dapp_id': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview?utm_source=blockscout&utm_medium=address', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'}] +NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG=[{'name': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview?utm_source=blockscout&utm_medium=address', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'}] NEXT_PUBLIC_NAME_SERVICE_API_HOST=https://bens.services.blockscout.com -NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES=['/apps'] +NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES=['/pools'] NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'GeckoTerminal','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/geckoterminal.png','baseUrl':'https://www.geckoterminal.com/','paths':{'token':'/optimism/pools'}}, {'title':'Tenderly','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/tenderly.png','baseUrl':'https://dashboard.tenderly.co','paths':{'tx':'/tx/optimistic'}},{'title':'3xpl','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/3xpl.png','baseUrl':'https://3xpl.com/','paths':{'tx':'/optimism/transaction','address':'/optimism/address'}}] -NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/optimism.svg -NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/optimism.svg +NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/optimism-mainnet-light.svg +NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/optimism-mainnet-dark.svg NEXT_PUBLIC_NETWORK_ID=10 NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/optimism.svg NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/optimism.svg @@ -66,4 +70,5 @@ NEXT_PUBLIC_STATS_API_HOST=https://stats-optimism-mainnet.k8s-prod-1.blockscout. NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com -NEXT_PUBLIC_WEB3_WALLETS=['token_pocket', 'metamask'] \ No newline at end of file +NEXT_PUBLIC_WEB3_WALLETS=['token_pocket', 'metamask'] +NEXT_PUBLIC_XSTAR_SCORE_URL=https://docs.xname.app/the-solution-adaptive-proof-of-humanity-on-blockchain/xhs-scoring-algorithm?utm_source=blockscout&utm_medium=address \ No newline at end of file diff --git a/configs/envs/.env.optimism_interop_0 b/configs/envs/.env.optimism_interop_0 new file mode 100644 index 0000000000..d424faa680 --- /dev/null +++ b/configs/envs/.env.optimism_interop_0 @@ -0,0 +1,47 @@ +# Set of ENVs for OP Interop Alpha 0 network explorer +# https://optimism-interop-alpha-0.blockscout.com +# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=optimism_interop_0" + +# Local ENVs +NEXT_PUBLIC_APP_PROTOCOL=http +NEXT_PUBLIC_APP_HOST=localhost +NEXT_PUBLIC_APP_PORT=3000 +NEXT_PUBLIC_APP_ENV=development +NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws + +# Instance ENVs +NEXT_PUBLIC_AD_BANNER_PROVIDER=none +NEXT_PUBLIC_AD_TEXT_PROVIDER=none +NEXT_PUBLIC_API_BASE_PATH=/ +NEXT_PUBLIC_API_HOST=optimism-interop-alpha-0.blockscout.com +NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml +NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] +NEXT_PUBLIC_FAULT_PROOF_ENABLED=true +NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/optimism.json +NEXT_PUBLIC_GAME_BADGE_CLAIM_LINK=https://badges.blockscout.com/mint/sherblockHolmesBadge +NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] +NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG={'background':['linear-gradient(90deg, rgb(232, 52, 53) 0%, rgb(139, 28, 232) 100%)'],'text_color':['rgb(255, 255, 255)']} +NEXT_PUBLIC_INTEROP_ENABLED=true +NEXT_PUBLIC_IS_TESTNET=true +NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH +NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/optimism.svg +NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/optimism.svg +NEXT_PUBLIC_NETWORK_ID=420120000 +NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/optimism.svg +NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/optimism.svg +NEXT_PUBLIC_NETWORK_NAME=OP Interop Alpha 0 +NEXT_PUBLIC_NETWORK_RPC_URL=https://interop-alpha-0.optimism.io +NEXT_PUBLIC_NETWORK_SHORT_NAME=OP Interop Alpha 0 +NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true +NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth-sepolia.blockscout.com/ +NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://bridge.interop-alpha-0.optimism.io +NEXT_PUBLIC_ROLLUP_OUTPUT_ROOTS_ENABLED=false +NEXT_PUBLIC_ROLLUP_TYPE=optimistic +NEXT_PUBLIC_STATS_API_HOST=https://stats-optimism-interop-devnet-0.k8s-prod-1.blockscout.com +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout +NEXT_PUBLIC_VIEWS_TOKEN_SCAM_TOGGLE_ENABLED=true +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com +NEXT_PUBLIC_WEB3_WALLETS=['token_pocket', 'metamask'] \ No newline at end of file diff --git a/configs/envs/.env.scroll_sepolia b/configs/envs/.env.scroll_sepolia index c29abc0442..43396566ab 100644 --- a/configs/envs/.env.scroll_sepolia +++ b/configs/envs/.env.scroll_sepolia @@ -14,7 +14,9 @@ NEXT_PUBLIC_API_BASE_PATH=/ NEXT_PUBLIC_API_HOST=scroll-sepolia.blockscout.com NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] +NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/scroll-testnet.json +NEXT_PUBLIC_GAME_BADGE_CLAIM_LINK=https://badges.blockscout.com/mint/sherblockHolmesBadge NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xa0d22caf6217a488b1e97b646c5ed88e8a3020a607bcd1f3fe8d4c430bb19ad5 NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG={'background':['rgba(255, 238, 218, 1)'],'text_color':['rgba(25, 6, 2, 1)']} @@ -33,9 +35,8 @@ NEXT_PUBLIC_NETWORK_RPC_URL=https://sepolia-rpc.scroll.io NEXT_PUBLIC_NETWORK_SHORT_NAME=Scroll Sepolia Testnet NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/scroll-testnet.png -NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=6Ld0iT8aAAAAAJdju0CmAwGjW7JTDvIw-Q5pwt5T +NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth-sepolia.blockscout.com +NEXT_PUBLIC_ROLLUP_TYPE=scroll NEXT_PUBLIC_STATS_API_HOST=https://stats-scroll-sepolia.k8s-prod-2.blockscout.com NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout -NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com -NEXT_PUBLIC_ROLLUP_TYPE=scroll -NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth-sepolia.blockscout.com/ \ No newline at end of file +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com \ No newline at end of file diff --git a/decs.d.ts b/decs.d.ts index 434d031e55..0f623b644b 100644 --- a/decs.d.ts +++ b/decs.d.ts @@ -1 +1,2 @@ -declare module 'react-identicons' +declare module 'react-identicons'; +declare module 'use-font-face-observer'; diff --git a/deploy/helmfile.yaml b/deploy/helmfile.yaml index 6052eda969..199073c4ad 100644 --- a/deploy/helmfile.yaml +++ b/deploy/helmfile.yaml @@ -8,8 +8,8 @@ helmDefaults: recreatePods: false repositories: - - name: blockscout-ci-cd - url: https://blockscout.github.io/blockscout-ci-cd + # - name: blockscout-ci-cd + # url: https://blockscout.github.io/blockscout-ci-cd - name: blockscout url: https://blockscout.github.io/helm-charts - name: bedag @@ -65,4 +65,4 @@ releases: values: - values/review/values.yaml.gotmpl - global: - env: "review" \ No newline at end of file + env: "review" diff --git a/deploy/scripts/build_sprite.sh b/deploy/scripts/build_sprite.sh index 0db22fd14a..5eace17256 100755 --- a/deploy/scripts/build_sprite.sh +++ b/deploy/scripts/build_sprite.sh @@ -1,20 +1,70 @@ #!/bin/bash -yarn icons build -i ./icons -o ./public/icons --optimize +icons_dir="./icons" +target_dir="./public/icons" + +yarn icons build -i $icons_dir -o $target_dir --optimize + +create_registry_file() { + # Create a temporary file to store the registry + local registry_file="$target_dir/registry.json" + + # Start the JSON array + echo "[]" > "$registry_file" + + # Detect OS and set appropriate stat command + get_file_size() { + local file="$1" + if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS + stat -f%z "$file" + else + # Linux and others + stat -c%s "$file" + fi + } + + # Function to process each file + process_file() { + local file="$1" + local relative_path="${file#$icons_dir/}" + local file_size=$(get_file_size "$file") + + # Create a temporary file with the new entry + jq --arg name "$relative_path" --arg size "$file_size" \ + '. + [{"name": $name, "file_size": ($size|tonumber)}]' \ + "$registry_file" > "${registry_file}.tmp" + + # Move the temporary file back + mv "${registry_file}.tmp" "$registry_file" + } + + # Find all SVG files and process them + find "$icons_dir" -type f -name "*.svg" | while read -r file; do + process_file "$file" + done +} # Skip hash creation and renaming for playwright environment if [ "$NEXT_PUBLIC_APP_ENV" != "pw" ]; then # Generate hash from the sprite file - HASH=$(md5sum ./public/icons/sprite.svg | cut -d' ' -f1 | head -c 8) + HASH=$(md5sum $target_dir/sprite.svg | cut -d' ' -f1 | head -c 8) # Remove old sprite files - rm -f ./public/icons/sprite.*.svg + rm -f $target_dir/sprite.*.svg # Rename the new sprite file - mv ./public/icons/sprite.svg "./public/icons/sprite.${HASH}.svg" + mv $target_dir/sprite.svg "$target_dir/sprite.${HASH}.svg" export NEXT_PUBLIC_ICON_SPRITE_HASH=${HASH} + # Skip registry creation in development environment + # just to make the dev build faster + # remove this condition if you want to create the registry file in development environment + if [ "$NEXT_PUBLIC_APP_ENV" != "development" ]; then + create_registry_file + fi + echo "SVG sprite created: sprite.${HASH}.svg" else echo "SVG sprite created: sprite.svg (hash skipped for playwright environment)" diff --git a/deploy/scripts/entrypoint.sh b/deploy/scripts/entrypoint.sh index d4a7cd8134..6abcb1fa6c 100755 --- a/deploy/scripts/entrypoint.sh +++ b/deploy/scripts/entrypoint.sh @@ -35,9 +35,6 @@ export_envs_from_preset() { # If there is a preset, load the environment variables from the its file export_envs_from_preset -# Generate OG image -node --no-warnings ./og_image_generator.js - # Download external assets ./download_assets.sh ./public/assets/configs @@ -61,6 +58,9 @@ else fi echo +# Generate OG image +node --no-warnings ./og_image_generator.js + # Create envs.js file with run-time environment variables for the client app ./make_envs_script.sh @@ -71,4 +71,4 @@ echo node ./feature-reporter.js echo "Starting Next.js application" -exec "$@" \ No newline at end of file +exec "$@" diff --git a/deploy/scripts/og_image_generator.js b/deploy/scripts/og_image_generator.js index b9b2edd2ba..6f607376ff 100755 --- a/deploy/scripts/og_image_generator.js +++ b/deploy/scripts/og_image_generator.js @@ -28,6 +28,7 @@ if (process.env.NEXT_PUBLIC_OG_IMAGE_URL) { background: bannerConfig.background?.[0], title_color: bannerConfig.text_color?.[0], invert_logo: !process.env.NEXT_PUBLIC_NETWORK_LOGO_DARK, + app_url: process.env.NEXT_PUBLIC_APP_HOST, }; console.log('⏳ Making request to OG image generator service...'); diff --git a/deploy/tools/envs-validator/schema.ts b/deploy/tools/envs-validator/schema.ts index 2acf484528..7f1d60df7d 100644 --- a/deploy/tools/envs-validator/schema.ts +++ b/deploy/tools/envs-validator/schema.ts @@ -346,6 +346,17 @@ const rollupSchema = yup value => value === undefined, ), }), + NEXT_PUBLIC_INTEROP_ENABLED: yup + .boolean() + .when('NEXT_PUBLIC_ROLLUP_TYPE', { + is: 'optimistic', + then: (schema) => schema, + otherwise: (schema) => schema.test( + 'not-exist', + 'NEXT_PUBLIC_INTEROP_ENABLED can only be used if NEXT_PUBLIC_ROLLUP_TYPE is set to \'optimistic\' ', + value => value === undefined, + ), + }), NEXT_PUBLIC_ROLLUP_PARENT_CHAIN_NAME: yup .string() .when('NEXT_PUBLIC_ROLLUP_TYPE', { diff --git a/deploy/tools/envs-validator/test/.env.optimism b/deploy/tools/envs-validator/test/.env.optimism index 8bd94ce0ea..32d36f3975 100644 --- a/deploy/tools/envs-validator/test/.env.optimism +++ b/deploy/tools/envs-validator/test/.env.optimism @@ -5,3 +5,4 @@ NEXT_PUBLIC_FAULT_PROOF_ENABLED=true NEXT_PUBLIC_ROLLUP_HOMEPAGE_SHOW_LATEST_BLOCKS=true NEXT_PUBLIC_ROLLUP_OUTPUT_ROOTS_ENABLED=false NEXT_PUBLIC_ROLLUP_PARENT_CHAIN={'baseUrl':'https://explorer.duckchain.io'} +NEXT_PUBLIC_INTEROP_ENABLED=true \ No newline at end of file diff --git a/deploy/tools/envs-validator/tsconfig.json b/deploy/tools/envs-validator/tsconfig.json index a169faac5c..f7911a9e1a 100644 --- a/deploy/tools/envs-validator/tsconfig.json +++ b/deploy/tools/envs-validator/tsconfig.json @@ -4,6 +4,7 @@ "noEmit": false, "target": "es2016", "module": "CommonJS", + "moduleResolution": "node", "paths": { "nextjs-routes": ["./nextjs/nextjs-routes.d.ts"], } diff --git a/deploy/tools/feature-reporter/tsconfig.json b/deploy/tools/feature-reporter/tsconfig.json index 1fefb24f4c..86b7068227 100644 --- a/deploy/tools/feature-reporter/tsconfig.json +++ b/deploy/tools/feature-reporter/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "noEmit": false, "module": "CommonJS", + "moduleResolution": "node", "outDir": "./build", "paths": { "nextjs-routes": ["./nextjs/nextjs-routes.d.ts"], diff --git a/deploy/tools/sitemap-generator/next-sitemap.config.js b/deploy/tools/sitemap-generator/next-sitemap.config.js index 98d55e9e11..a28468d42b 100644 --- a/deploy/tools/sitemap-generator/next-sitemap.config.js +++ b/deploy/tools/sitemap-generator/next-sitemap.config.js @@ -59,7 +59,7 @@ module.exports = { { userAgent: '*', allow: '/', - disallow: ['/auth/*', '/login', '/sprite', '/account/*', '/api/*', '/node-api/*'], + disallow: ['/auth/*', '/login', '/chakra', '/sprite', '/account/*', '/api/*', '/node-api/*'], }, ], }, @@ -70,6 +70,7 @@ module.exports = { '/auth/*', '/login', '/sprite', + '/chakra', ], transform: async(config, path) => { switch (path) { @@ -114,6 +115,11 @@ module.exports = { return null; } break; + case '/interop-messages': + if (process.env.NEXT_PUBLIC_INTEROP_ENABLED !== 'true') { + return null; + } + break; case '/pools': if (process.env.NEXT_PUBLIC_DEX_POOLS_ENABLED !== 'true') { return null; diff --git a/docs/ENVS.md b/docs/ENVS.md index 0a89d60fa2..05fa5d75cc 100644 --- a/docs/ENVS.md +++ b/docs/ENVS.md @@ -2,11 +2,16 @@ The app instance can be customized by passing the following variables to the Node.js environment at runtime. Some of these variables have been deprecated, and their full list can be found in the [file](./DEPRECATED_ENVS.md). -**IMPORTANT NOTE!** For _production_ build purposes all json-like values should be single-quoted. If it contains a hash (`#`) or a dollar-sign (`$`) the whole value should be wrapped in single quotes as well (see `dotenv` [readme](https://github.com/bkeepers/dotenv#variable-substitution) for the reference) +## Read before you run the app -## Disclaimer about using variables +### Variables compulsoriness +Please note that in the tables below, the "Compulsoriness" column indicates whether the variable is required for starting up the application, except for the "App Features" section. All features are optional by definition; therefore, the "Compulsoriness" column indicates whether a certain variable is required or optional only within the context of that feature, not for the entire application. -Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will be exposed to the browser. So any user can obtain its values. Make sure that for all 3rd-party services keys (e.g., Sentri, Auth0, WalletConnect, etc.) in the services administration panel you have created a whitelist of allowed origins and have added your app domain into it. That will help you prevent using your key by unauthorized app, if someone gets its value. +### Disclaimer about using variables +Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will be exposed to the browser. So any user can obtain its values. Make sure that for all 3rd-party services keys (e.g., Auth0, WalletConnect, etc.) in the services administration panel you have created a whitelist of allowed origins and have added your app domain into it. That will help you prevent using your key by unauthorized app, if someone gets its value. + +### Note about escaping variables values +All json-like values should be single-quoted. If it contains a hash (`#`) or a dollar-sign (`$`) the whole value should be wrapped in single quotes as well (see `dotenv` [readme](https://github.com/bkeepers/dotenv#variable-substitution) for the reference)   @@ -351,7 +356,7 @@ Settings for meta tags, OG tags and SEO ## App features -*Note* The variables which are marked as required should be passed as described in order to enable the particular feature, but they are not required in the whole app context. +*Note* The variables which are marked as required should be passed as described in order to enable the particular feature, but they are not required in the entire app context. ### My account @@ -461,6 +466,7 @@ This feature is **enabled by default** with the `coinzilla` ads provider. To swi | NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL | `string` | URL for L2 -> L1 withdrawals (Optimistic stack only) | Required for `optimistic` rollups | - | `https://app.optimism.io/bridge/withdraw` | v1.24.0+ | | NEXT_PUBLIC_FAULT_PROOF_ENABLED | `boolean` | Set to `true` for chains with fault proof system enabled (Optimistic stack only) | - | - | `true` | v1.31.0+ | | NEXT_PUBLIC_HAS_MUD_FRAMEWORK | `boolean` | Set to `true` for instances that use MUD framework (Optimistic stack only) | - | - | `true` | v1.33.0+ | +| NEXT_PUBLIC_INTEROP_ENABLED | `boolean` | Enables "Interop messages" page (Optimistic stack only) | - | `false` | `true` | v1.39.0+ | | NEXT_PUBLIC_ROLLUP_HOMEPAGE_SHOW_LATEST_BLOCKS | `boolean` | Set to `true` to display "Latest blocks" widget instead of "Latest batches" on the home page | - | - | `true` | v1.36.0+ | | NEXT_PUBLIC_ROLLUP_OUTPUT_ROOTS_ENABLED | `boolean` | Enables "Output roots" page (Optimistic stack only) | - | `false` | `true` | v1.37.0+ | | NEXT_PUBLIC_ROLLUP_PARENT_CHAIN_NAME | `string` | Set to customize L1 transaction status labels in the UI (e.g., "Sent to "). This setting is applicable only for Arbitrum-based chains. **DEPRECATED** _Use `NEXT_PUBLIC_ROLLUP_PARENT_CHAIN` instead_ | - | - | `DuckChain` | v1.37.0+ | diff --git a/eslint.config.mjs b/eslint.config.mjs index 649a06c34f..8483f6bede 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -27,13 +27,24 @@ const RESTRICTED_MODULES = { { name: 'playwright/TestApp', message: 'Please use render() fixture from test() function of playwright/lib module' }, { name: '@chakra-ui/react', - importNames: [ 'Popover', 'Menu', 'PinInput', 'useToast', 'Skeleton' ], - message: 'Please use corresponding component or hook from ui/shared/chakra component instead', + importNames: [ + 'Menu', 'useToast', 'useDisclosure', 'useClipboard', 'Tooltip', 'Skeleton', 'IconButton', 'Button', 'ButtonGroup', 'Link', 'LinkBox', 'LinkOverlay', + 'Dialog', 'DialogRoot', 'DialogContent', 'DialogHeader', 'DialogCloseTrigger', + 'Tag', 'Switch', 'Image', 'Popover', 'PopoverTrigger', 'PopoverContent', 'PopoverBody', 'PopoverFooter', + 'DrawerRoot', 'DrawerBody', 'DrawerContent', 'DrawerOverlay', 'DrawerBackdrop', 'DrawerTrigger', 'Drawer', + 'Alert', 'AlertIcon', 'AlertTitle', 'AlertDescription', + 'Select', 'SelectRoot', 'SelectControl', 'SelectContent', 'SelectItem', 'SelectValueText', + 'Heading', 'Badge', 'Tabs', 'Show', 'Hide', 'Checkbox', 'CheckboxGroup', + 'Table', 'TableRoot', 'TableBody', 'TableHeader', 'TableRow', 'TableCell', + 'Menu', 'MenuRoot', 'MenuTrigger', 'MenuContent', 'MenuItem', 'MenuTriggerItem', 'MenuRadioItemGroup', 'MenuContextTrigger', + 'Rating', 'RatingGroup', + ], + message: 'Please use corresponding component or hook from "toolkit" instead', }, { name: 'next/link', importNames: [ 'default' ], - message: 'Please use ui/shared/NextLink component instead', + message: 'Please use toolkit/chakra/link component instead', }, ], patterns: [ @@ -294,6 +305,7 @@ export default tseslint.config( '/^playwright/', '/^stubs/', '/^theme/', + '/^toolkit/', '/^ui/', ], [ 'parent', 'sibling', 'index' ], @@ -440,4 +452,13 @@ export default tseslint.config( 'no-restricted-properties': 'off', }, }, + { + files: [ + 'toolkit/chakra/**', + ], + rules: { + // for toolkit components allow to import @chakra-ui/react directly + 'no-restricted-imports': 'off', + }, + }, ); diff --git a/icons/close.svg b/icons/close.svg new file mode 100644 index 0000000000..0631c7c76b --- /dev/null +++ b/icons/close.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/cross.svg b/icons/cross.svg index 44a66fcc75..8bf9679845 100644 --- a/icons/cross.svg +++ b/icons/cross.svg @@ -1,3 +1,3 @@ - - + + diff --git a/icons/heart_filled.svg b/icons/heart_filled.svg index 80926b1668..64635b353c 100644 --- a/icons/heart_filled.svg +++ b/icons/heart_filled.svg @@ -1,3 +1,3 @@ - - + + diff --git a/icons/heart_outline.svg b/icons/heart_outline.svg index 8bf7ce3e36..34c36398f8 100644 --- a/icons/heart_outline.svg +++ b/icons/heart_outline.svg @@ -1,4 +1,4 @@ - - - + + + diff --git a/icons/info_filled.svg b/icons/info_filled.svg new file mode 100644 index 0000000000..19bf3c0d31 --- /dev/null +++ b/icons/info_filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/interop.svg b/icons/interop.svg new file mode 100644 index 0000000000..b5692777ea --- /dev/null +++ b/icons/interop.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/search.svg b/icons/search.svg index 1d7814fa05..c1849adc0d 100644 --- a/icons/search.svg +++ b/icons/search.svg @@ -1,3 +1,3 @@ - + diff --git a/icons/wallets/metamask.svg b/icons/wallets/metamask.svg index ea6a6382a5..220fbc130f 100644 --- a/icons/wallets/metamask.svg +++ b/icons/wallets/metamask.svg @@ -1,14 +1,16 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/jest/lib.tsx b/jest/lib.tsx index 061b375fe6..f6119698c7 100644 --- a/jest/lib.tsx +++ b/jest/lib.tsx @@ -1,4 +1,3 @@ -import { ChakraProvider } from '@chakra-ui/react'; import { GrowthBookProvider } from '@growthbook/growthbook-react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import type { RenderOptions } from '@testing-library/react'; @@ -8,7 +7,7 @@ import React from 'react'; import { AppContextProvider } from 'lib/contexts/app'; import { ScrollDirectionProvider } from 'lib/contexts/scrollDirection'; import { SocketProvider } from 'lib/socket/context'; -import theme from 'theme/theme'; +import { Provider as ChakraProvider } from 'toolkit/chakra/provider'; import 'lib/setLocale'; @@ -32,7 +31,7 @@ const TestApp = ({ children }: { children: React.ReactNode }) => { })); return ( - + diff --git a/jest/setup.ts b/jest/setup.ts index 6ccc67ad4c..d64cff2d48 100644 --- a/jest/setup.ts +++ b/jest/setup.ts @@ -41,3 +41,15 @@ global.console = { consoleError(...args); }, }; + +// Polyfill for structuredClone +if (typeof structuredClone === 'undefined') { + global.structuredClone = (obj: T): T => { + try { + return JSON.parse(JSON.stringify(obj)) as T; + } catch (error) { + // Fallback for circular references and other special cases + return obj; + } + }; +} diff --git a/lib/address/getCheckedSummedAddress.ts b/lib/address/getCheckedSummedAddress.ts index 5c92b9a390..c3779744f8 100644 --- a/lib/address/getCheckedSummedAddress.ts +++ b/lib/address/getCheckedSummedAddress.ts @@ -2,12 +2,18 @@ import { getAddress } from 'viem'; import config from 'configs/app'; +const ERC1191_CHAIN_IDS = [ + '30', // RSK Mainnet + '31', // RSK Testnet +]; + export default function getCheckedSummedAddress(address: string): string { try { return getAddress( address, - // We need to pass chainId to getAddress to make it work correctly for some chains, e.g. Rootstock - config.chain.id ? Number(config.chain.id) : undefined, + // We need to pass chainId to getAddress to make it work correctly for chains that support ERC-1191 + // https://eips.ethereum.org/EIPS/eip-1191#usage--table + ERC1191_CHAIN_IDS.includes(config.chain.id ?? '') ? Number(config.chain.id) : undefined, ); } catch (error) { return address; diff --git a/lib/api/resources.ts b/lib/api/resources.ts index e23a78f936..c593b2f17b 100644 --- a/lib/api/resources.ts +++ b/lib/api/resources.ts @@ -82,6 +82,7 @@ import type { } from 'types/api/ens'; import type { IndexingStatus } from 'types/api/indexingStatus'; import type { InternalTransactionsResponse } from 'types/api/internalTransaction'; +import type { InteropMessageListResponse } from 'types/api/interop'; import type { LogsResponseTx, LogsResponseAddress } from 'types/api/log'; import type { MudWorldsResponse } from 'types/api/mudWorlds'; import type { NovesAccountHistoryResponse, NovesDescribeTxsResponse, NovesResponseData } from 'types/api/noves'; @@ -1243,6 +1244,15 @@ export const RESOURCES = { block_countdown: { path: '/api', }, + + // INTEROP + optimistic_l2_interop_messages: { + path: '/api/v2/optimism/interop/messages', + filterFields: [], + }, + optimistic_l2_interop_messages_count: { + path: '/api/v2/optimism/interop/messages/count', + }, }; export type ResourceName = keyof typeof RESOURCES; @@ -1296,7 +1306,7 @@ export type PaginatedResources = 'blocks' | 'block_txs' | 'block_election_reward 'watchlist' | 'private_tags_address' | 'private_tags_tx' | 'domains_lookup' | 'addresses_lookup' | 'user_ops' | 'validators_stability' | 'validators_blackfort' | 'validators_zilliqa' | 'noves_address_history' | 'token_transfers_all' | 'scroll_l2_txn_batches' | 'scroll_l2_txn_batch_txs' | 'scroll_l2_txn_batch_blocks' | -'scroll_l2_deposits' | 'scroll_l2_withdrawals' | 'advanced_filter' | 'pools'; +'scroll_l2_deposits' | 'scroll_l2_withdrawals' | 'advanced_filter' | 'pools' | 'optimistic_l2_interop_messages'; export type PaginatedResponse = ResourcePayload; @@ -1473,6 +1483,8 @@ Q extends 'optimistic_l2_output_roots_count' ? number : Q extends 'optimistic_l2_withdrawals_count' ? number : Q extends 'optimistic_l2_deposits_count' ? number : Q extends 'optimistic_l2_dispute_games_count' ? number : +Q extends 'optimistic_l2_interop_messages' ? InteropMessageListResponse : +Q extends 'optimistic_l2_interop_messages_count' ? number : Q extends 'shibarium_withdrawals' ? ShibariumWithdrawalsResponse : Q extends 'shibarium_deposits' ? ShibariumDepositsResponse : Q extends 'shibarium_withdrawals_count' ? number : diff --git a/lib/contexts/chakra.tsx b/lib/contexts/chakra.tsx deleted file mode 100644 index 3ace6cd278..0000000000 --- a/lib/contexts/chakra.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { - ChakraProvider as ChakraProviderDefault, - cookieStorageManagerSSR, - localStorageManager, -} from '@chakra-ui/react'; -import type { ChakraProviderProps } from '@chakra-ui/react'; -import React from 'react'; - -import { get, NAMES } from 'lib/cookies'; -import theme from 'theme/theme'; - -interface Props extends ChakraProviderProps { - cookies?: string; -} - -export function ChakraProvider({ cookies, children }: Props) { - // When a cookie header is present, cookieStorageManagerSSR looks for a "chakra-ui-color-mode" cookie. - // If it doesn’t find one, it doesn’t consider theme’s initialColorMode. Instead, it is defaulting to light mode - // So we need to use localStorageManager instead of cookieStorageManagerSSR to get the correct default color mode - const colorModeManager = - typeof cookies === 'string' && get(NAMES.COLOR_MODE, cookies) ? - cookieStorageManagerSSR(typeof document !== 'undefined' ? document.cookie : cookies) : - localStorageManager; - - return ( - - { children } - - ); -} diff --git a/lib/contexts/rewards.tsx b/lib/contexts/rewards.tsx index b74aa95f1b..672bfebce9 100644 --- a/lib/contexts/rewards.tsx +++ b/lib/contexts/rewards.tsx @@ -1,6 +1,6 @@ -import { useBoolean } from '@chakra-ui/react'; import type { UseQueryResult } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query'; +import { useToggle } from '@uidotdev/usehooks'; import { useRouter } from 'next/router'; import React, { createContext, useContext, useEffect, useMemo, useCallback } from 'react'; import { useSignMessage } from 'wagmi'; @@ -22,10 +22,10 @@ import * as cookies from 'lib/cookies'; import decodeJWT from 'lib/decodeJWT'; import getErrorMessage from 'lib/errors/getErrorMessage'; import getErrorObjPayload from 'lib/errors/getErrorObjPayload'; -import useToast from 'lib/hooks/useToast'; import getQueryParamString from 'lib/router/getQueryParamString'; import removeQueryParam from 'lib/router/removeQueryParam'; import useAccount from 'lib/web3/useAccount'; +import { toaster } from 'toolkit/chakra/toaster'; import useProfileQuery from 'ui/snippets/auth/useProfileQuery'; const feature = config.features.rewards; @@ -45,7 +45,7 @@ type TRewardsContext = { openLoginModal: () => void; closeLoginModal: () => void; saveApiToken: (token: string | undefined) => void; - login: (refCode: string) => Promise<{ isNewUser?: boolean; invalidRefCodeError?: boolean }>; + login: (refCode: string) => Promise<{ isNewUser: boolean; reward: string | null; invalidRefCodeError?: boolean }>; claim: () => Promise; }; @@ -70,7 +70,7 @@ const initialState = { openLoginModal: () => {}, closeLoginModal: () => {}, saveApiToken: () => {}, - login: async() => ({}), + login: async() => ({ isNewUser: false, reward: null }), claim: async() => {}, }; @@ -116,13 +116,12 @@ export function RewardsContextProvider({ children }: Props) { const router = useRouter(); const queryClient = useQueryClient(); const apiFetch = useApiFetch(); - const toast = useToast(); const { address } = useAccount(); const { signMessageAsync } = useSignMessage(); const profileQuery = useProfileQuery(); - const [ isLoginModalOpen, setIsLoginModalOpen ] = useBoolean(false); - const [ isInitialized, setIsInitialized ] = useBoolean(false); + const [ isLoginModalOpen, setIsLoginModalOpen ] = useToggle(false); + const [ isInitialized, setIsInitialized ] = useToggle(false); const [ apiToken, setApiToken ] = React.useState(); // Initialize state with the API token from cookies @@ -133,7 +132,7 @@ export function RewardsContextProvider({ children }: Props) { if (registeredAddress === profileQuery.data?.address_hash) { setApiToken(token); } - setIsInitialized.on(); + setIsInitialized(true); } }, [ setIsInitialized, profileQuery ]); @@ -189,22 +188,18 @@ export function RewardsContextProvider({ children }: Props) { cookies.set(cookies.NAMES.REWARDS_REFERRAL_CODE, refCode); removeQueryParam(router, 'ref'); if (!apiToken) { - setIsLoginModalOpen.on(); + setIsLoginModalOpen(true); } } }, [ router, apiToken, isInitialized, setIsLoginModalOpen ]); const errorToast = useCallback((error: unknown) => { const apiError = getErrorObjPayload<{ message: string }>(error); - toast({ - position: 'top-right', + toaster.error({ title: 'Error', description: apiError?.message || getErrorMessage(error) || 'Something went wrong. Try again later.', - status: 'error', - variant: 'subtle', - isClosable: true, }); - }, [ toast ]); + }, [ ]); // Login to the rewards program const login = useCallback(async(refCode: string) => { @@ -216,10 +211,14 @@ export function RewardsContextProvider({ children }: Props) { apiFetch('rewards_nonce') as Promise, refCode ? apiFetch('rewards_check_ref_code', { pathParams: { code: refCode } }) as Promise : - Promise.resolve({ valid: true }), + Promise.resolve({ valid: true, reward: null }), ]); if (!checkCodeResponse.valid) { - return { invalidRefCodeError: true }; + return { + invalidRefCodeError: true, + isNewUser: false, + reward: null, + }; } const message = getMessageToSign(address, nonceResponse.nonce, checkUserQuery.data?.exists, refCode); const signature = await signMessageAsync({ message }); @@ -234,7 +233,10 @@ export function RewardsContextProvider({ children }: Props) { }, }) as RewardsLoginResponse; saveApiToken(loginResponse.token); - return { isNewUser: loginResponse.created }; + return { + isNewUser: loginResponse.created, + reward: checkCodeResponse.reward, + }; } catch (_error) { errorToast(_error); throw _error; @@ -256,6 +258,14 @@ export function RewardsContextProvider({ children }: Props) { } }, [ apiFetch, errorToast, fetchParams ]); + const openLoginModal = React.useCallback(() => { + setIsLoginModalOpen(true); + }, [ setIsLoginModalOpen ]); + + const closeLoginModal = React.useCallback(() => { + setIsLoginModalOpen(false); + }, [ setIsLoginModalOpen ]); + const value = useMemo(() => { if (!feature.isEnabled) { return initialState; @@ -270,14 +280,15 @@ export function RewardsContextProvider({ children }: Props) { saveApiToken, isInitialized, isLoginModalOpen, - openLoginModal: setIsLoginModalOpen.on, - closeLoginModal: setIsLoginModalOpen.off, + openLoginModal, + closeLoginModal, login, claim, }; }, [ - isLoginModalOpen, setIsLoginModalOpen, balancesQuery, dailyRewardQuery, checkUserQuery, + balancesQuery, dailyRewardQuery, checkUserQuery, apiToken, login, claim, referralsQuery, rewardsConfigQuery, isInitialized, saveApiToken, + isLoginModalOpen, openLoginModal, closeLoginModal, ]); return ( diff --git a/lib/hooks/useGraphLinks.tsx b/lib/hooks/useGraphLinks.tsx index b8f83b4cf8..8943f38bae 100644 --- a/lib/hooks/useGraphLinks.tsx +++ b/lib/hooks/useGraphLinks.tsx @@ -9,7 +9,7 @@ const feature = config.features.marketplace; export default function useGraphLinks() { const fetch = useFetch(); - return useQuery, Record>>({ + return useQuery, Record>>({ queryKey: [ 'graph-links' ], queryFn: async() => fetch((feature.isEnabled && feature.graphLinksUrl) ? feature.graphLinksUrl : '', undefined, { resource: 'graph-links' }), enabled: feature.isEnabled && Boolean(feature.graphLinksUrl), diff --git a/lib/hooks/useInitialList.tsx b/lib/hooks/useInitialList.tsx new file mode 100644 index 0000000000..e8f5aab9a3 --- /dev/null +++ b/lib/hooks/useInitialList.tsx @@ -0,0 +1,36 @@ +import React from 'react'; + +type Id = string | number; + +export interface Params { + data: Array; + idFn: (item: T) => Id; + enabled: boolean; +} + +export default function useInitialList({ data, idFn, enabled }: Params) { + const [ list, setList ] = React.useState>([]); + + React.useEffect(() => { + if (enabled) { + setList(data.map(idFn)); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ enabled ]); + + const isNew = React.useCallback((data: T) => { + return !list.includes(idFn(data)); + }, [ list, idFn ]); + + const getAnimationProp = React.useCallback((data: T) => { + return isNew(data) ? 'fade-in 500ms linear' : undefined; + }, [ isNew ]); + + return React.useMemo(() => { + return { + list, + isNew, + getAnimationProp, + }; + }, [ list, isNew, getAnimationProp ]); +} diff --git a/lib/hooks/useNavItems.tsx b/lib/hooks/useNavItems.tsx index 28c130c713..fda83f31b8 100644 --- a/lib/hooks/useNavItems.tsx +++ b/lib/hooks/useNavItems.tsx @@ -115,6 +115,13 @@ export default function useNavItems(): ReturnType { const rollupFeature = config.features.rollup; + const rollupInteropMessages = rollupFeature.isEnabled && rollupFeature.interopEnabled ? { + text: 'Interop messages', + nextRoute: { pathname: '/interop-messages' as const }, + icon: 'interop', + isActive: pathname === '/interop-messages', + } : null; + if (rollupFeature.isEnabled && ( rollupFeature.type === 'optimistic' || rollupFeature.type === 'arbitrum' || @@ -127,7 +134,8 @@ export default function useNavItems(): ReturnType { internalTxs, rollupDeposits, rollupWithdrawals, - ], + rollupInteropMessages, + ].filter(Boolean), [ blocks, rollupTxnBatches, diff --git a/lib/hooks/useToast.tsx b/lib/hooks/useToast.tsx deleted file mode 100644 index 7afc6f3e11..0000000000 --- a/lib/hooks/useToast.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import type { UseToastOptions, ToastProps } from '@chakra-ui/react'; -import { createToastFn, useChakra } from '@chakra-ui/react'; -import React from 'react'; - -import Toast from 'ui/shared/chakra/Toast'; - -// there is no toastComponent prop in UseToastOptions type -// but these options will be passed to createRenderToast under the hood -// and it accepts custom toastComponent -const defaultOptions: UseToastOptions & { toastComponent?: React.FC } = { - toastComponent: Toast, - position: 'top-right', - isClosable: true, - containerStyle: { - margin: 3, - marginBottom: 0, - }, - variant: 'subtle', -}; - -export default function useToastModified() { - const { theme } = useChakra(); - - return React.useMemo( - () => createToastFn(theme.direction, defaultOptions), - [ theme.direction ], - ); -} diff --git a/lib/metadata/getPageOgType.ts b/lib/metadata/getPageOgType.ts index 8f26075149..70a299cf99 100644 --- a/lib/metadata/getPageOgType.ts +++ b/lib/metadata/getPageOgType.ts @@ -60,10 +60,12 @@ const OG_TYPE_DICT: Record = { '/advanced-filter': 'Root page', '/pools': 'Root page', '/pools/[hash]': 'Regular page', + '/interop-messages': 'Root page', // service routes, added only to make typescript happy '/login': 'Regular page', '/sprite': 'Regular page', + '/chakra': 'Regular page', '/api/metrics': 'Regular page', '/api/monitoring/invalid-api-schema': 'Regular page', '/api/log': 'Regular page', @@ -72,7 +74,6 @@ const OG_TYPE_DICT: Record = { '/api/csrf': 'Regular page', '/api/healthz': 'Regular page', '/api/config': 'Regular page', - '/api/sprite': 'Regular page', }; export default function getPageOgType(pathname: Route['pathname']) { diff --git a/lib/metadata/templates/description.ts b/lib/metadata/templates/description.ts index 6c738d5bf2..b90e9e6fe1 100644 --- a/lib/metadata/templates/description.ts +++ b/lib/metadata/templates/description.ts @@ -63,10 +63,12 @@ const TEMPLATE_MAP: Record = { '/advanced-filter': DEFAULT_TEMPLATE, '/pools': DEFAULT_TEMPLATE, '/pools/[hash]': DEFAULT_TEMPLATE, + '/interop-messages': DEFAULT_TEMPLATE, // service routes, added only to make typescript happy '/login': DEFAULT_TEMPLATE, '/sprite': DEFAULT_TEMPLATE, + '/chakra': DEFAULT_TEMPLATE, '/api/metrics': DEFAULT_TEMPLATE, '/api/monitoring/invalid-api-schema': DEFAULT_TEMPLATE, '/api/log': DEFAULT_TEMPLATE, @@ -75,7 +77,6 @@ const TEMPLATE_MAP: Record = { '/api/csrf': DEFAULT_TEMPLATE, '/api/healthz': DEFAULT_TEMPLATE, '/api/config': DEFAULT_TEMPLATE, - '/api/sprite': DEFAULT_TEMPLATE, }; const TEMPLATE_MAP_ENHANCED: Partial> = { diff --git a/lib/metadata/templates/title.ts b/lib/metadata/templates/title.ts index 9c3f62ec89..947fc5cbaf 100644 --- a/lib/metadata/templates/title.ts +++ b/lib/metadata/templates/title.ts @@ -60,10 +60,12 @@ const TEMPLATE_MAP: Record = { '/advanced-filter': '%network_name% advanced filter', '/pools': '%network_name% DEX pools', '/pools/[hash]': '%network_name% pool details', + '/interop-messages': '%network_name% interop messages', // service routes, added only to make typescript happy '/login': '%network_name% login', '/sprite': '%network_name% SVG sprite', + '/chakra': '%network_name% Chakra UI showcase', '/api/metrics': '%network_name% node API prometheus metrics', '/api/monitoring/invalid-api-schema': '%network_name% node API prometheus metrics', '/api/log': '%network_name% node API request log', @@ -72,7 +74,6 @@ const TEMPLATE_MAP: Record = { '/api/csrf': '%network_name% node API CSRF token', '/api/healthz': '%network_name% node API health check', '/api/config': '%network_name% node API app config', - '/api/sprite': '%network_name% node API SVG sprite content', }; const TEMPLATE_MAP_ENHANCED: Partial> = { diff --git a/lib/mixpanel/getPageType.ts b/lib/mixpanel/getPageType.ts index 12ae0eb2cb..4303c2d111 100644 --- a/lib/mixpanel/getPageType.ts +++ b/lib/mixpanel/getPageType.ts @@ -58,10 +58,12 @@ export const PAGE_TYPE_DICT: Record = { '/advanced-filter': 'Advanced filter', '/pools': 'DEX pools', '/pools/[hash]': 'Pool details', + '/interop-messages': 'Interop messages', // service routes, added only to make typescript happy '/login': 'Login', '/sprite': 'Sprite', + '/chakra': 'Chakra UI showcase', '/api/metrics': 'Node API: Prometheus metrics', '/api/monitoring/invalid-api-schema': 'Node API: Prometheus metrics', '/api/log': 'Node API: Request log', @@ -70,7 +72,6 @@ export const PAGE_TYPE_DICT: Record = { '/api/csrf': 'Node API: CSRF token', '/api/healthz': 'Node API: Health check', '/api/config': 'Node API: App config', - '/api/sprite': 'Node API: SVG sprite content', }; export default function getPageType(pathname: Route['pathname']) { diff --git a/lib/mixpanel/useLogPageView.tsx b/lib/mixpanel/useLogPageView.tsx index da5a6952f0..b7a09a6d61 100644 --- a/lib/mixpanel/useLogPageView.tsx +++ b/lib/mixpanel/useLogPageView.tsx @@ -1,5 +1,3 @@ -import type { ColorMode } from '@chakra-ui/react'; -import { useColorMode } from '@chakra-ui/react'; import { usePathname } from 'next/navigation'; import { useRouter } from 'next/router'; import React from 'react'; @@ -8,6 +6,8 @@ import config from 'configs/app'; import * as cookies from 'lib/cookies'; import getQueryParamString from 'lib/router/getQueryParamString'; import { COLOR_THEMES } from 'lib/settings/colorTheme'; +import { useColorMode } from 'toolkit/chakra/color-mode'; +import type { ColorMode } from 'toolkit/chakra/color-mode'; import getPageType from './getPageType'; import getTabName from './getTabName'; diff --git a/lib/settings/colorTheme.ts b/lib/settings/colorTheme.ts index c5b7edaa1f..5745ea693d 100644 --- a/lib/settings/colorTheme.ts +++ b/lib/settings/colorTheme.ts @@ -1,7 +1,7 @@ -import type { ColorMode } from '@chakra-ui/react'; - import type { ColorThemeId } from 'types/settings'; +import type { ColorMode } from 'toolkit/chakra/color-mode'; + interface ColorTheme { id: ColorThemeId; label: string; diff --git a/lib/socket/types.ts b/lib/socket/types.ts index 11ae7db150..5674dd1449 100644 --- a/lib/socket/types.ts +++ b/lib/socket/types.ts @@ -49,13 +49,13 @@ interface SocketMessageParamsGeneric; - export type BlocksIndexStatus = SocketMessageParamsGeneric<'block_index_status', { finished: boolean; ratio: string }>; - export type InternalTxsIndexStatus = SocketMessageParamsGeneric<'internal_txs_index_status', { finished: boolean; ratio: string }>; + export type BlocksIndexStatus = SocketMessageParamsGeneric<'index_status', { finished: boolean; ratio: string }>; + export type InternalTxsIndexStatus = SocketMessageParamsGeneric<'index_status', { finished: boolean; ratio: string }>; export type TxStatusUpdate = SocketMessageParamsGeneric<'collated', NewBlockSocketResponse>; export type TxRawTrace = SocketMessageParamsGeneric<'raw_trace', RawTracesResponse>; export type NewTx = SocketMessageParamsGeneric<'transaction', { transaction: number }>; export type NewPendingTx = SocketMessageParamsGeneric<'pending_transaction', { pending_transaction: number }>; - export type NewOptimisticDeposits = SocketMessageParamsGeneric<'deposits', { deposits: number }>; + export type NewOptimisticDeposits = SocketMessageParamsGeneric<'new_optimism_deposits', { deposits: number }>; export type NewArbitrumDeposits = SocketMessageParamsGeneric<'new_messages_to_rollup_amount', { new_messages_to_rollup_amount: number }>; export type AddressBalance = SocketMessageParamsGeneric<'balance', { balance: string; block_number: number; exchange_rate: string }>; export type AddressCurrentCoinBalance = diff --git a/lib/socket/useSocketChannel.tsx b/lib/socket/useSocketChannel.tsx index f0a67bc9b4..17f9ec3b0d 100644 --- a/lib/socket/useSocketChannel.tsx +++ b/lib/socket/useSocketChannel.tsx @@ -3,7 +3,7 @@ import { useEffect, useRef, useState } from 'react'; import { useSocket } from './context'; -const CHANNEL_REGISTRY: Record = {}; +const CHANNEL_REGISTRY: Record = {}; interface Params { topic: string | undefined; @@ -53,11 +53,12 @@ export default function useSocketChannel({ topic, params, isDisabled, onJoin, on let ch: Channel; if (CHANNEL_REGISTRY[topic]) { - ch = CHANNEL_REGISTRY[topic]; + ch = CHANNEL_REGISTRY[topic].channel; + CHANNEL_REGISTRY[topic].subscribers++; onJoinRef.current?.(ch, ''); } else { ch = socket.channel(topic); - CHANNEL_REGISTRY[topic] = ch; + CHANNEL_REGISTRY[topic] = { channel: ch, subscribers: 1 }; ch.join() .receive('ok', (message) => onJoinRef.current?.(ch, message)) .receive('error', () => { @@ -68,8 +69,14 @@ export default function useSocketChannel({ topic, params, isDisabled, onJoin, on setChannel(ch); return () => { - ch.leave(); - delete CHANNEL_REGISTRY[topic]; + if (CHANNEL_REGISTRY[topic]) { + CHANNEL_REGISTRY[topic].subscribers > 0 && CHANNEL_REGISTRY[topic].subscribers--; + if (CHANNEL_REGISTRY[topic].subscribers === 0) { + ch.leave(); + delete CHANNEL_REGISTRY[topic]; + } + } + setChannel(undefined); }; }, [ socket, topic, params, isDisabled, onSocketError ]); diff --git a/mocks/interop/interop.ts b/mocks/interop/interop.ts new file mode 100644 index 0000000000..ca91595ad0 --- /dev/null +++ b/mocks/interop/interop.ts @@ -0,0 +1,56 @@ +import type { ChainInfo, InteropMessage } from 'types/api/interop'; + +export const chain: ChainInfo = { + chain_id: 1, + chain_name: 'Ethereum', + chain_logo: 'https://example.com/logo.png', + instance_url: 'https://example.com', +}; + +export const interopMessageIn: InteropMessage = { + init_transaction_hash: '0x047A81aFB05D9B1f8844bf60fcA05DCCFbC584B9', + nonce: 1, + payload: 'payload', + relay_transaction_hash: '0x62d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193', + sender: '0x047A81aFB05D9B1f8844bf60fcA05DCCFbC584B9', + status: 'Relayed', + target: '0x047A81aFB05D9B1f8844bf60fcA05DCCFbC584B9', + timestamp: '2022-10-10T14:34:30.000000Z', + init_chain: chain, +}; + +export const interopMessageIn1: InteropMessage = { + init_transaction_hash: '0x047A81aFB05D9B1f8844bf60fcA05DCCFbC584B9', + nonce: 1, + payload: 'payload', + relay_transaction_hash: '0x62d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193', + sender: '0x047A81aFB05D9B1f8844bf60fcA05DCCFbC584B9', + status: 'Sent', + target: '0x047A81aFB05D9B1f8844bf60fcA05DCCFbC584B9', + timestamp: '2022-10-10T14:34:30.000000Z', + init_chain: null, +}; + +export const interopMessageOut: InteropMessage = { + init_transaction_hash: '0x047A81aFB05D9B1f8844bf60fcA05DCCFbC584B9', + nonce: 1, + payload: 'payload', + relay_transaction_hash: '0x62d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193', + sender: '0x047A81aFB05D9B1f8844bf60fcA05DCCFbC584B9', + status: 'Relayed', + target: '0x047A81aFB05D9B1f8844bf60fcA05DCCFbC584B9', + timestamp: '2022-10-10T14:34:30.000000Z', + relay_chain: chain, +}; + +export const interopMessageOut1: InteropMessage = { + init_transaction_hash: '0x047A81aFB05D9B1f8844bf60fcA05DCCFbC584B9', + nonce: 1, + payload: 'payload', + relay_transaction_hash: '0x62d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193', + sender: '0x047A81aFB05D9B1f8844bf60fcA05DCCFbC584B9', + status: 'Failed', + target: '0x047A81aFB05D9B1f8844bf60fcA05DCCFbC584B9', + timestamp: '2022-10-10T14:34:30.000000Z', + relay_chain: null, +}; diff --git a/mocks/tokens/tokenTransfer.ts b/mocks/tokens/tokenTransfer.ts index f17da56ec4..d09044ab31 100644 --- a/mocks/tokens/tokenTransfer.ts +++ b/mocks/tokens/tokenTransfer.ts @@ -1,6 +1,8 @@ import type { TokenInfo } from 'types/api/token'; import type { TokenTransfer, TokenTransferResponse } from 'types/api/tokenTransfer'; +import * as tokenInstanceMock from './tokenInstance'; + export const erc20: TokenTransfer = { from: { hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', @@ -86,6 +88,7 @@ export const erc721: TokenTransfer = { }, total: { token_id: '875879856', + token_instance: tokenInstanceMock.base, }, transaction_hash: '0xf13bc7afe5e02b494dd2f22078381d36a4800ef94a0ccc147431db56c301e6cc', type: 'token_transfer', @@ -135,6 +138,7 @@ export const erc1155A: TokenTransfer = { token_id: '123', value: '42', decimals: null, + token_instance: null, }, transaction_hash: '0x05d6589367633c032d757a69c5fb16c0e33e3994b0d9d1483f82aeee1f05d746', type: 'token_minting', @@ -151,7 +155,7 @@ export const erc1155B: TokenTransfer = { name: 'SastanaNFT', symbol: 'ipfs://QmUpFUfVKDCWeZQk5pvDFUxnpQP9N6eLSHhNUy49T1JVtY', }, - total: { token_id: '12345678', value: '100000000000000000000', decimals: null }, + total: { token_id: '12345678', value: '100000000000000000000', decimals: null, token_instance: null }, }; export const erc1155C: TokenTransfer = { @@ -161,7 +165,7 @@ export const erc1155C: TokenTransfer = { name: 'SastanaNFT', symbol: 'ipfs://QmUpFUfVKDCWeZQk5pvDFUxnpQP9N6eLSHhNUy49T1JVtY', }, - total: { token_id: '483200961027732618117991942553110860267520', value: '200000000000000000000', decimals: null }, + total: { token_id: '483200961027732618117991942553110860267520', value: '200000000000000000000', decimals: null, token_instance: null }, }; export const erc1155D: TokenTransfer = { @@ -171,7 +175,7 @@ export const erc1155D: TokenTransfer = { name: 'SastanaNFT', symbol: 'ipfs://QmUpFUfVKDCWeZQk5pvDFUxnpQP9N6eLSHhNUy49T1JVtY', }, - total: { token_id: '456', value: '42', decimals: null }, + total: { token_id: '456', value: '42', decimals: null, token_instance: null }, }; export const erc404A: TokenTransfer = { @@ -213,6 +217,7 @@ export const erc404A: TokenTransfer = { value: '42000000000000000000000000', decimals: '18', token_id: null, + token_instance: null, }, transaction_hash: '0x05d6589367633c032d757a69c5fb16c0e33e3994b0d9d1483f82aeee1f05d746', type: 'token_transfer', @@ -230,7 +235,7 @@ export const erc404B: TokenTransfer = { name: 'SastanaNFT', symbol: 'ipfs://QmUpFUfVKDCWeZQk5pvDFUxnpQP9N6eLSHhNUy49T1JVtY', }, - total: { token_id: '4625304364899952' }, + total: { token_id: '4625304364899952', token_instance: null }, }; export const mixTokens: TokenTransferResponse = { diff --git a/mocks/txs/state.ts b/mocks/txs/state.ts index d409f4679a..4c19a27d9d 100644 --- a/mocks/txs/state.ts +++ b/mocks/txs/state.ts @@ -19,6 +19,7 @@ export const mintToken: TxStateChange = { direction: 'from', total: { token_id: '15077554365819457090226168288698582604878106156134383525616269766016907608065', + token_instance: null, }, }, ], @@ -57,6 +58,7 @@ export const receiveMintedToken: TxStateChange = { direction: 'to', total: { token_id: '15077554365819457090226168288698582604878106156134383525616269766016907608065', + token_instance: null, }, }, ], diff --git a/mocks/txs/tx.ts b/mocks/txs/tx.ts index c0dd894f38..cb30924455 100644 --- a/mocks/txs/tx.ts +++ b/mocks/txs/tx.ts @@ -3,6 +3,7 @@ import type { Transaction } from 'types/api/transaction'; import * as addressMock from 'mocks/address/address'; import { publicTag, privateTag, watchlistName } from 'mocks/address/tag'; +import * as interopMock from 'mocks/interop/interop'; import * as tokenTransferMock from 'mocks/tokens/tokenTransfer'; import * as decodedInputDataMock from 'mocks/txs/decodedInputData'; @@ -423,3 +424,29 @@ export const withRecipientContract = { ...withRecipientEns, to: addressMock.contract, }; + +export const withInteropInMessage: Transaction = { + ...base, + op_interop: { + init_chain: interopMock.chain, + nonce: 1, + payload: '0x', + init_transaction_hash: '0x01a8c328b0370068aaaef49c107f70901cd79adcda81e3599a88855532122e09', + sender: addressMock.hash, + status: 'Sent', + target: addressMock.hash, + }, +}; + +export const withInteropOutMessage: Transaction = { + ...base, + op_interop: { + relay_chain: interopMock.chain, + nonce: 1, + payload: '0xfa4b78b90000000000000000000000000000000000000000000000000000000005001bcfe835d1028984e9e6e7d016b77164eacbcc6cc061e9333c0b37982b504f7ea791000000000000000000000000a79b29ad7e0196c95b87f4663ded82fbf2e3add8', + relay_transaction_hash: '0x01a8c328b0370068aaaef49c107f70901cd79adcda81e3599a88855532122e09', + sender: addressMock.hash, + status: 'Sent', + target: addressMock.hash, + }, +}; diff --git a/next-env.d.ts b/next-env.d.ts index a4a7b3f5cf..52e831b434 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -2,4 +2,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information. +// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information. diff --git a/nextjs/csp/policies/app.ts b/nextjs/csp/policies/app.ts index d1eda3a8ff..e445281572 100644 --- a/nextjs/csp/policies/app.ts +++ b/nextjs/csp/policies/app.ts @@ -71,8 +71,7 @@ export function app(): CspDev.DirectiveDescriptor { config.app.isDev ? KEY_WORDS.UNSAFE_EVAL : '', // hash of ColorModeScript: system + dark - '\'sha256-e7MRMmTzLsLQvIy1iizO1lXf7VWYoQ6ysj5fuUzvRwE=\'', - '\'sha256-9A7qFFHmxdWjZMQmfzYD2XWaNHLu1ZmQB0Ds4Go764k=\'', + '\'sha256-yYJq8IP5/WhJj6zxyTmujEqBFs/MufRufp2QKJFU76M=\'', // CapybaraRunner '\'sha256-5+YTmTcBwCYdJ8Jetbr6kyjGp0Ry/H7ptpoun6CrSwQ=\'', diff --git a/nextjs/getServerSideProps.ts b/nextjs/getServerSideProps.ts index aab480e91d..e9ea5faab5 100644 --- a/nextjs/getServerSideProps.ts +++ b/nextjs/getServerSideProps.ts @@ -46,7 +46,7 @@ Promise>> => { const isTrackingDisabled = process.env.DISABLE_TRACKING === 'true'; - if (!isTrackingDisabled && !config.app.isDev) { + if (!isTrackingDisabled) { // log pageview const hostname = req.headers.host; const timestamp = new Date().toISOString(); @@ -380,6 +380,17 @@ export const mud: GetServerSideProps = async(context) => { return base(context); }; +export const interopMessages: GetServerSideProps = async(context) => { + const rollupFeature = config.features.rollup; + if (!rollupFeature.isEnabled || !rollupFeature.interopEnabled) { + return { + notFound: true, + }; + } + + return base(context); +}; + export const pools: GetServerSideProps = async(context) => { if (!config.features.pools.isEnabled) { return { diff --git a/nextjs/nextjs-routes.d.ts b/nextjs/nextjs-routes.d.ts index c5d601ba9e..d5c74ed03b 100644 --- a/nextjs/nextjs-routes.d.ts +++ b/nextjs/nextjs-routes.d.ts @@ -26,7 +26,6 @@ declare module "nextjs-routes" { | StaticRoute<"/api/metrics"> | StaticRoute<"/api/monitoring/invalid-api-schema"> | StaticRoute<"/api/proxy"> - | StaticRoute<"/api/sprite"> | StaticRoute<"/api-docs"> | DynamicRoute<"/apps/[id]", { "id": string }> | StaticRoute<"/apps"> @@ -39,6 +38,7 @@ declare module "nextjs-routes" { | DynamicRoute<"/block/countdown/[height]", { "height": string }> | StaticRoute<"/block/countdown"> | StaticRoute<"/blocks"> + | StaticRoute<"/chakra"> | StaticRoute<"/contract-verification"> | StaticRoute<"/csv-export"> | StaticRoute<"/deposits"> @@ -47,6 +47,7 @@ declare module "nextjs-routes" { | StaticRoute<"/graphiql"> | StaticRoute<"/"> | StaticRoute<"/internal-txs"> + | StaticRoute<"/interop-messages"> | StaticRoute<"/login"> | StaticRoute<"/mud-worlds"> | DynamicRoute<"/name-domains/[name]", { "name": string }> diff --git a/package.json b/package.json index abf00bd5dc..84b769038b 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,8 @@ "start": "next start", "start:docker:local": "docker run -p 3000:3000 --env-file .env.local blockscout-frontend:local", "start:docker:preset": "./tools/scripts/docker.preset.sh", + "chakra:snippets:add": "chakra snippet add --outdir ./toolkit/chakra", + "chakra:typegen": "chakra typegen ./toolkit/theme/theme.ts", "lint:eslint": "eslint .", "lint:eslint:fix": "eslint . --fix", "lint:tsc": "tsc -p ./tsconfig.json", @@ -36,17 +38,16 @@ "og-image:generate:dev": "./tools/scripts/og-image-generator.dev.sh", "sitemap:generate:dev": "./tools/scripts/sitemap-generator.dev.sh", "monitoring:prometheus:local": "docker run --name blockscout_prometheus -d -p 127.0.0.1:9090:9090 -v $(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus", - "monitoring:grafana:local": "docker run -d -p 4000:3000 --name=blockscout_grafana --user $(id -u) --volume $(pwd)/grafana:/var/lib/grafana grafana/grafana-enterprise" + "monitoring:grafana:local": "docker run -d -p 4000:3000 --name=blockscout_grafana --user $(id -u) --volume $(pwd)/grafana:/var/lib/grafana grafana/grafana-enterprise", + "postinstall": "chakra typegen ./toolkit/theme/theme.ts" }, "dependencies": { "@blockscout/bens-types": "1.4.1", "@blockscout/stats-types": "2.5.0-alpha", "@blockscout/visualizer-types": "0.2.0", - "@chakra-ui/react": "2.7.1", - "@chakra-ui/theme-tools": "^2.0.18", + "@chakra-ui/react": "3.15.0", "@cloudnouns/kit": "1.1.6", - "@emotion/react": "^11.10.4", - "@emotion/styled": "^11.10.4", + "@emotion/react": "11.14.0", "@growthbook/growthbook-react": "0.21.0", "@helia/verified-fetch": "2.0.1", "@hypelab/sdk-react": "^1.0.0", @@ -54,6 +55,7 @@ "@metamask/providers": "^10.2.1", "@monaco-editor/react": "^4.4.6", "@next/bundle-analyzer": "15.0.3", + "@opentelemetry/api": "^1.4.1", "@opentelemetry/auto-instrumentations-node": "0.43.0", "@opentelemetry/exporter-jaeger": "1.27.0", "@opentelemetry/exporter-metrics-otlp-proto": "0.49.1", @@ -62,15 +64,16 @@ "@opentelemetry/sdk-node": "0.49.1", "@opentelemetry/sdk-trace-node": "1.22.0", "@opentelemetry/semantic-conventions": "1.22.0", - "@reown/appkit": "1.6.7", - "@reown/appkit-adapter-wagmi": "1.6.7", - "@rollbar/react": "0.12.0-beta", + "@reown/appkit": "1.7.0", + "@reown/appkit-adapter-wagmi": "1.7.0", + "@rollbar/react": "0.12.1", "@scure/base": "1.1.9", "@slise/embed-react": "^2.2.0", "@tanstack/react-query": "5.55.4", "@tanstack/react-query-devtools": "5.55.4", "@types/papaparse": "^5.3.5", "@types/react-scroll": "^1.8.4", + "@uidotdev/usehooks": "2.4.1", "airtable": "^0.12.2", "bignumber.js": "^9.1.0", "blo": "^1.1.1", @@ -82,7 +85,6 @@ "dom-to-image": "^2.6.0", "es-toolkit": "1.31.0", "focus-visible": "^5.2.0", - "framer-motion": "^6.5.1", "getit-sdk": "^1.0.4", "gradient-avatar": "git+https://github.com/blockscout/gradient-avatar.git", "graphiql": "^2.2.0", @@ -92,7 +94,8 @@ "magic-bytes.js": "1.8.0", "mixpanel-browser": "^2.47.0", "monaco-editor": "^0.34.1", - "next": "15.0.3", + "next": "15.2.3", + "next-themes": "0.4.4", "nextjs-routes": "^1.0.8", "node-fetch": "^3.2.9", "papaparse": "^5.3.2", @@ -107,20 +110,22 @@ "react-dom": "18.3.1", "react-google-recaptcha": "3.1.0", "react-hook-form": "7.52.1", + "react-icons": "5.4.0", "react-identicons": "^1.2.5", "react-intersection-observer": "^9.5.2", "react-jazzicon": "^1.0.4", "react-number-format": "^5.3.1", "react-scroll": "^1.8.7", "rollbar": "2.26.4", - "swagger-ui-react": "^5.9.0", + "swagger-ui-react": "5.20.3", "use-font-face-observer": "^1.2.1", "valibot": "0.38.0", - "viem": "2.21.54", - "wagmi": "2.14.0", + "viem": "2.23.14", + "wagmi": "2.14.15", "xss": "^1.0.14" }, "devDependencies": { + "@chakra-ui/cli": "3.15.0", "@eslint/compat": "1.2.2", "@eslint/js": "9.14.0", "@next/eslint-plugin-next": "15.0.3", @@ -135,7 +140,7 @@ "@types/csp-dev": "^1.0.0", "@types/d3": "^7.4.0", "@types/dom-to-image": "^2.6.4", - "@types/jest": "^29.2.0", + "@types/jest": "29.2.1", "@types/js-cookie": "^3.0.2", "@types/mixpanel-browser": "^2.38.1", "@types/node": "20.16.7", @@ -144,7 +149,7 @@ "@types/react": "18.3.12", "@types/react-dom": "18.3.1", "@types/react-google-recaptcha": "^2.1.5", - "@types/swagger-ui-react": "^4.11.0", + "@types/swagger-ui-react": "5.18.0", "@types/ws": "^8.5.3", "@typescript-eslint/eslint-plugin": "^5.60.0", "@vitejs/plugin-react": "^4.0.0", diff --git a/pages/404.tsx b/pages/404.tsx index bd4e020c0d..f19180f980 100644 --- a/pages/404.tsx +++ b/pages/404.tsx @@ -4,19 +4,12 @@ import type { NextPageWithLayout } from 'nextjs/types'; import PageNextJs from 'nextjs/PageNextJs'; -import { useRollbar } from 'lib/rollbar'; import AppError from 'ui/shared/AppError/AppError'; import LayoutError from 'ui/shared/layout/LayoutError'; const error = new Error('Not found', { cause: { status: 404 } }); const Page: NextPageWithLayout = () => { - const rollbar = useRollbar(); - - React.useEffect(() => { - rollbar?.error('Page not found'); - }, [ rollbar ]); - return ( diff --git a/pages/_app.tsx b/pages/_app.tsx index 01a8841550..8b52030b34 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -1,4 +1,4 @@ -import { type ChakraProps } from '@chakra-ui/react'; +import type { HTMLChakraProps } from '@chakra-ui/react'; import { GrowthBookProvider } from '@growthbook/growthbook-react'; import { QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; @@ -10,7 +10,6 @@ import type { NextPageWithLayout } from 'nextjs/types'; import config from 'configs/app'; import useQueryClientConfig from 'lib/api/useQueryClientConfig'; import { AppContextProvider } from 'lib/contexts/app'; -import { ChakraProvider } from 'lib/contexts/chakra'; import { MarketplaceContextProvider } from 'lib/contexts/marketplace'; import { RewardsContextProvider } from 'lib/contexts/rewards'; import { ScrollDirectionProvider } from 'lib/contexts/scrollDirection'; @@ -20,6 +19,8 @@ import useLoadFeatures from 'lib/growthbook/useLoadFeatures'; import useNotifyOnNavigation from 'lib/hooks/useNotifyOnNavigation'; import { clientConfig as rollbarConfig, Provider as RollbarProvider } from 'lib/rollbar'; import { SocketProvider } from 'lib/socket/context'; +import { Provider as ChakraProvider } from 'toolkit/chakra/provider'; +import { Toaster } from 'toolkit/chakra/toaster'; import RewardsLoginModal from 'ui/rewards/login/RewardsLoginModal'; import AppErrorBoundary from 'ui/shared/AppError/AppErrorBoundary'; import AppErrorGlobalContainer from 'ui/shared/AppError/AppErrorGlobalContainer'; @@ -34,7 +35,7 @@ type AppPropsWithLayout = AppProps & { Component: NextPageWithLayout; }; -const ERROR_SCREEN_STYLES: ChakraProps = { +const ERROR_SCREEN_STYLES: HTMLChakraProps<'div'> = { h: '100vh', display: 'flex', flexDirection: 'column', @@ -47,16 +48,29 @@ const ERROR_SCREEN_STYLES: ChakraProps = { }; function MyApp({ Component, pageProps }: AppPropsWithLayout) { + // to avoid hydration mismatch between server and client + // we have to render the app only on client (when it is mounted) + // https://github.com/pacocoursey/next-themes?tab=readme-ov-file#avoid-hydration-mismatch + const [ mounted, setMounted ] = React.useState(false); + + React.useEffect(() => { + setMounted(true); + }, []); + useLoadFeatures(pageProps.uuid); useNotifyOnNavigation(); const growthBook = initGrowthBook(pageProps.uuid); const queryClient = useQueryClientConfig(); + if (!mounted) { + return null; + } + const getLayout = Component.getLayout ?? ((page) => { page }); return ( - + { getLayout() } + { config.features.rewards.isEnabled && } diff --git a/pages/_document.tsx b/pages/_document.tsx index 1706e247ac..9032774cbe 100644 --- a/pages/_document.tsx +++ b/pages/_document.tsx @@ -1,4 +1,3 @@ -import { ColorModeScript } from '@chakra-ui/react'; import type { DocumentContext } from 'next/document'; import Document, { Html, Head, Main, NextScript } from 'next/document'; import React from 'react'; @@ -7,7 +6,6 @@ import logRequestFromBot from 'nextjs/utils/logRequestFromBot'; import * as serverTiming from 'nextjs/utils/serverTiming'; import config from 'configs/app'; -import theme from 'theme/theme'; import * as svgSprite from 'ui/shared/IconSvg'; class MyDocument extends Document { @@ -57,7 +55,6 @@ class MyDocument extends Document { -
diff --git a/pages/api/sprite.ts b/pages/api/sprite.ts deleted file mode 100644 index 9ba9d7030c..0000000000 --- a/pages/api/sprite.ts +++ /dev/null @@ -1,49 +0,0 @@ -import fs from 'fs'; -import type { NextApiRequest, NextApiResponse } from 'next'; -import path from 'path'; - -import config from 'configs/app'; - -const ROOT_DIR = './icons'; -const NAME_PREFIX = ROOT_DIR.replace('./', '') + '/'; - -interface IconInfo { - name: string; - fileSize: number; -} - -const getIconName = (filePath: string) => filePath.replace(NAME_PREFIX, '').replace('.svg', ''); - -function collectIconNames(dir: string) { - const files = fs.readdirSync(dir, { withFileTypes: true }); - let icons: Array = []; - - files.forEach((file) => { - const filePath = path.join(dir, file.name); - const stats = fs.statSync(filePath); - - file.name.endsWith('.svg') && icons.push({ - name: getIconName(filePath), - fileSize: stats.size, - }); - - if (file.isDirectory()) { - icons = [ ...icons, ...collectIconNames(filePath) ]; - } - }); - - return icons; -} - -export default async function spriteHandler(req: NextApiRequest, res: NextApiResponse) { - - if (!config.app.isDev) { - return res.status(404).json({ error: 'Not found' }); - } - - const icons = collectIconNames(ROOT_DIR); - - res.status(200).json({ - icons, - }); -} diff --git a/pages/chakra.tsx b/pages/chakra.tsx new file mode 100644 index 0000000000..87c61a1f74 --- /dev/null +++ b/pages/chakra.tsx @@ -0,0 +1,18 @@ +import type { NextPage } from 'next'; +import React from 'react'; + +import PageNextJs from 'nextjs/PageNextJs'; + +import Chakra from 'ui/pages/Chakra'; + +const Page: NextPage = () => { + return ( + + + + ); +}; + +export default Page; + +export { base as getServerSideProps } from 'nextjs/getServerSideProps'; diff --git a/pages/interop-messages.tsx b/pages/interop-messages.tsx new file mode 100644 index 0000000000..4570647127 --- /dev/null +++ b/pages/interop-messages.tsx @@ -0,0 +1,19 @@ +import type { NextPage } from 'next'; +import dynamic from 'next/dynamic'; +import React from 'react'; + +import PageNextJs from 'nextjs/PageNextJs'; + +const InteropMessages = dynamic(() => import('ui/pages/InteropMessages'), { ssr: false }); + +const Page: NextPage = () => { + return ( + + + + ); +}; + +export default Page; + +export { interopMessages as getServerSideProps } from 'nextjs/getServerSideProps'; diff --git a/pages/stats.tsx b/pages/stats/index.tsx similarity index 100% rename from pages/stats.tsx rename to pages/stats/index.tsx diff --git a/playwright/TestApp.tsx b/playwright/TestApp.tsx index 5c09d7b166..7eaedeb6b2 100644 --- a/playwright/TestApp.tsx +++ b/playwright/TestApp.tsx @@ -1,4 +1,3 @@ -import { ChakraProvider } from '@chakra-ui/react'; import { GrowthBookProvider } from '@growthbook/growthbook-react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import React from 'react'; @@ -15,7 +14,7 @@ import { RewardsContextProvider } from 'lib/contexts/rewards'; import { SettingsContextProvider } from 'lib/contexts/settings'; import { SocketProvider } from 'lib/socket/context'; import { currentChain } from 'lib/web3/chains'; -import theme from 'theme/theme'; +import { Provider as ChakraProvider } from 'toolkit/chakra/provider'; import { port as socketPort } from './utils/socket'; @@ -73,7 +72,7 @@ const TestApp = ({ children, withSocket, appContext = defaultAppContext, marketp })); return ( - + diff --git a/playwright/fixtures/mockEnvs.ts b/playwright/fixtures/mockEnvs.ts index a723b9c62d..6033c83f77 100644 --- a/playwright/fixtures/mockEnvs.ts +++ b/playwright/fixtures/mockEnvs.ts @@ -97,4 +97,10 @@ export const ENVS_MAP: Record> = { externalTxs: [ [ 'NEXT_PUBLIC_TX_EXTERNAL_TRANSACTIONS_CONFIG', '{"chain_name": "Solana", "chain_logo_url": "http://example.url", "explorer_url_template": "https://scan.io/tx/{hash}"}' ], ], + interop: [ + [ 'NEXT_PUBLIC_ROLLUP_TYPE', 'optimistic' ], + [ 'NEXT_PUBLIC_ROLLUP_L1_BASE_URL', 'https://localhost:3101' ], + [ 'NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL', 'https://localhost:3102' ], + [ 'NEXT_PUBLIC_INTEROP_ENABLED', 'true' ], + ], }; diff --git a/public/icons/name.d.ts b/public/icons/name.d.ts index f10b3053c0..0d29fd5579 100644 --- a/public/icons/name.d.ts +++ b/public/icons/name.d.ts @@ -35,6 +35,7 @@ | "checkered_flag" | "clock-light" | "clock" + | "close" | "coins/bitcoin" | "collection" | "columns" @@ -80,10 +81,12 @@ | "heart_filled" | "heart_outline" | "hourglass" + | "info_filled" | "info" | "integration/full" | "integration/partial" | "internal_txns" + | "interop" | "key" | "lightning_navbar" | "lightning" diff --git a/public/static/badges.svg b/public/static/merits/badges.svg similarity index 100% rename from public/static/badges.svg rename to public/static/merits/badges.svg diff --git a/public/static/merits/campaigns.svg b/public/static/merits/campaigns.svg new file mode 100644 index 0000000000..efbd2064c4 --- /dev/null +++ b/public/static/merits/campaigns.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/public/static/merits_program.png b/public/static/merits/merits_program.png similarity index 100% rename from public/static/merits_program.png rename to public/static/merits/merits_program.png diff --git a/public/static/merits/offers.svg b/public/static/merits/offers.svg new file mode 100644 index 0000000000..7f7f218458 --- /dev/null +++ b/public/static/merits/offers.svg @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/stubs/interop.ts b/stubs/interop.ts new file mode 100644 index 0000000000..a5dfb525aa --- /dev/null +++ b/stubs/interop.ts @@ -0,0 +1,21 @@ +import type { InteropMessage } from 'types/api/interop'; + +import { ADDRESS_HASH } from './addressParams'; +import { TX_HASH } from './tx'; + +export const INTEROP_MESSAGE: InteropMessage = { + init_transaction_hash: TX_HASH, + nonce: 52, + payload: '0x4f0edcc90000000000000000000000007da521cbbe62e89cd75e0993c78b8c68c25f696b', + relay_chain: { + chain_id: 420120000, + chain_name: 'Optimism Testnet', + chain_logo: null, + instance_url: 'https://optimism-interop-alpha-0.blockscout.com/', + }, + relay_transaction_hash: TX_HASH, + sender: ADDRESS_HASH, + status: 'Relayed', + target: ADDRESS_HASH, + timestamp: '2025-02-20T01:05:14.000000Z', +}; diff --git a/stubs/token.ts b/stubs/token.ts index c301ff7cd2..d371628505 100644 --- a/stubs/token.ts +++ b/stubs/token.ts @@ -110,6 +110,7 @@ export const TOKEN_TRANSFER_ERC_721: TokenTransfer = { ...TOKEN_TRANSFER_ERC_20, total: { token_id: '35870', + token_instance: null, }, token: TOKEN_INFO_ERC_721, }; @@ -120,6 +121,7 @@ export const TOKEN_TRANSFER_ERC_1155: TokenTransfer = { token_id: '35870', value: '123', decimals: '18', + token_instance: null, }, token: TOKEN_INFO_ERC_1155, }; @@ -130,6 +132,7 @@ export const TOKEN_TRANSFER_ERC_404: TokenTransfer = { token_id: '35870', value: '123', decimals: '18', + token_instance: null, }, token: TOKEN_INFO_ERC_404, }; diff --git a/stubs/txStateChanges.ts b/stubs/txStateChanges.ts index 7a26ee8a77..f843c8c3ae 100644 --- a/stubs/txStateChanges.ts +++ b/stubs/txStateChanges.ts @@ -32,6 +32,7 @@ export const STATE_CHANGE_TOKEN: TxStateChange = { direction: 'to', total: { token_id: '1621395', + token_instance: null, }, }, ], diff --git a/theme/.gitignore b/theme/.gitignore deleted file mode 100644 index b2d59d1f75..0000000000 --- a/theme/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/node_modules -/dist \ No newline at end of file diff --git a/theme/components/Alert/Alert.pw.tsx b/theme/components/Alert/Alert.pw.tsx deleted file mode 100644 index 144a4540e8..0000000000 --- a/theme/components/Alert/Alert.pw.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import type { AlertProps } from '@chakra-ui/react'; -import { Alert, AlertIcon, AlertTitle } from '@chakra-ui/react'; -import React from 'react'; - -import { test, expect } from 'playwright/lib'; - -test.use({ viewport: { width: 400, height: 720 } }); - -const TEST_CASES: Array = [ - { - status: 'info', - }, - { - status: 'warning', - }, - { - status: 'success', - }, - { - status: 'error', - }, - { - status: 'info', - colorScheme: 'gray', - }, -]; - -TEST_CASES.forEach((props) => { - const testName = Object.entries(props).map(([ key, value ]) => `${ key }=${ value }`).join(', '); - - test(`${ testName } +@dark-mode`, async({ render }) => { - const component = await render( - - - - This is alert text - - , - ); - - await expect(component).toHaveScreenshot(); - }); -}); diff --git a/theme/components/Alert/Alert.ts b/theme/components/Alert/Alert.ts deleted file mode 100644 index acce04ee0e..0000000000 --- a/theme/components/Alert/Alert.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { alertAnatomy as parts } from '@chakra-ui/anatomy'; -import type { StyleFunctionProps } from '@chakra-ui/styled-system'; -import { createMultiStyleConfigHelpers, cssVar } from '@chakra-ui/styled-system'; -import { transparentize } from '@chakra-ui/theme-tools'; - -const { definePartsStyle, defineMultiStyleConfig } = - createMultiStyleConfigHelpers(parts.keys); - -const $fg = cssVar('alert-fg'); -const $bg = cssVar('alert-bg'); - -function getBg(props: StyleFunctionProps) { - const { theme, colorScheme: c } = props; - const darkBg = transparentize(`${ c }.200`, 0.16)(theme); - return { - light: `colors.${ c }.100`, - dark: darkBg, - }; -} - -const baseStyle = definePartsStyle({ - container: { - bg: $bg.reference, - borderRadius: 'md', - px: 6, - py: 3, - }, - title: { - fontWeight: 'bold', - lineHeight: '6', - marginEnd: '2', - }, - description: { - lineHeight: '6', - }, - icon: { - color: $fg.reference, - flexShrink: 0, - marginEnd: '3', - w: '5', - h: '6', - }, - spinner: { - color: $fg.reference, - flexShrink: 0, - marginEnd: '3', - w: '5', - h: '5', - }, -}); - -const variantSubtle = definePartsStyle((props) => { - const { colorScheme } = props; - const bg = getBg(props); - - return { - container: { - [$fg.variable]: colorScheme === 'gray' ? 'colors.blackAlpha.800' : `colors.${ colorScheme }.500`, - [$bg.variable]: colorScheme === 'gray' ? 'colors.blackAlpha.100' : bg.light, - _dark: { - [$fg.variable]: colorScheme === 'gray' ? 'colors.whiteAlpha.800' : `colors.${ colorScheme }.200`, - [$bg.variable]: colorScheme === 'gray' ? 'colors.whiteAlpha.200' : bg.dark, - }, - }, - }; -}); - -const variantSolid = definePartsStyle((props) => { - const { colorScheme: c } = props; - return { - container: { - [$fg.variable]: `colors.white`, - [$bg.variable]: `colors.${ c }.500`, - _dark: { - [$fg.variable]: `colors.gray.900`, - [$bg.variable]: `colors.${ c }.200`, - }, - color: $fg.reference, - }, - }; -}); - -const variants = { - subtle: variantSubtle, - solid: variantSolid, -}; - -const Alert = defineMultiStyleConfig({ - baseStyle, - variants, - defaultProps: { - variant: 'subtle', - colorScheme: 'blue', - }, -}); - -export default Alert; diff --git a/theme/components/Alert/__screenshots__/Alert.pw.tsx_dark-color-mode_status-error-dark-mode-1.png b/theme/components/Alert/__screenshots__/Alert.pw.tsx_dark-color-mode_status-error-dark-mode-1.png deleted file mode 100644 index d1f0f8e805..0000000000 Binary files a/theme/components/Alert/__screenshots__/Alert.pw.tsx_dark-color-mode_status-error-dark-mode-1.png and /dev/null differ diff --git a/theme/components/Alert/__screenshots__/Alert.pw.tsx_dark-color-mode_status-info-colorScheme-gray-dark-mode-1.png b/theme/components/Alert/__screenshots__/Alert.pw.tsx_dark-color-mode_status-info-colorScheme-gray-dark-mode-1.png deleted file mode 100644 index 259f974e96..0000000000 Binary files a/theme/components/Alert/__screenshots__/Alert.pw.tsx_dark-color-mode_status-info-colorScheme-gray-dark-mode-1.png and /dev/null differ diff --git a/theme/components/Alert/__screenshots__/Alert.pw.tsx_dark-color-mode_status-info-dark-mode-1.png b/theme/components/Alert/__screenshots__/Alert.pw.tsx_dark-color-mode_status-info-dark-mode-1.png deleted file mode 100644 index 34f5fbbf66..0000000000 Binary files a/theme/components/Alert/__screenshots__/Alert.pw.tsx_dark-color-mode_status-info-dark-mode-1.png and /dev/null differ diff --git a/theme/components/Alert/__screenshots__/Alert.pw.tsx_dark-color-mode_status-success-dark-mode-1.png b/theme/components/Alert/__screenshots__/Alert.pw.tsx_dark-color-mode_status-success-dark-mode-1.png deleted file mode 100644 index 12ca38da9c..0000000000 Binary files a/theme/components/Alert/__screenshots__/Alert.pw.tsx_dark-color-mode_status-success-dark-mode-1.png and /dev/null differ diff --git a/theme/components/Alert/__screenshots__/Alert.pw.tsx_dark-color-mode_status-warning-dark-mode-1.png b/theme/components/Alert/__screenshots__/Alert.pw.tsx_dark-color-mode_status-warning-dark-mode-1.png deleted file mode 100644 index 7b73b0cac1..0000000000 Binary files a/theme/components/Alert/__screenshots__/Alert.pw.tsx_dark-color-mode_status-warning-dark-mode-1.png and /dev/null differ diff --git a/theme/components/Alert/__screenshots__/Alert.pw.tsx_default_status-error-dark-mode-1.png b/theme/components/Alert/__screenshots__/Alert.pw.tsx_default_status-error-dark-mode-1.png deleted file mode 100644 index 03427b8929..0000000000 Binary files a/theme/components/Alert/__screenshots__/Alert.pw.tsx_default_status-error-dark-mode-1.png and /dev/null differ diff --git a/theme/components/Alert/__screenshots__/Alert.pw.tsx_default_status-info-colorScheme-gray-dark-mode-1.png b/theme/components/Alert/__screenshots__/Alert.pw.tsx_default_status-info-colorScheme-gray-dark-mode-1.png deleted file mode 100644 index 2f5c4ef2a6..0000000000 Binary files a/theme/components/Alert/__screenshots__/Alert.pw.tsx_default_status-info-colorScheme-gray-dark-mode-1.png and /dev/null differ diff --git a/theme/components/Alert/__screenshots__/Alert.pw.tsx_default_status-info-dark-mode-1.png b/theme/components/Alert/__screenshots__/Alert.pw.tsx_default_status-info-dark-mode-1.png deleted file mode 100644 index 7bb647aba2..0000000000 Binary files a/theme/components/Alert/__screenshots__/Alert.pw.tsx_default_status-info-dark-mode-1.png and /dev/null differ diff --git a/theme/components/Alert/__screenshots__/Alert.pw.tsx_default_status-success-dark-mode-1.png b/theme/components/Alert/__screenshots__/Alert.pw.tsx_default_status-success-dark-mode-1.png deleted file mode 100644 index 5dd37bf3b3..0000000000 Binary files a/theme/components/Alert/__screenshots__/Alert.pw.tsx_default_status-success-dark-mode-1.png and /dev/null differ diff --git a/theme/components/Alert/__screenshots__/Alert.pw.tsx_default_status-warning-dark-mode-1.png b/theme/components/Alert/__screenshots__/Alert.pw.tsx_default_status-warning-dark-mode-1.png deleted file mode 100644 index 75d62d855b..0000000000 Binary files a/theme/components/Alert/__screenshots__/Alert.pw.tsx_default_status-warning-dark-mode-1.png and /dev/null differ diff --git a/theme/components/Badge.ts b/theme/components/Badge.ts deleted file mode 100644 index ab0b13a491..0000000000 --- a/theme/components/Badge.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { defineStyle, defineStyleConfig } from '@chakra-ui/styled-system'; -import { mode } from '@chakra-ui/theme-tools'; - -const baseStyle = defineStyle({ - fontSize: 'xs', - borderRadius: 'sm', - fontWeight: 'bold', -}); - -const variantSubtle = defineStyle((props) => { - const { colorScheme: c } = props; - - if (c === 'gray') { - return { - bg: mode('blackAlpha.50', 'whiteAlpha.100')(props), - color: mode('blackAlpha.800', 'whiteAlpha.800')(props), - }; - } - - if (c === 'gray-blue') { - return { - bg: mode('gray.100', 'gray.800')(props), - color: mode('blackAlpha.800', 'whiteAlpha.800')(props), - _hover: { - opacity: 0.76, - }, - }; - } - - if (c === 'black-blue') { - return { - bg: mode('blue.50', 'blue.800')(props), - color: mode('blackAlpha.800', 'whiteAlpha.800')(props), - }; - } - - if (c === 'black-purple') { - return { - bg: mode('purple.100', 'purple.800')(props), - color: mode('blackAlpha.800', 'whiteAlpha.800')(props), - }; - } - - return { - bg: mode(`${ c }.50`, `${ c }.800`)(props), - color: mode(`${ c }.500`, `${ c }.100`)(props), - }; -}); - -const variants = { - subtle: variantSubtle, -}; - -const Badge = defineStyleConfig({ - baseStyle, - variants, - defaultProps: { - variant: 'subtle', - colorScheme: 'gray', - }, -}); - -export default Badge; diff --git a/theme/components/Button/Button.pw.tsx b/theme/components/Button/Button.pw.tsx deleted file mode 100644 index 4435715b88..0000000000 --- a/theme/components/Button/Button.pw.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { Box, Button, Flex } from '@chakra-ui/react'; -import React from 'react'; - -import { test, expect } from 'playwright/lib'; - -test.use({ viewport: { width: 150, height: 350 } }); - -[ - { variant: 'solid', states: [ 'default', 'disabled', 'hovered', 'active' ] }, - { variant: 'outline', colorScheme: 'gray', withDarkMode: true, states: [ 'default', 'disabled', 'hovered', 'active', 'selected' ] }, - { variant: 'outline', colorScheme: 'blue', withDarkMode: true, states: [ 'default', 'disabled', 'hovered', 'active', 'selected' ] }, - { variant: 'simple', withDarkMode: true, states: [ 'default', 'hovered' ] }, - { variant: 'ghost', withDarkMode: true, states: [ 'default', 'hovered', 'active' ] }, - { variant: 'subtle', states: [ 'default', 'hovered' ] }, - { variant: 'subtle', colorScheme: 'gray', states: [ 'default', 'hovered' ], withDarkMode: true }, - { variant: 'hero', states: [ 'default', 'hovered' ], withDarkMode: true }, - { variant: 'header', states: [ 'default', 'hovered', 'selected' ], withDarkMode: true }, - { variant: 'radio_group', states: [ 'default', 'hovered', 'selected' ], withDarkMode: true }, -].forEach(({ variant, colorScheme, withDarkMode, states }) => { - test.describe(`variant ${ variant }${ colorScheme ? ` with ${ colorScheme } color scheme` : '' }${ withDarkMode ? ' +@dark-mode' : '' }`, () => { - test('base view', async({ render }) => { - const component = await render( - - { states?.map((state) => { - switch (state) { - case 'default': { - return ( - - Default: - - - ); - } - case 'disabled': { - return ( - - Disabled: - - - ); - } - case 'active': { - return ( - - Active: - - - ); - } - case 'hovered': { - return ( - - Hovered: - - - ); - } - case 'selected': { - return ( - - Selected: - - - ); - } - - default: { - return null; - } - } - }) } - , - ); - await component.getByText('Hover me').hover(); - await expect(component).toHaveScreenshot(); - }); - }); -}); diff --git a/theme/components/Button/Button.ts b/theme/components/Button/Button.ts deleted file mode 100644 index 7f6ea7605f..0000000000 --- a/theme/components/Button/Button.ts +++ /dev/null @@ -1,315 +0,0 @@ -import { defineStyle, defineStyleConfig } from '@chakra-ui/styled-system'; -import { mode } from '@chakra-ui/theme-tools'; -import { runIfFn } from '@chakra-ui/utils'; - -import config from 'configs/app'; - -const variantSolid = defineStyle((props) => { - const { colorScheme: c } = props; - - const bg = `${ c }.600`; - const color = 'white'; - const hoverBg = `${ c }.400`; - const activeBg = hoverBg; - - return { - bg, - color, - _hover: { - bg: hoverBg, - _disabled: { - bg, - }, - }, - _disabled: { - opacity: 0.2, - }, - // According to design there is no "active" or "pressed" state - // It is simply should be the same as the "hover" state - _active: { bg: activeBg }, - fontWeight: 600, - }; -}); - -const variantOutline = defineStyle((props) => { - const { colorScheme: c } = props; - - const isGrayTheme = c === 'gray'; - - const bg = 'transparent'; - - const color = isGrayTheme ? mode('blackAlpha.800', 'whiteAlpha.800')(props) : mode(`${ c }.600`, `${ c }.300`)(props); - const borderColor = isGrayTheme ? mode('gray.200', 'gray.600')(props) : mode(`${ c }.600`, `${ c }.300`)(props); - - const selectedBg = isGrayTheme ? mode('blue.50', 'gray.600')(props) : mode(`${ c }.50`, 'gray.600')(props); - const selectedColor = mode('blue.600', 'gray.50')(props); - - return { - color, - fontWeight: props.fontWeight || 600, - borderWidth: props.borderWidth || '2px', - borderStyle: 'solid', - borderColor, - bg, - _hover: { - color: 'link_hovered', - borderColor: 'link_hovered', - bg, - span: { - color: 'link_hovered', - }, - _disabled: { - color, - borderColor, - }, - }, - _disabled: { - opacity: 0.2, - }, - // According to design there is no "active" or "pressed" state - // It is simply should be the same as the "hover" state - _active: { - color: 'link_hovered', - borderColor: 'link_hovered', - bg, - span: { - color: 'link_hovered', - }, - _disabled: { - color: 'link_hovered', - borderColor: 'link_hovered', - }, - }, - // We have a special state for this button variant that serves as a popover trigger. - // When any items (filters) are selected in the popover, the button should change its background and text color. - // The last CSS selector is for redefining styles for the TabList component. - [` - &[data-selected=true], - &[data-selected=true][aria-selected=true] - `]: { - bg: selectedBg, - color: selectedColor, - borderColor: selectedBg, - }, - }; -}); - -const variantRadioGroup = defineStyle((props) => { - const outline = runIfFn(variantOutline, props); - const bgColor = mode('blue.50', 'gray.800')(props); - const selectedTextColor = mode('blue.700', 'gray.50')(props); - - return { - ...outline, - fontWeight: 500, - cursor: 'pointer', - bgColor: 'none', - borderColor: bgColor, - _hover: { - borderColor: bgColor, - color: 'link_hovered', - }, - _active: { - bgColor: 'none', - }, - _notFirst: { - borderLeftWidth: 0, - }, - // We have a special state for this button variant that serves as a popover trigger. - // When any items (filters) are selected in the popover, the button should change its background and text color. - // The last CSS selector is for redefining styles for the TabList component. - [` - &[data-selected=true], - &[data-selected=true][aria-selected=true] - `]: { - cursor: 'initial', - bgColor, - borderColor: bgColor, - color: selectedTextColor, - _hover: { - color: selectedTextColor, - }, - }, - }; -}); - -const variantSimple = defineStyle((props) => { - const outline = runIfFn(variantOutline, props); - - return { - color: outline.color, - _hover: { - color: outline._hover.color, - }, - }; -}); - -const variantGhost = defineStyle((props) => { - const { colorScheme: c } = props; - const activeBg = mode(`${ c }.50`, 'gray.800')(props); - - return { - bg: 'transparent', - color: mode(`${ c }.700`, 'gray.400')(props), - _active: { - color: mode(`${ c }.700`, 'gray.50')(props), - bg: mode(`${ c }.50`, 'gray.800')(props), - }, - _hover: { - color: `${ c }.400`, - _active: { - bg: props.isActive ? activeBg : 'transparent', - color: mode(`${ c }.700`, 'gray.50')(props), - }, - }, - }; -}); - -const variantSubtle = defineStyle((props) => { - const { colorScheme: c } = props; - - if (c === 'gray') { - return { - bg: mode('blackAlpha.200', 'whiteAlpha.200')(props), - color: mode('blackAlpha.800', 'whiteAlpha.800')(props), - _hover: { - color: 'link_hovered', - _disabled: { - color: mode('blackAlpha.800', 'whiteAlpha.800')(props), - bg: mode('blackAlpha.200', 'whiteAlpha.200')(props), - }, - }, - }; - } - - return { - bg: `${ c }.100`, - color: `${ c }.600`, - _hover: { - color: 'link_hovered', - }, - }; -}); - -// for buttons in the hero banner -const variantHero = defineStyle((props) => { - const buttonConfig = config.UI.homepage.heroBanner?.button; - return { - bg: mode( - buttonConfig?._default?.background?.[0] || 'blue.600', - buttonConfig?._default?.background?.[1] || buttonConfig?._default?.background?.[0] || 'blue.600', - )(props), - color: mode( - buttonConfig?._default?.text_color?.[0] || 'white', - buttonConfig?._default?.text_color?.[1] || buttonConfig?._default?.text_color?.[0] || 'white', - )(props), - _hover: { - bg: mode( - buttonConfig?._hover?.background?.[0] || 'blue.400', - buttonConfig?._hover?.background?.[1] || buttonConfig?._hover?.background?.[0] || 'blue.400', - )(props), - color: mode( - buttonConfig?._hover?.text_color?.[0] || 'white', - buttonConfig?._hover?.text_color?.[1] || buttonConfig?._hover?.text_color?.[0] || 'white', - )(props), - }, - '&[data-selected=true]': { - bg: mode( - buttonConfig?._selected?.background?.[0] || 'blue.50', - buttonConfig?._selected?.background?.[1] || buttonConfig?._selected?.background?.[0] || 'blue.50', - )(props), - color: mode( - buttonConfig?._selected?.text_color?.[0] || 'blackAlpha.800', - buttonConfig?._selected?.text_color?.[1] || buttonConfig?._selected?.text_color?.[0] || 'blackAlpha.800', - )(props), - }, - }; -}); - -// for buttons in the page header -const variantHeader = defineStyle((props) => { - - return { - bgColor: 'transparent', - color: mode('blackAlpha.800', 'gray.400')(props), - borderColor: mode('gray.300', 'gray.600')(props), - borderWidth: props.borderWidth || '2px', - borderStyle: 'solid', - _hover: { - color: 'link_hovered', - borderColor: 'link_hovered', - }, - '&[data-selected=true]': { - bgColor: mode('blackAlpha.50', 'whiteAlpha.100')(props), - color: mode('blackAlpha.800', 'whiteAlpha.800')(props), - borderColor: 'transparent', - borderWidth: props.borderWidth || '0px', - }, - '&[data-selected=true][data-warning=true]': { - bgColor: mode('orange.100', 'orange.900')(props), - color: mode('blackAlpha.800', 'whiteAlpha.800')(props), - borderColor: 'transparent', - borderWidth: props.borderWidth || '0px', - }, - }; -}); - -const variants = { - solid: variantSolid, - outline: variantOutline, - simple: variantSimple, - ghost: variantGhost, - subtle: variantSubtle, - hero: variantHero, - header: variantHeader, - radio_group: variantRadioGroup, -}; - -const baseStyle = defineStyle({ - fontWeight: 600, - borderRadius: 'base', - overflow: 'hidden', - _focusVisible: { - boxShadow: { base: 'none', lg: 'outline' }, - }, -}); - -const sizes = { - lg: defineStyle({ - h: 12, - minW: 'unset', - fontSize: 'lg', - px: 6, - }), - md: defineStyle({ - h: 10, - minW: 'unset', - fontSize: 'md', - px: 4, - }), - sm: defineStyle({ - h: 8, - minW: 'unset', - fontSize: 'sm', - px: 3, - }), - xs: defineStyle({ - h: 6, - minW: 'unset', - fontSize: 'xs', - px: 2, - }), -}; - -const Button = defineStyleConfig({ - baseStyle, - variants, - sizes, - defaultProps: { - variant: 'solid', - size: 'md', - colorScheme: 'blue', - }, -}); - -export default Button; diff --git a/theme/components/Button/__screenshots__/Button.pw.tsx_dark-color-mode_variant-ghost-dark-mode-base-view-1.png b/theme/components/Button/__screenshots__/Button.pw.tsx_dark-color-mode_variant-ghost-dark-mode-base-view-1.png deleted file mode 100644 index 37ff0091e6..0000000000 Binary files a/theme/components/Button/__screenshots__/Button.pw.tsx_dark-color-mode_variant-ghost-dark-mode-base-view-1.png and /dev/null differ diff --git a/theme/components/Button/__screenshots__/Button.pw.tsx_dark-color-mode_variant-header-dark-mode-base-view-1.png b/theme/components/Button/__screenshots__/Button.pw.tsx_dark-color-mode_variant-header-dark-mode-base-view-1.png deleted file mode 100644 index e622a8b4eb..0000000000 Binary files a/theme/components/Button/__screenshots__/Button.pw.tsx_dark-color-mode_variant-header-dark-mode-base-view-1.png and /dev/null differ diff --git a/theme/components/Button/__screenshots__/Button.pw.tsx_dark-color-mode_variant-hero-dark-mode-base-view-1.png b/theme/components/Button/__screenshots__/Button.pw.tsx_dark-color-mode_variant-hero-dark-mode-base-view-1.png deleted file mode 100644 index ddc8f0ef3c..0000000000 Binary files a/theme/components/Button/__screenshots__/Button.pw.tsx_dark-color-mode_variant-hero-dark-mode-base-view-1.png and /dev/null differ diff --git a/theme/components/Button/__screenshots__/Button.pw.tsx_dark-color-mode_variant-outline-with-blue-color-scheme-dark-mode-base-view-1.png b/theme/components/Button/__screenshots__/Button.pw.tsx_dark-color-mode_variant-outline-with-blue-color-scheme-dark-mode-base-view-1.png deleted file mode 100644 index b079eac502..0000000000 Binary files a/theme/components/Button/__screenshots__/Button.pw.tsx_dark-color-mode_variant-outline-with-blue-color-scheme-dark-mode-base-view-1.png and /dev/null differ diff --git a/theme/components/Button/__screenshots__/Button.pw.tsx_dark-color-mode_variant-outline-with-gray-color-scheme-dark-mode-base-view-1.png b/theme/components/Button/__screenshots__/Button.pw.tsx_dark-color-mode_variant-outline-with-gray-color-scheme-dark-mode-base-view-1.png deleted file mode 100644 index 9262b0d04a..0000000000 Binary files a/theme/components/Button/__screenshots__/Button.pw.tsx_dark-color-mode_variant-outline-with-gray-color-scheme-dark-mode-base-view-1.png and /dev/null differ diff --git a/theme/components/Button/__screenshots__/Button.pw.tsx_dark-color-mode_variant-radio-group-dark-mode-base-view-1.png b/theme/components/Button/__screenshots__/Button.pw.tsx_dark-color-mode_variant-radio-group-dark-mode-base-view-1.png deleted file mode 100644 index cf4a712198..0000000000 Binary files a/theme/components/Button/__screenshots__/Button.pw.tsx_dark-color-mode_variant-radio-group-dark-mode-base-view-1.png and /dev/null differ diff --git a/theme/components/Button/__screenshots__/Button.pw.tsx_dark-color-mode_variant-simple-dark-mode-base-view-1.png b/theme/components/Button/__screenshots__/Button.pw.tsx_dark-color-mode_variant-simple-dark-mode-base-view-1.png deleted file mode 100644 index 82c8ff359e..0000000000 Binary files a/theme/components/Button/__screenshots__/Button.pw.tsx_dark-color-mode_variant-simple-dark-mode-base-view-1.png and /dev/null differ diff --git a/theme/components/Button/__screenshots__/Button.pw.tsx_dark-color-mode_variant-subtle-with-gray-color-scheme-dark-mode-base-view-1.png b/theme/components/Button/__screenshots__/Button.pw.tsx_dark-color-mode_variant-subtle-with-gray-color-scheme-dark-mode-base-view-1.png deleted file mode 100644 index aedec3455a..0000000000 Binary files a/theme/components/Button/__screenshots__/Button.pw.tsx_dark-color-mode_variant-subtle-with-gray-color-scheme-dark-mode-base-view-1.png and /dev/null differ diff --git a/theme/components/Button/__screenshots__/Button.pw.tsx_default_variant-ghost-dark-mode-base-view-1.png b/theme/components/Button/__screenshots__/Button.pw.tsx_default_variant-ghost-dark-mode-base-view-1.png deleted file mode 100644 index c1f3c959cf..0000000000 Binary files a/theme/components/Button/__screenshots__/Button.pw.tsx_default_variant-ghost-dark-mode-base-view-1.png and /dev/null differ diff --git a/theme/components/Button/__screenshots__/Button.pw.tsx_default_variant-header-dark-mode-base-view-1.png b/theme/components/Button/__screenshots__/Button.pw.tsx_default_variant-header-dark-mode-base-view-1.png deleted file mode 100644 index 026d216e0a..0000000000 Binary files a/theme/components/Button/__screenshots__/Button.pw.tsx_default_variant-header-dark-mode-base-view-1.png and /dev/null differ diff --git a/theme/components/Button/__screenshots__/Button.pw.tsx_default_variant-hero-dark-mode-base-view-1.png b/theme/components/Button/__screenshots__/Button.pw.tsx_default_variant-hero-dark-mode-base-view-1.png deleted file mode 100644 index 5df1869f44..0000000000 Binary files a/theme/components/Button/__screenshots__/Button.pw.tsx_default_variant-hero-dark-mode-base-view-1.png and /dev/null differ diff --git a/theme/components/Button/__screenshots__/Button.pw.tsx_default_variant-outline-with-blue-color-scheme-dark-mode-base-view-1.png b/theme/components/Button/__screenshots__/Button.pw.tsx_default_variant-outline-with-blue-color-scheme-dark-mode-base-view-1.png deleted file mode 100644 index a3a33865d4..0000000000 Binary files a/theme/components/Button/__screenshots__/Button.pw.tsx_default_variant-outline-with-blue-color-scheme-dark-mode-base-view-1.png and /dev/null differ diff --git a/theme/components/Button/__screenshots__/Button.pw.tsx_default_variant-outline-with-gray-color-scheme-dark-mode-base-view-1.png b/theme/components/Button/__screenshots__/Button.pw.tsx_default_variant-outline-with-gray-color-scheme-dark-mode-base-view-1.png deleted file mode 100644 index 9d69c07b5d..0000000000 Binary files a/theme/components/Button/__screenshots__/Button.pw.tsx_default_variant-outline-with-gray-color-scheme-dark-mode-base-view-1.png and /dev/null differ diff --git a/theme/components/Button/__screenshots__/Button.pw.tsx_default_variant-radio-group-dark-mode-base-view-1.png b/theme/components/Button/__screenshots__/Button.pw.tsx_default_variant-radio-group-dark-mode-base-view-1.png deleted file mode 100644 index f8a1d79190..0000000000 Binary files a/theme/components/Button/__screenshots__/Button.pw.tsx_default_variant-radio-group-dark-mode-base-view-1.png and /dev/null differ diff --git a/theme/components/Button/__screenshots__/Button.pw.tsx_default_variant-simple-dark-mode-base-view-1.png b/theme/components/Button/__screenshots__/Button.pw.tsx_default_variant-simple-dark-mode-base-view-1.png deleted file mode 100644 index eb6bb39eb0..0000000000 Binary files a/theme/components/Button/__screenshots__/Button.pw.tsx_default_variant-simple-dark-mode-base-view-1.png and /dev/null differ diff --git a/theme/components/Button/__screenshots__/Button.pw.tsx_default_variant-solid-base-view-1.png b/theme/components/Button/__screenshots__/Button.pw.tsx_default_variant-solid-base-view-1.png deleted file mode 100644 index 88de692805..0000000000 Binary files a/theme/components/Button/__screenshots__/Button.pw.tsx_default_variant-solid-base-view-1.png and /dev/null differ diff --git a/theme/components/Button/__screenshots__/Button.pw.tsx_default_variant-subtle-base-view-1.png b/theme/components/Button/__screenshots__/Button.pw.tsx_default_variant-subtle-base-view-1.png deleted file mode 100644 index 36b02771b6..0000000000 Binary files a/theme/components/Button/__screenshots__/Button.pw.tsx_default_variant-subtle-base-view-1.png and /dev/null differ diff --git a/theme/components/Button/__screenshots__/Button.pw.tsx_default_variant-subtle-with-gray-color-scheme-dark-mode-base-view-1.png b/theme/components/Button/__screenshots__/Button.pw.tsx_default_variant-subtle-with-gray-color-scheme-dark-mode-base-view-1.png deleted file mode 100644 index fdecbca7b8..0000000000 Binary files a/theme/components/Button/__screenshots__/Button.pw.tsx_default_variant-subtle-with-gray-color-scheme-dark-mode-base-view-1.png and /dev/null differ diff --git a/theme/components/Checkbox.ts b/theme/components/Checkbox.ts deleted file mode 100644 index 7a24f98e0c..0000000000 --- a/theme/components/Checkbox.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { checkboxAnatomy as parts } from '@chakra-ui/anatomy'; -import { - createMultiStyleConfigHelpers, - defineStyle, - cssVar, -} from '@chakra-ui/styled-system'; -import { mode } from '@chakra-ui/theme-tools'; -import { runIfFn } from '@chakra-ui/utils'; - -const { definePartsStyle, defineMultiStyleConfig } = - createMultiStyleConfigHelpers(parts.keys); - -const $size = cssVar('checkbox-size'); - -const baseStyleControl = defineStyle((props) => { - const { colorScheme: c } = props; - - return { - _checked: { - bg: mode(`${ c }.500`, `${ c }.300`)(props), - borderColor: mode(`${ c }.500`, `${ c }.300`)(props), - _hover: { - bg: mode(`${ c }.600`, `${ c }.400`)(props), - borderColor: mode(`${ c }.600`, `${ c }.400`)(props), - }, - }, - _indeterminate: { - bg: mode(`${ c }.500`, `${ c }.300`)(props), - borderColor: mode(`${ c }.500`, `${ c }.300`)(props), - }, - }; -}); - -const sizes = { - sm: definePartsStyle({ - control: { [$size.variable]: 'sizes.3' }, - label: { fontSize: 'sm' }, - icon: { fontSize: '3xs' }, - }), - md: definePartsStyle({ - control: { [$size.variable]: 'sizes.4' }, - label: { fontSize: 'md' }, - icon: { fontSize: '2xs' }, - }), - lg: definePartsStyle({ - control: { [$size.variable]: 'sizes.5' }, - label: { fontSize: 'md' }, - icon: { fontSize: '2xs' }, - }), -}; - -const baseStyleLabel = defineStyle({ - _disabled: { opacity: 0.2 }, -}); - -const baseStyle = definePartsStyle((props) => ({ - label: baseStyleLabel, - control: runIfFn(baseStyleControl, props), -})); - -const Checkbox = defineMultiStyleConfig({ - baseStyle, - sizes, -}); - -export default Checkbox; diff --git a/theme/components/Drawer.ts b/theme/components/Drawer.ts deleted file mode 100644 index 47ceb12b63..0000000000 --- a/theme/components/Drawer.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { drawerAnatomy as parts } from '@chakra-ui/anatomy'; -import { - createMultiStyleConfigHelpers, - defineStyle, -} from '@chakra-ui/styled-system'; -import { mode } from '@chakra-ui/theme-tools'; -import { runIfFn } from '@chakra-ui/utils'; - -const { definePartsStyle, defineMultiStyleConfig } = - createMultiStyleConfigHelpers(parts.keys); - -import getDefaultTransitionProps from '../utils/getDefaultTransitionProps'; - -const transitionProps = getDefaultTransitionProps(); - -const baseStyleOverlay = defineStyle({ - ...transitionProps, - bg: 'blackAlpha.800', - zIndex: 'overlay', -}); - -const baseStyleDialog = defineStyle((props) => { - const { isFullHeight } = props; - - return { - ...(isFullHeight && { height: '100vh' }), - ...transitionProps, - zIndex: 'modal', - maxH: '100vh', - bg: mode('white', 'gray.900')(props), - color: 'inherit', - boxShadow: mode('lg', 'dark-lg')(props), - }; -}); - -const baseStyle = definePartsStyle((props) => ({ - overlay: baseStyleOverlay, - dialog: runIfFn(baseStyleDialog, props), -})); - -const Drawer = defineMultiStyleConfig({ - baseStyle, -}); - -export default Drawer; diff --git a/theme/components/FancySelect.ts b/theme/components/FancySelect.ts deleted file mode 100644 index 72fd57425d..0000000000 --- a/theme/components/FancySelect.ts +++ /dev/null @@ -1,26 +0,0 @@ -const sizes = { - sm: { - field: { - px: '0', - height: '36px', - }, - }, - md: { - field: { - px: '0', - height: '56px', - }, - }, - lg: { - field: { - px: '0', - height: '76px', - }, - }, -}; - -const FancySelect = { - sizes, -}; - -export default FancySelect; diff --git a/theme/components/Form.ts b/theme/components/Form.ts deleted file mode 100644 index 119e2b0316..0000000000 --- a/theme/components/Form.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { formAnatomy as parts } from '@chakra-ui/anatomy'; -import { createMultiStyleConfigHelpers } from '@chakra-ui/styled-system'; -import type { StyleFunctionProps } from '@chakra-ui/theme-tools'; - -import getFormStyles from '../utils/getFormStyles'; -import FancySelect from './FancySelect'; -import FormLabel from './FormLabel'; -import Input from './Input'; -import Textarea from './Textarea'; - -const { definePartsStyle, defineMultiStyleConfig } = - createMultiStyleConfigHelpers(parts.keys); - -function getFloatingVariantStylesForSize(size: 'md' | 'lg', props: StyleFunctionProps) { - const formStyles = getFormStyles(props); - - const activeLabelStyles = { - ...FormLabel.variants?.floating?.(props)._focusWithin, - ...FormLabel.sizes?.[size](props)._focusWithin, - }; - - const activeInputStyles = (() => { - switch (size) { - case 'md': { - return { - paddingTop: '26px', - paddingBottom: '10px', - }; - } - - case 'lg': { - return { - paddingTop: '38px', - paddingBottom: '18px', - }; - } - } - })(); - - const inputPx = (() => { - switch (size) { - case 'md': { - return '16px'; - } - - case 'lg': { - return '24px'; - } - } - })(); - - return { - container: { - // active styles - _focusWithin: { - label: activeLabelStyles, - 'input, textarea': activeInputStyles, - }, - '&[data-active=true] label': activeLabelStyles, - - // label styles - label: FormLabel.sizes?.[size](props) || {}, - 'input:not(:placeholder-shown) + label, textarea:not(:placeholder-shown) + label': activeLabelStyles, - 'textarea:not(:placeholder-shown) + label': { - bgColor: formStyles.input.filled.bgColor, - }, - [` - input[readonly] + label, - textarea[readonly] + label, - &[aria-readonly=true] label - `]: { - bgColor: formStyles.input.readOnly.bgColor, - }, - [` - input[aria-invalid=true] + label, - textarea[aria-invalid=true] + label, - &[aria-invalid=true] label - `]: { - color: formStyles.placeholder.error.color, - }, - [` - input[disabled] + label, - textarea[disabled] + label, - &[aria-disabled=true] label - `]: { - color: formStyles.placeholder.disabled.color, - }, - - // input styles - input: Input.sizes?.[size].field, - 'input[aria-autocomplete=list]': FancySelect.sizes[size].field, - textarea: Textarea.sizes?.[size], - 'input, textarea': { - padding: inputPx, - }, - 'input:not(:placeholder-shown), textarea:not(:placeholder-shown)': activeInputStyles, - - // indicator styles - 'input:not(:placeholder-shown) + label .chakra-form__required-indicator, textarea:not(:placeholder-shown) + label .chakra-form__required-indicator': { - color: formStyles.placeholder.default.color, - }, - [` - input[aria-invalid=true] + label .chakra-form__required-indicator, - textarea[aria-invalid=true] + label .chakra-form__required-indicator, - &[aria-invalid=true] .chakra-form__required-indicator - `]: { - color: formStyles.placeholder.error.color, - }, - [` - input[disabled] + label .chakra-form__required-indicator, - textarea[disabled] + label .chakra-form__required-indicator, - &[aria-disabled=true] .chakra-form__required-indicator - `]: { - color: formStyles.placeholder.disabled.color, - }, - }, - }; -} - -const baseStyle = definePartsStyle(() => { - return { - requiredIndicator: { - marginStart: 0, - color: 'gray.500', - }, - }; -}); - -const variantFloating = definePartsStyle((props) => { - return { - container: { - label: FormLabel.variants?.floating(props) || {}, - }, - }; -}); - -const sizes = { - lg: definePartsStyle((props) => { - if (props.variant === 'floating') { - return getFloatingVariantStylesForSize('lg', props); - } - - return {}; - }), - md: definePartsStyle((props) => { - if (props.variant === 'floating') { - return getFloatingVariantStylesForSize('md', props); - } - - return {}; - }), -}; - -const variants = { - floating: variantFloating, -}; - -const Form = defineMultiStyleConfig({ - baseStyle, - variants, - sizes, - defaultProps: { - size: 'md', - }, -}); - -export default Form; diff --git a/theme/components/Form/FormControl.pw.tsx b/theme/components/Form/FormControl.pw.tsx deleted file mode 100644 index 39ab811036..0000000000 --- a/theme/components/Form/FormControl.pw.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import { FormControl, Input, FormLabel } from '@chakra-ui/react'; -import React from 'react'; - -import { test, expect } from 'playwright/lib'; - -test.use({ viewport: { width: 500, height: 300 } }); - -test.describe('floating label size md +@dark-mode', () => { - test('empty', async({ render }) => { - const component = await render( - - - Smart contract / Address (0x...) - , - ); - - await expect(component).toHaveScreenshot(); - - await component.locator('input').focus(); - await expect(component).toHaveScreenshot(); - }); - - test('empty error', async({ render }) => { - const component = await render( - - - Smart contract / Address (0x...) - , - ); - - await expect(component).toHaveScreenshot(); - - await component.locator('input').focus(); - await expect(component).toHaveScreenshot(); - }); - - test('filled', async({ render }) => { - const component = await render( - - - Smart contract / Address (0x...) - , - ); - - await expect(component).toHaveScreenshot(); - }); - - test('filled disabled', async({ render }) => { - const component = await render( - - - Smart contract / Address (0x...) - , - ); - - await expect(component).toHaveScreenshot(); - }); - - test('filled read-only', async({ render }) => { - const component = await render( - - - Smart contract / Address (0x...) - , - ); - - await expect(component).toHaveScreenshot(); - }); - - test('filled error', async({ render }) => { - const component = await render( - - - Smart contract / Address (0x...) - , - ); - - await expect(component).toHaveScreenshot(); - }); -}); - -test.describe('floating label size lg +@dark-mode', () => { - test('empty', async({ render }) => { - const component = await render( - - - Smart contract / Address (0x...) - , - ); - - await expect(component).toHaveScreenshot(); - - await component.locator('input').focus(); - await expect(component).toHaveScreenshot(); - }); - - test('filled', async({ render }) => { - const component = await render( - - - Smart contract / Address (0x...) - , - ); - - await expect(component).toHaveScreenshot(); - - await component.locator('input').focus(); - await expect(component).toHaveScreenshot(); - }); -}); diff --git a/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-lg-dark-mode-empty-1.png b/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-lg-dark-mode-empty-1.png deleted file mode 100644 index 1f1dd2e377..0000000000 Binary files a/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-lg-dark-mode-empty-1.png and /dev/null differ diff --git a/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-lg-dark-mode-empty-2.png b/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-lg-dark-mode-empty-2.png deleted file mode 100644 index 89db3e3de7..0000000000 Binary files a/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-lg-dark-mode-empty-2.png and /dev/null differ diff --git a/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-lg-dark-mode-filled-1.png b/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-lg-dark-mode-filled-1.png deleted file mode 100644 index ff66b3eb82..0000000000 Binary files a/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-lg-dark-mode-filled-1.png and /dev/null differ diff --git a/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-lg-dark-mode-filled-2.png b/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-lg-dark-mode-filled-2.png deleted file mode 100644 index c56bd9edeb..0000000000 Binary files a/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-lg-dark-mode-filled-2.png and /dev/null differ diff --git a/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-md-dark-mode-empty-1.png b/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-md-dark-mode-empty-1.png deleted file mode 100644 index 3453be33b4..0000000000 Binary files a/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-md-dark-mode-empty-1.png and /dev/null differ diff --git a/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-md-dark-mode-empty-2.png b/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-md-dark-mode-empty-2.png deleted file mode 100644 index df1f13adc2..0000000000 Binary files a/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-md-dark-mode-empty-2.png and /dev/null differ diff --git a/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-md-dark-mode-empty-error-1.png b/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-md-dark-mode-empty-error-1.png deleted file mode 100644 index 4d19167035..0000000000 Binary files a/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-md-dark-mode-empty-error-1.png and /dev/null differ diff --git a/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-md-dark-mode-empty-error-2.png b/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-md-dark-mode-empty-error-2.png deleted file mode 100644 index 8f252b3add..0000000000 Binary files a/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-md-dark-mode-empty-error-2.png and /dev/null differ diff --git a/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-md-dark-mode-filled-1.png b/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-md-dark-mode-filled-1.png deleted file mode 100644 index d1c5826ade..0000000000 Binary files a/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-md-dark-mode-filled-1.png and /dev/null differ diff --git a/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-md-dark-mode-filled-disabled-1.png b/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-md-dark-mode-filled-disabled-1.png deleted file mode 100644 index c37d9af17f..0000000000 Binary files a/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-md-dark-mode-filled-disabled-1.png and /dev/null differ diff --git a/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-md-dark-mode-filled-error-1.png b/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-md-dark-mode-filled-error-1.png deleted file mode 100644 index 36af890044..0000000000 Binary files a/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-md-dark-mode-filled-error-1.png and /dev/null differ diff --git a/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-md-dark-mode-filled-read-only-1.png b/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-md-dark-mode-filled-read-only-1.png deleted file mode 100644 index 06974bcc7c..0000000000 Binary files a/theme/components/Form/__screenshots__/FormControl.pw.tsx_dark-color-mode_floating-label-size-md-dark-mode-filled-read-only-1.png and /dev/null differ diff --git a/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-lg-dark-mode-empty-1.png b/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-lg-dark-mode-empty-1.png deleted file mode 100644 index eea73198d2..0000000000 Binary files a/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-lg-dark-mode-empty-1.png and /dev/null differ diff --git a/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-lg-dark-mode-empty-2.png b/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-lg-dark-mode-empty-2.png deleted file mode 100644 index 60812d7443..0000000000 Binary files a/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-lg-dark-mode-empty-2.png and /dev/null differ diff --git a/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-lg-dark-mode-filled-1.png b/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-lg-dark-mode-filled-1.png deleted file mode 100644 index 24de73349c..0000000000 Binary files a/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-lg-dark-mode-filled-1.png and /dev/null differ diff --git a/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-lg-dark-mode-filled-2.png b/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-lg-dark-mode-filled-2.png deleted file mode 100644 index 0f549d03d9..0000000000 Binary files a/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-lg-dark-mode-filled-2.png and /dev/null differ diff --git a/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-md-dark-mode-empty-1.png b/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-md-dark-mode-empty-1.png deleted file mode 100644 index 6c295197f2..0000000000 Binary files a/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-md-dark-mode-empty-1.png and /dev/null differ diff --git a/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-md-dark-mode-empty-2.png b/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-md-dark-mode-empty-2.png deleted file mode 100644 index e118b2e86f..0000000000 Binary files a/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-md-dark-mode-empty-2.png and /dev/null differ diff --git a/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-md-dark-mode-empty-error-1.png b/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-md-dark-mode-empty-error-1.png deleted file mode 100644 index 84482f53d7..0000000000 Binary files a/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-md-dark-mode-empty-error-1.png and /dev/null differ diff --git a/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-md-dark-mode-empty-error-2.png b/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-md-dark-mode-empty-error-2.png deleted file mode 100644 index b077743f2f..0000000000 Binary files a/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-md-dark-mode-empty-error-2.png and /dev/null differ diff --git a/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-md-dark-mode-filled-1.png b/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-md-dark-mode-filled-1.png deleted file mode 100644 index 56712ddcf5..0000000000 Binary files a/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-md-dark-mode-filled-1.png and /dev/null differ diff --git a/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-md-dark-mode-filled-disabled-1.png b/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-md-dark-mode-filled-disabled-1.png deleted file mode 100644 index 9b30da40a5..0000000000 Binary files a/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-md-dark-mode-filled-disabled-1.png and /dev/null differ diff --git a/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-md-dark-mode-filled-error-1.png b/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-md-dark-mode-filled-error-1.png deleted file mode 100644 index acb481c7a7..0000000000 Binary files a/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-md-dark-mode-filled-error-1.png and /dev/null differ diff --git a/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-md-dark-mode-filled-read-only-1.png b/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-md-dark-mode-filled-read-only-1.png deleted file mode 100644 index c721e5196e..0000000000 Binary files a/theme/components/Form/__screenshots__/FormControl.pw.tsx_default_floating-label-size-md-dark-mode-filled-read-only-1.png and /dev/null differ diff --git a/theme/components/FormLabel.ts b/theme/components/FormLabel.ts deleted file mode 100644 index c67ac969eb..0000000000 --- a/theme/components/FormLabel.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { defineStyle, defineStyleConfig } from '@chakra-ui/styled-system'; - -import getFormStyles from '../utils/getFormStyles'; - -const baseStyle = defineStyle({ - display: 'flex', - fontSize: 'md', - marginEnd: '3', - mb: '2', - fontWeight: 'medium', - transitionProperty: 'common', - transitionDuration: 'normal', - opacity: 1, - _disabled: { - opacity: 0.2, - }, -}); - -const variantFloating = defineStyle((props) => { - const formStyles = getFormStyles(props); - - return { - left: '2px', - top: '2px', - zIndex: 2, - position: 'absolute', - borderRadius: 'base', - boxSizing: 'border-box', - color: formStyles.placeholder.default.color, - backgroundColor: props.bgColor || props.backgroundColor || 'transparent', - pointerEvents: 'none', - margin: 0, - transformOrigin: 'top left', - transitionProperty: 'font-size, line-height, padding, top, background-color', - overflow: 'hidden', - whiteSpace: 'nowrap', - textOverflow: 'ellipsis', - _focusWithin: { - backgroundColor: props.bgColor || props.backgroundColor || 'transparent', - color: formStyles.placeholder.default.color, - fontSize: 'xs', - lineHeight: '16px', - borderTopRightRadius: 'none', - '& svg': { - width: '16px', - height: '16px', - }, - }, - '& svg': { - transitionProperty: 'width, height', - transitionDuration: 'normal', - transitionTimingFunction: 'ease', - width: '24px', - height: '24px', - mr: '2', - }, - }; -}); - -const variants = { - floating: variantFloating, -}; - -const sizes = { - lg: defineStyle((props) => { - if (props.variant === 'floating') { - return { - fontSize: 'md', - lineHeight: '24px', - padding: '26px 4px 26px 24px', - right: '22px', - _focusWithin: { - padding: '16px 0 2px 24px', - }, - '&[data-fancy=true]': { - right: '36px', - }, - }; - } - - return {}; - }), - md: defineStyle((props) => { - if (props.variant === 'floating') { - return { - fontSize: 'md', - lineHeight: '20px', - padding: '18px 4px 18px 16px', - right: '22px', - _focusWithin: { - padding: '10px 0 2px 16px', - }, - '&[data-fancy=true]': { - right: '36px', - }, - }; - } - - return {}; - }), -}; - -const FormLabel = defineStyleConfig({ - variants, - baseStyle, - sizes, -}); - -export default FormLabel; diff --git a/theme/components/Heading.ts b/theme/components/Heading.ts deleted file mode 100644 index e9cf8c51b2..0000000000 --- a/theme/components/Heading.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { defineStyle, defineStyleConfig } from '@chakra-ui/styled-system'; -import type { SystemStyleInterpolation } from '@chakra-ui/theme-tools'; -import { mode } from '@chakra-ui/theme-tools'; - -const baseStyle: SystemStyleInterpolation = (props) => { - return { - fontWeight: '500', - color: mode('blackAlpha.800', 'whiteAlpha.800')(props), - }; -}; - -const sizes = { - '2xl': defineStyle({ - fontSize: '48px', - lineHeight: '60px', - }), - xl: defineStyle({ - fontSize: '40px', - lineHeight: '48px', - }), - lg: defineStyle({ - fontSize: '32px', - lineHeight: '40px', - letterSpacing: '-0.5px', - }), - md: defineStyle({ - fontSize: '24px', - lineHeight: '32px', - }), - sm: defineStyle({ - fontSize: '18px', - lineHeight: '24px', - }), - xs: defineStyle({ - fontSize: '14px', - lineHeight: '20px', - }), -}; - -const Heading = defineStyleConfig({ - sizes, - baseStyle, -}); - -export default Heading; diff --git a/theme/components/Input.ts b/theme/components/Input.ts deleted file mode 100644 index d127835109..0000000000 --- a/theme/components/Input.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { inputAnatomy as parts } from '@chakra-ui/anatomy'; -import { Input as InputComponent } from '@chakra-ui/react'; -import { - createMultiStyleConfigHelpers, - defineStyle, -} from '@chakra-ui/styled-system'; -import { mode } from '@chakra-ui/theme-tools'; - -const { definePartsStyle, defineMultiStyleConfig } = - createMultiStyleConfigHelpers(parts.keys); - -import getDefaultTransitionProps from '../utils/getDefaultTransitionProps'; -import getOutlinedFieldStyles from '../utils/getOutlinedFieldStyles'; - -const size = { - xs: defineStyle({ - fontSize: 'md', - lineHeight: '24px', - px: '8px', - py: '4px', - h: '32px', - borderRadius: 'base', - }), - sm: defineStyle({ - fontSize: 'md', - lineHeight: '24px', - px: '8px', - py: '12px', - h: '40px', - borderRadius: 'base', - }), - // TEMPORARY INPUT SIZE!!! - // soon we will migrate to the new size and get rid off this one - // lg -> 60 - // md -> 48 - // sm -> 40 - // xs ->32 - sm_md: defineStyle({ - fontSize: 'md', - lineHeight: '24px', - px: '8px', - py: '12px', - h: '48px', - borderRadius: 'base', - }), - md: defineStyle({ - fontSize: 'md', - lineHeight: '20px', - px: '20px', - py: '20px', - h: '60px', - borderRadius: 'base', - }), - lg: defineStyle({ - fontSize: 'md', - lineHeight: '20px', - px: '24px', - py: '28px', - h: '80px', - borderRadius: 'base', - }), -}; - -const variantOutline = definePartsStyle((props) => { - const transitionProps = getDefaultTransitionProps(); - - return { - field: getOutlinedFieldStyles(props), - addon: { - border: '2px solid', - borderColor: 'transparent', - bg: mode('blackAlpha.100', 'whiteAlpha.200')(props), - color: mode('blackAlpha.800', 'whiteAlpha.800')(props), - ...transitionProps, - }, - }; -}); - -const sizes = { - xs: definePartsStyle({ - field: size.xs, - addon: size.xs, - }), - sm: definePartsStyle({ - field: size.sm, - addon: size.sm, - }), - sm_md: definePartsStyle({ - field: size.sm_md, - addon: size.sm_md, - }), - md: definePartsStyle({ - field: size.md, - addon: size.md, - }), - lg: definePartsStyle({ - field: size.lg, - addon: size.lg, - }), -}; - -const variants = { - outline: variantOutline, -}; - -const Input = defineMultiStyleConfig({ - sizes, - variants, - defaultProps: { - size: 'md', - }, -}); - -InputComponent.defaultProps = { - ...InputComponent.defaultProps, - placeholder: ' ', -}; - -export default Input; diff --git a/theme/components/Link.ts b/theme/components/Link.ts deleted file mode 100644 index 6cbee7bf00..0000000000 --- a/theme/components/Link.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type { SystemStyleInterpolation } from '@chakra-ui/styled-system'; -import { defineStyle, defineStyleConfig } from '@chakra-ui/styled-system'; -import { mode } from '@chakra-ui/theme-tools'; - -import getDefaultTransitionProps from '../utils/getDefaultTransitionProps'; - -const baseStyle = defineStyle(getDefaultTransitionProps()); - -const variantPrimary = defineStyle((props) => { - return { - color: 'link', - _hover: { - color: 'link_hovered', - textDecorationStyle: props.textDecorationStyle || 'solid', - }, - }; -}); - -const variantSecondary = defineStyle((props) => { - return { - color: mode('gray.600', 'gray.500')(props), - _hover: { - color: mode('gray.600', 'gray.400')(props), - }, - }; -}); - -const variants: Record = { - primary: variantPrimary, - secondary: variantSecondary, -}; - -const defaultProps = { - variant: 'primary', -}; - -const Link = defineStyleConfig({ - variants, - defaultProps, - baseStyle, -}); - -export default Link; diff --git a/theme/components/Menu.ts b/theme/components/Menu.ts deleted file mode 100644 index 5e425ff738..0000000000 --- a/theme/components/Menu.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { menuAnatomy as parts } from '@chakra-ui/anatomy'; -import { - createMultiStyleConfigHelpers, - cssVar, - defineStyle, -} from '@chakra-ui/styled-system'; - -const { defineMultiStyleConfig, definePartsStyle } = - createMultiStyleConfigHelpers(parts.keys); - -const $bg = cssVar('menu-bg'); -const $shadow = cssVar('menu-shadow'); - -const baseStyleList = defineStyle({ - [$bg.variable]: '#fff', - [$shadow.variable]: 'shadows.2xl', - _dark: { - [$bg.variable]: 'colors.gray.900', - [$shadow.variable]: 'shadows.dark-lg', - }, - borderWidth: '0', - bg: $bg.reference, - boxShadow: $shadow.reference, -}); - -const baseStyleItem = defineStyle({ - _focus: { - [$bg.variable]: 'transparent', - _dark: { - [$bg.variable]: 'transparent', - }, - }, - _hover: { - [$bg.variable]: 'colors.blue.50', - _dark: { - [$bg.variable]: 'colors.whiteAlpha.100', - }, - }, - bg: $bg.reference, -}); - -const baseStyle = definePartsStyle({ - list: baseStyleList, - item: baseStyleItem, -}); - -const Menu = defineMultiStyleConfig({ - baseStyle, -}); - -export default Menu; diff --git a/theme/components/Modal.ts b/theme/components/Modal.ts deleted file mode 100644 index 2992f4e7d8..0000000000 --- a/theme/components/Modal.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { modalAnatomy as parts } from '@chakra-ui/anatomy'; -import { Modal as ModalComponent } from '@chakra-ui/react'; -import { - createMultiStyleConfigHelpers, - defineStyle, -} from '@chakra-ui/styled-system'; -import { mode } from '@chakra-ui/theme-tools'; -import { runIfFn } from '@chakra-ui/utils'; - -const { defineMultiStyleConfig, definePartsStyle } = - createMultiStyleConfigHelpers(parts.keys); - -const baseStyleDialog = defineStyle(() => { - return { - padding: 8, - borderRadius: 'lg', - bg: 'dialog_bg', - margin: 'auto', - }; -}); - -const baseStyleDialogContainer = defineStyle({ - '::-webkit-scrollbar': { display: 'none' }, - 'scrollbar-width': 'none', - // '@supports (height: -webkit-fill-available)': { height: '-webkit-fill-available' }, -}); - -const baseStyleHeader = defineStyle((props) => ({ - padding: 0, - marginBottom: 8, - fontSize: '2xl', - lineHeight: 10, - color: mode('blackAlpha.800', 'whiteAlpha.800')(props), -})); - -const baseStyleBody = defineStyle({ - padding: 0, - marginBottom: 8, - flex: 'initial', -}); - -const baseStyleFooter = defineStyle({ - padding: 0, - justifyContent: 'flex-start', -}); - -const baseStyleCloseButton = defineStyle((props) => { - return { - top: 8, - right: 8, - height: 10, - width: 10, - color: mode('gray.700', 'gray.500')(props), - _hover: { color: 'link_hovered' }, - _active: { bg: 'none' }, - }; -}); - -const baseStyleOverlay = defineStyle({ - bg: 'blackAlpha.800', -}); - -const baseStyle = definePartsStyle((props) => ({ - dialog: runIfFn(baseStyleDialog), - dialogContainer: baseStyleDialogContainer, - - header: runIfFn(baseStyleHeader, props), - body: baseStyleBody, - footer: baseStyleFooter, - closeButton: runIfFn(baseStyleCloseButton, props), - overlay: baseStyleOverlay, -})); - -const sizes = { - sm: definePartsStyle({ - dialogContainer: { - height: '100%', - }, - dialog: { - maxW: '536px', - }, - }), - md: definePartsStyle({ - dialogContainer: { - height: '100%', - }, - dialog: { - maxW: '760px', - }, - }), - full: definePartsStyle({ - dialogContainer: { - height: '100%', - }, - dialog: { - maxW: '100vw', - my: '0', - borderRadius: '0', - padding: '80px 16px 32px 16px', - height: '100%', - overflowY: 'scroll', - }, - closeButton: { - top: 4, - right: 6, - width: 6, - height: 6, - }, - header: { - mb: 6, - }, - }), -}; - -const Modal = defineMultiStyleConfig({ - sizes, - baseStyle, -}); - -export default Modal; - -ModalComponent.defaultProps = { - ...ModalComponent.defaultProps, - isCentered: true, -}; diff --git a/theme/components/PinInput.ts b/theme/components/PinInput.ts deleted file mode 100644 index 251b38ca78..0000000000 --- a/theme/components/PinInput.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { defineStyle, defineStyleConfig } from '@chakra-ui/styled-system'; - -import getOutlinedFieldStyles from '../utils/getOutlinedFieldStyles'; - -const baseStyle = defineStyle({ - textAlign: 'center', - bgColor: 'dialog_bg', -}); - -const sizes = { - md: defineStyle({ - fontSize: 'md', - w: 10, - h: 10, - borderRadius: 'md', - }), -}; - -const variants = { - outline: defineStyle( - (props) => getOutlinedFieldStyles(props), - ), -}; - -const PinInput = defineStyleConfig({ - baseStyle, - sizes, - variants, - defaultProps: { - size: 'md', - }, -}); - -export default PinInput; diff --git a/theme/components/Popover.ts b/theme/components/Popover.ts deleted file mode 100644 index 9f277d8605..0000000000 --- a/theme/components/Popover.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { popoverAnatomy as parts } from '@chakra-ui/anatomy'; -import { - createMultiStyleConfigHelpers, - defineStyle, -} from '@chakra-ui/styled-system'; -import { cssVar, mode } from '@chakra-ui/theme-tools'; - -const { defineMultiStyleConfig, definePartsStyle } = - createMultiStyleConfigHelpers(parts.keys); - -const $popperBg = cssVar('popper-bg'); - -const $arrowBg = cssVar('popper-arrow-bg'); -const $arrowShadowColor = cssVar('popper-arrow-shadow-color'); - -const baseStylePopper = defineStyle({ - zIndex: 'popover', -}); - -const baseStyleContent = defineStyle((props) => { - const bg = mode('white', 'gray.900')(props); - const shadowColor = mode('blackAlpha.200', 'whiteAlpha.300')(props); - - return { - [$popperBg.variable]: `colors.${ bg }`, - bg: $popperBg.reference, - [$arrowBg.variable]: $popperBg.reference, - [$arrowShadowColor.variable]: `colors.${ shadowColor }`, - _dark: { - [$popperBg.variable]: `colors.gray.900`, - [$arrowShadowColor.variable]: `colors.whiteAlpha.300`, - boxShadow: 'dark-lg', - }, - width: 'xs', - border: 'none', - borderColor: 'inherit', - borderRadius: 'md', - boxShadow: '2xl', - zIndex: 'inherit', - _focusVisible: { - outline: 0, - boxShadow: '2xl', - }, - }; -}); - -const baseStyleHeader = defineStyle({ - px: 3, - py: 2, - borderBottomWidth: '1px', -}); - -const baseStyleBody = defineStyle({ - px: 4, - py: 4, -}); - -const baseStyleFooter = defineStyle({ - px: 3, - py: 2, - borderTopWidth: '1px', -}); - -const baseStyleCloseButton = defineStyle({ - position: 'absolute', - borderRadius: 'md', - top: 1, - insetEnd: 2, - padding: 2, -}); - -const baseStyle = definePartsStyle((props) => ({ - popper: baseStylePopper, - content: baseStyleContent(props), - header: baseStyleHeader, - body: baseStyleBody, - footer: baseStyleFooter, - arrow: {}, - closeButton: baseStyleCloseButton, -})); - -const Popover = defineMultiStyleConfig({ - baseStyle, -}); - -export default Popover; diff --git a/theme/components/Radio.ts b/theme/components/Radio.ts deleted file mode 100644 index 18033bc5fd..0000000000 --- a/theme/components/Radio.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { radioAnatomy as parts } from '@chakra-ui/anatomy'; -import { - createMultiStyleConfigHelpers, - defineStyle, -} from '@chakra-ui/styled-system'; - -const { defineMultiStyleConfig, definePartsStyle } = - createMultiStyleConfigHelpers(parts.keys); - -const baseStyleLabel = defineStyle({ - _disabled: { opacity: 0.2 }, - width: 'fit-content', -}); - -const baseStyleContainer = defineStyle({ - width: 'fit-content', -}); - -const baseStyle = definePartsStyle({ - label: baseStyleLabel, - container: baseStyleContainer, -}); - -const sizes = { - md: definePartsStyle({ - control: { w: '4', h: '4' }, - label: { fontSize: 'md' }, - }), - lg: definePartsStyle({ - control: { w: '5', h: '5' }, - label: { fontSize: 'md' }, - }), - sm: definePartsStyle({ - control: { width: '3', height: '3' }, - label: { fontSize: 'sm' }, - }), -}; - -const Radio = defineMultiStyleConfig({ - baseStyle, - sizes, -}); - -export default Radio; diff --git a/theme/components/Select.ts b/theme/components/Select.ts deleted file mode 100644 index 3e53c6952a..0000000000 --- a/theme/components/Select.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { selectAnatomy as parts } from '@chakra-ui/anatomy'; -import { - createMultiStyleConfigHelpers, - defineStyle, -} from '@chakra-ui/styled-system'; -import { mode } from '@chakra-ui/theme-tools'; - -import Input from './Input'; - -const { defineMultiStyleConfig, definePartsStyle } = - createMultiStyleConfigHelpers(parts.keys); - -const variantOutline = definePartsStyle((props) => { - return { - field: { - ...Input.variants?.outline(props).field, - borderColor: mode('gray.200', 'gray.600')(props), - _hover: { - borderColor: mode('gray.300', 'gray.500')(props), - }, - _focusVisible: { - borderColor: mode('gray.200', 'gray.600')(props), - boxShadow: 'none', - }, - cursor: 'pointer', - }, - }; -}); - -const iconSpacing = defineStyle({ - paddingInlineEnd: '8', -}); - -const sizes = { - lg: { - ...Input.sizes?.lg, - field: { - ...Input.sizes?.lg.field, - ...iconSpacing, - }, - }, - md: { - ...Input.sizes?.md, - field: { - ...Input.sizes?.md.field, - ...iconSpacing, - }, - }, - sm: { - ...Input.sizes?.sm, - field: { - ...Input.sizes?.sm.field, - ...iconSpacing, - }, - }, - xs: { - ...Input.sizes?.xs, - field: { - ...Input.sizes?.xs.field, - ...iconSpacing, - fontSize: 'sm', - lineHeight: '20px', - }, - }, -}; - -const Select = defineMultiStyleConfig({ - variants: { - ...Input.variants, - outline: variantOutline, - }, - sizes, - defaultProps: { - size: 'xs', - }, -}); - -export default Select; diff --git a/theme/components/Skeleton.ts b/theme/components/Skeleton.ts deleted file mode 100644 index 6648bf4368..0000000000 --- a/theme/components/Skeleton.ts +++ /dev/null @@ -1,47 +0,0 @@ -// eslint-disable-next-line no-restricted-imports -import { Skeleton as SkeletonComponent } from '@chakra-ui/react'; -import { - defineStyle, - defineStyleConfig, -} from '@chakra-ui/styled-system'; -import { keyframes } from '@chakra-ui/system'; -import { getColor, mode } from '@chakra-ui/theme-tools'; - -const shine = () => - keyframes({ - to: { backgroundPositionX: '-200%' }, - }); - -const baseStyle = defineStyle((props) => { - const defaultStartColor = mode('blackAlpha.50', 'whiteAlpha.50')(props); - const defaultEndColor = mode('blackAlpha.100', 'whiteAlpha.100')(props); - - const { - startColor = defaultStartColor, - endColor = defaultEndColor, - theme, - } = props; - - const start = getColor(theme, startColor); - const end = getColor(theme, endColor); - - return { - opacity: 1, - borderRadius: 'md', - borderColor: start, - background: `linear-gradient(90deg, ${ start } 8%, ${ end } 18%, ${ start } 33%)`, - backgroundSize: '200% 100%', - }; -}); - -const Skeleton = defineStyleConfig({ - baseStyle, -}); - -export default Skeleton; - -SkeletonComponent.defaultProps = { - ...SkeletonComponent.defaultProps, - speed: 1, - animation: `1s linear infinite ${ shine() }`, -}; diff --git a/theme/components/Spinner.ts b/theme/components/Spinner.ts deleted file mode 100644 index 3179c944b3..0000000000 --- a/theme/components/Spinner.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { defineStyle, defineStyleConfig } from '@chakra-ui/styled-system'; -import { mode } from '@chakra-ui/theme-tools'; - -const baseStyle = defineStyle((props) => { - const { emptyColor, color } = props; - - return { - borderColor: color || 'blue.500', - borderBottomColor: emptyColor || mode('blackAlpha.200', 'whiteAlpha.200')(props), - borderLeftColor: emptyColor || mode('blackAlpha.200', 'whiteAlpha.200')(props), - }; -}); - -const Spinner = defineStyleConfig({ - baseStyle, - defaultProps: { - size: 'md', - }, -}); - -export default Spinner; diff --git a/theme/components/Switch.ts b/theme/components/Switch.ts deleted file mode 100644 index 6059618c0f..0000000000 --- a/theme/components/Switch.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { switchAnatomy as parts } from '@chakra-ui/anatomy'; -import { defineStyle, createMultiStyleConfigHelpers } from '@chakra-ui/styled-system'; -import { mode } from '@chakra-ui/theme-tools'; - -const { defineMultiStyleConfig, definePartsStyle } = - createMultiStyleConfigHelpers(parts.keys); - -const baseStyleTrack = defineStyle((props) => { - const { colorScheme: c } = props; - - return { - _checked: { - bg: mode(`${ c }.500`, `${ c }.300`)(props), - _hover: { - bg: mode(`${ c }.600`, `${ c }.400`)(props), - }, - }, - _focusVisible: { - boxShadow: 'none', - }, - }; -}); - -const baseStyle = definePartsStyle((props) => ({ - track: baseStyleTrack(props), -})); - -const Switch = defineMultiStyleConfig({ - baseStyle, -}); - -export default Switch; diff --git a/theme/components/Table.ts b/theme/components/Table.ts deleted file mode 100644 index 583cd0ece7..0000000000 --- a/theme/components/Table.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { tableAnatomy as parts } from '@chakra-ui/anatomy'; -import { - createMultiStyleConfigHelpers, -} from '@chakra-ui/styled-system'; -import { mode } from '@chakra-ui/theme-tools'; - -import getDefaultTransitionProps from '../utils/getDefaultTransitionProps'; - -const { defineMultiStyleConfig, definePartsStyle } = - createMultiStyleConfigHelpers(parts.keys); - -const variantSimple = definePartsStyle((props) => { - const transitionProps = getDefaultTransitionProps(); - - return { - th: { - border: 0, - color: mode('blackAlpha.700', 'whiteAlpha.700')(props), - backgroundColor: mode('blackAlpha.100', 'whiteAlpha.200')(props), - ...transitionProps, - }, - thead: { - ...transitionProps, - }, - td: { - borderColor: 'divider', - ...transitionProps, - }, - }; -}); - -const sizes = { - sm: definePartsStyle({ - th: { - px: '6px', - py: '10px', - fontSize: 'sm', - _first: { - pl: 3, - }, - _last: { - pr: 3, - }, - }, - td: { - px: '6px', - py: 4, - fontSize: 'sm', - fontWeight: 500, - lineHeight: 5, - _first: { - pl: 3, - }, - _last: { - pr: 3, - }, - }, - }), -}; - -const variants = { - simple: variantSimple, -}; - -const baseStyle = definePartsStyle({ - th: { - textTransform: 'none', - fontFamily: 'body', - fontWeight: '500', - overflow: 'hidden', - color: 'gray.500', - letterSpacing: 'none', - _first: { - borderTopLeftRadius: '8px', - }, - _last: { - borderTopRightRadius: '8px', - }, - }, - td: { - fontSize: 'md', - verticalAlign: 'top', - }, - table: { - tableLayout: 'fixed', - borderTopLeftRadius: 'base', - borderTopRightRadius: 'base', - overflow: 'unset', - fontVariant: 'normal', - fontVariantLigatures: 'no-contextual', - }, -}); - -const Table = defineMultiStyleConfig({ - baseStyle, - sizes, - variants, - defaultProps: { - size: 'sm', - variant: 'simple', - }, -}); - -export default Table; diff --git a/theme/components/Tabs.ts b/theme/components/Tabs.ts deleted file mode 100644 index f64bec2886..0000000000 --- a/theme/components/Tabs.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { tabsAnatomy as parts } from '@chakra-ui/anatomy'; -import { - createMultiStyleConfigHelpers, -} from '@chakra-ui/styled-system'; -import { mode } from '@chakra-ui/theme-tools'; -const { defineMultiStyleConfig, definePartsStyle } = - createMultiStyleConfigHelpers(parts.keys); - -import Button from './Button/Button'; - -const variantSoftRounded = definePartsStyle((props) => { - return { - tab: { - borderRadius: 'base', - fontWeight: '600', - color: mode('blue.700', 'gray.400')(props), - _selected: { - color: mode('blue.700', 'gray.50')(props), - bg: mode('blue.50', 'gray.800')(props), - _hover: { - color: mode('blue.700', 'gray.50')(props), - }, - }, - _hover: { - color: 'link_hovered', - }, - _focusVisible: { - boxShadow: { base: 'none', lg: 'outline' }, - }, - }, - }; -}); - -const variantOutline = definePartsStyle((props) => { - return { - tab: { - ...Button.variants?.outline(props), - ...Button.baseStyle, - _selected: Button.variants?.outline(props)._active, - }, - }; -}); - -const variantRadioGroup = definePartsStyle((props) => { - return { - tab: { - ...Button.baseStyle, - ...Button.variants?.radio_group(props), - _selected: Button.variants?.radio_group(props)?.[` - &[data-selected=true], - &[data-selected=true][aria-selected=true] - `], - borderRadius: 'none', - '&[role="tab"]': { - _first: { - borderTopLeftRadius: 'base', - borderBottomLeftRadius: 'base', - }, - _last: { - borderTopRightRadius: 'base', - borderBottomRightRadius: 'base', - }, - }, - }, - }; -}); - -const sizes = { - sm: definePartsStyle({ - tab: Button.sizes?.sm, - }), - md: definePartsStyle({ - tab: Button.sizes?.md, - }), -}; - -const variants = { - 'soft-rounded': variantSoftRounded, - outline: variantOutline, - radio_group: variantRadioGroup, -}; - -const Tabs = defineMultiStyleConfig({ - sizes, - variants, -}); - -export default Tabs; diff --git a/theme/components/Tag/Tag.pw.tsx b/theme/components/Tag/Tag.pw.tsx deleted file mode 100644 index 28da22a9ef..0000000000 --- a/theme/components/Tag/Tag.pw.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { Box, Tag } from '@chakra-ui/react'; -import React from 'react'; - -import { test, expect } from 'playwright/lib'; - -[ 'blue', 'gray', 'gray-blue', 'orange', 'green', 'purple', 'cyan', 'teal' ].forEach((colorScheme) => { - test(`${ colorScheme } color scheme +@dark-mode`, async({ render }) => { - const component = await render(content); - await expect(component.getByText(/content/i)).toHaveScreenshot(); - }); -}); - -test('with long text', async({ render }) => { - const component = await render( - - this is very looooooooooong text - , - ); - await expect(component.getByText(/this/i)).toHaveScreenshot(); -}); diff --git a/theme/components/Tag/Tag.ts b/theme/components/Tag/Tag.ts deleted file mode 100644 index 240a2a1c26..0000000000 --- a/theme/components/Tag/Tag.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { tagAnatomy as parts } from '@chakra-ui/anatomy'; -import { - createMultiStyleConfigHelpers, - defineStyle, -} from '@chakra-ui/styled-system'; -import { mode } from '@chakra-ui/theme-tools'; - -import getDefaultTransitionProps from '../../utils/getDefaultTransitionProps'; -import Badge from '../Badge'; - -const transitionProps = getDefaultTransitionProps(); - -const { defineMultiStyleConfig, definePartsStyle } = - createMultiStyleConfigHelpers(parts.keys); - -const variants = { - subtle: definePartsStyle((props) => ({ - container: Badge.variants?.subtle(props), - })), - select: definePartsStyle((props) => ({ - container: { - bg: mode('gray.100', 'gray.800')(props), - color: mode('gray.500', 'whiteAlpha.800')(props), - cursor: 'pointer', - _hover: { - color: 'blue.400', - opacity: 0.76, - }, - [` - &[data-selected=true], - &[data-selected=true][aria-selected=true] - `]: { - bg: mode('blue.500', 'blue.900')(props), - color: 'whiteAlpha.800', - }, - }, - })), -}; - -const sizes = { - sm: definePartsStyle({ - container: { - minH: 6, - minW: 6, - fontSize: 'sm', - px: 1, - py: '2px', - lineHeight: 5, - }, - }), - md: definePartsStyle({ - container: { - minH: 8, - minW: 8, - fontSize: 'sm', - px: '6px', - py: '6px', - lineHeight: 5, - }, - }), -}; - -const baseStyleContainer = defineStyle({ - display: 'inline-block', - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - borderRadius: 'sm', - ...transitionProps, -}); - -const baseStyle = definePartsStyle({ - container: baseStyleContainer, -}); - -const Tag = defineMultiStyleConfig({ - baseStyle, - variants, - sizes, - defaultProps: { - size: 'sm', - variant: 'subtle', - colorScheme: 'gray', - }, -}); - -export default Tag; diff --git a/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_blue-color-scheme-dark-mode-1.png b/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_blue-color-scheme-dark-mode-1.png deleted file mode 100644 index cc5ed7c640..0000000000 Binary files a/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_blue-color-scheme-dark-mode-1.png and /dev/null differ diff --git a/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_cyan-color-scheme-dark-mode-1.png b/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_cyan-color-scheme-dark-mode-1.png deleted file mode 100644 index 93e36ae37d..0000000000 Binary files a/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_cyan-color-scheme-dark-mode-1.png and /dev/null differ diff --git a/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_gray-blue-color-scheme-dark-mode-1.png b/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_gray-blue-color-scheme-dark-mode-1.png deleted file mode 100644 index 991537c134..0000000000 Binary files a/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_gray-blue-color-scheme-dark-mode-1.png and /dev/null differ diff --git a/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_gray-color-scheme-dark-mode-1.png b/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_gray-color-scheme-dark-mode-1.png deleted file mode 100644 index 2cc37e85b8..0000000000 Binary files a/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_gray-color-scheme-dark-mode-1.png and /dev/null differ diff --git a/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_green-color-scheme-dark-mode-1.png b/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_green-color-scheme-dark-mode-1.png deleted file mode 100644 index acd4ad7582..0000000000 Binary files a/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_green-color-scheme-dark-mode-1.png and /dev/null differ diff --git a/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_orange-color-scheme-dark-mode-1.png b/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_orange-color-scheme-dark-mode-1.png deleted file mode 100644 index 0079acf091..0000000000 Binary files a/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_orange-color-scheme-dark-mode-1.png and /dev/null differ diff --git a/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_purple-color-scheme-dark-mode-1.png b/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_purple-color-scheme-dark-mode-1.png deleted file mode 100644 index 7d5714466f..0000000000 Binary files a/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_purple-color-scheme-dark-mode-1.png and /dev/null differ diff --git a/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_teal-color-scheme-dark-mode-1.png b/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_teal-color-scheme-dark-mode-1.png deleted file mode 100644 index a5c299c701..0000000000 Binary files a/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_teal-color-scheme-dark-mode-1.png and /dev/null differ diff --git a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_blue-color-scheme-dark-mode-1.png b/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_blue-color-scheme-dark-mode-1.png deleted file mode 100644 index ce7946c035..0000000000 Binary files a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_blue-color-scheme-dark-mode-1.png and /dev/null differ diff --git a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_cyan-color-scheme-dark-mode-1.png b/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_cyan-color-scheme-dark-mode-1.png deleted file mode 100644 index 1cac3c3c0c..0000000000 Binary files a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_cyan-color-scheme-dark-mode-1.png and /dev/null differ diff --git a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_gray-blue-color-scheme-dark-mode-1.png b/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_gray-blue-color-scheme-dark-mode-1.png deleted file mode 100644 index b3f72fbba9..0000000000 Binary files a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_gray-blue-color-scheme-dark-mode-1.png and /dev/null differ diff --git a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_gray-color-scheme-dark-mode-1.png b/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_gray-color-scheme-dark-mode-1.png deleted file mode 100644 index fadfdd7262..0000000000 Binary files a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_gray-color-scheme-dark-mode-1.png and /dev/null differ diff --git a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_green-color-scheme-dark-mode-1.png b/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_green-color-scheme-dark-mode-1.png deleted file mode 100644 index 926ff7e591..0000000000 Binary files a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_green-color-scheme-dark-mode-1.png and /dev/null differ diff --git a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_orange-color-scheme-dark-mode-1.png b/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_orange-color-scheme-dark-mode-1.png deleted file mode 100644 index 3b8b6c4939..0000000000 Binary files a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_orange-color-scheme-dark-mode-1.png and /dev/null differ diff --git a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_purple-color-scheme-dark-mode-1.png b/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_purple-color-scheme-dark-mode-1.png deleted file mode 100644 index aec0669393..0000000000 Binary files a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_purple-color-scheme-dark-mode-1.png and /dev/null differ diff --git a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_teal-color-scheme-dark-mode-1.png b/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_teal-color-scheme-dark-mode-1.png deleted file mode 100644 index 8b713c96bc..0000000000 Binary files a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_teal-color-scheme-dark-mode-1.png and /dev/null differ diff --git a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_with-long-text-1.png b/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_with-long-text-1.png deleted file mode 100644 index 7ce1510b5b..0000000000 Binary files a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_with-long-text-1.png and /dev/null differ diff --git a/theme/components/Text.ts b/theme/components/Text.ts deleted file mode 100644 index 435330bc3b..0000000000 --- a/theme/components/Text.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { SystemStyleInterpolation } from '@chakra-ui/styled-system'; -import { defineStyle, defineStyleConfig } from '@chakra-ui/styled-system'; -import { mode } from '@chakra-ui/theme-tools'; - -const variantPrimary = defineStyle((props) => ({ - color: mode('blackAlpha.800', 'whiteAlpha.800')(props), -})); - -const variantSecondary = defineStyle((props) => ({ - color: mode('gray.500', 'gray.400')(props), -})); - -const variantInherit = { - color: 'inherit', -}; - -const variants: Record = { - primary: variantPrimary, - secondary: variantSecondary, - inherit: variantInherit, -}; - -const defaultProps = { - variant: 'primary', -}; - -const Text = defineStyleConfig({ - defaultProps, - variants, -}); - -export default Text; diff --git a/theme/components/Textarea.ts b/theme/components/Textarea.ts deleted file mode 100644 index bb0a00ca56..0000000000 --- a/theme/components/Textarea.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Textarea as TextareaComponent } from '@chakra-ui/react'; -import { defineStyle, defineStyleConfig } from '@chakra-ui/styled-system'; - -import getOutlinedFieldStyles from '../utils/getOutlinedFieldStyles'; - -const sizes = { - md: defineStyle({ - fontSize: 'md', - lineHeight: '20px', - h: '160px', - borderRadius: 'base', - }), - lg: defineStyle({ - fontSize: 'md', - lineHeight: '20px', - px: '24px', - py: '28px', - h: '160px', - borderRadius: 'base', - }), -}; - -const Textarea = defineStyleConfig({ - sizes, - variants: { - outline: defineStyle(getOutlinedFieldStyles), - }, - defaultProps: { - variant: 'outline', - }, -}); - -TextareaComponent.defaultProps = { - ...TextareaComponent.defaultProps, - placeholder: ' ', -}; - -export default Textarea; diff --git a/theme/components/Tooltip/Tooltip.pw.tsx b/theme/components/Tooltip/Tooltip.pw.tsx deleted file mode 100644 index e41255ca61..0000000000 --- a/theme/components/Tooltip/Tooltip.pw.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Box, Tooltip, Icon } from '@chakra-ui/react'; -import React from 'react'; - -import { test, expect } from 'playwright/lib'; - -test('base view +@dark-mode', async({ render, page }) => { - const component = await render( - - - trigger - - , - ); - - await component.getByText(/trigger/i).hover(); - - await expect(page).toHaveScreenshot({ clip: { x: 0, y: 40, width: 130, height: 64 } }); -}); - -// was not able to reproduce in tests issue when Icon is used as trigger for tooltip -// https://github.com/chakra-ui/chakra-ui/issues/7107 -test.fixme('with icon', async({ render, page }) => { - const component = await render( - - - - - - - , - ); - - const tooltip = page.getByText(/tooltip content/i); - await expect(tooltip).toBeHidden(); - - await component.locator('svg[aria-label="Trigger"]').hover(); - await expect(tooltip).toBeVisible(); -}); diff --git a/theme/components/Tooltip/Tooltip.ts b/theme/components/Tooltip/Tooltip.ts deleted file mode 100644 index d0bbae57e2..0000000000 --- a/theme/components/Tooltip/Tooltip.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Tooltip as TooltipComponent } from '@chakra-ui/react'; -import { defineStyle, defineStyleConfig } from '@chakra-ui/styled-system'; -import { mode, cssVar } from '@chakra-ui/theme-tools'; - -const $bg = cssVar('tooltip-bg'); -const $fg = cssVar('tooltip-fg'); -const $arrowBg = cssVar('popper-arrow-bg'); - -const variantNav = defineStyle((props) => { - return { - bg: mode('blue.50', 'gray.800')(props), - color: 'blue.400', - padding: '15px 12px', - minWidth: '120px', - borderRadius: 'base', - fontSize: '14px', - lineHeight: '20px', - textAlign: 'center', - boxShadow: 'none', - fontWeight: '500', - }; -}); - -const variants = { - nav: variantNav, -}; - -const baseStyle = defineStyle((props) => { - const bg = mode('gray.700', 'gray.200')(props); - const fg = mode('white', 'black')(props); - - return { - bg: $bg.reference, - color: $fg.reference, - [$bg.variable]: `colors.${ bg }`, - [$fg.variable]: `colors.${ fg }`, - [$arrowBg.variable]: $bg.reference, - maxWidth: props.maxWidth || props.maxW || 'calc(100vw - 8px)', - marginX: '4px', - }; -}); - -const Tooltip = defineStyleConfig({ - variants, - baseStyle, -}); - -TooltipComponent.defaultProps = { ...TooltipComponent.defaultProps, hasArrow: true }; - -export default Tooltip; diff --git a/theme/components/Tooltip/__screenshots__/Tooltip.pw.tsx_dark-color-mode_base-view-dark-mode-1.png b/theme/components/Tooltip/__screenshots__/Tooltip.pw.tsx_dark-color-mode_base-view-dark-mode-1.png deleted file mode 100644 index 8b61705f88..0000000000 Binary files a/theme/components/Tooltip/__screenshots__/Tooltip.pw.tsx_dark-color-mode_base-view-dark-mode-1.png and /dev/null differ diff --git a/theme/components/Tooltip/__screenshots__/Tooltip.pw.tsx_default_base-view-dark-mode-1.png b/theme/components/Tooltip/__screenshots__/Tooltip.pw.tsx_default_base-view-dark-mode-1.png deleted file mode 100644 index ab9a1ac7b9..0000000000 Binary files a/theme/components/Tooltip/__screenshots__/Tooltip.pw.tsx_default_base-view-dark-mode-1.png and /dev/null differ diff --git a/theme/components/index.ts b/theme/components/index.ts deleted file mode 100644 index 61903f55f6..0000000000 --- a/theme/components/index.ts +++ /dev/null @@ -1,55 +0,0 @@ -import Alert from './Alert/Alert'; -import Badge from './Badge'; -import Button from './Button/Button'; -import Checkbox from './Checkbox'; -import Drawer from './Drawer'; -import Form from './Form'; -import FormLabel from './FormLabel'; -import Heading from './Heading'; -import Input from './Input'; -import Link from './Link'; -import Menu from './Menu'; -import Modal from './Modal'; -import PinInput from './PinInput'; -import Popover from './Popover'; -import Radio from './Radio'; -import Select from './Select'; -import Skeleton from './Skeleton'; -import Spinner from './Spinner'; -import Switch from './Switch'; -import Table from './Table'; -import Tabs from './Tabs'; -import Tag from './Tag/Tag'; -import Text from './Text'; -import Textarea from './Textarea'; -import Tooltip from './Tooltip/Tooltip'; - -const components = { - Alert, - Badge, - Button, - Checkbox, - Drawer, - Heading, - Input, - Form, - FormLabel, - Link, - Menu, - Modal, - PinInput, - Popover, - Radio, - Select, - Skeleton, - Spinner, - Switch, - Tabs, - Table, - Tag, - Text, - Textarea, - Tooltip, -}; - -export default components; diff --git a/theme/config.ts b/theme/config.ts deleted file mode 100644 index ddca2b54ea..0000000000 --- a/theme/config.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { type ThemeConfig } from '@chakra-ui/react'; - -import appConfig from 'configs/app'; - -const config: ThemeConfig = { - initialColorMode: appConfig.UI.colorTheme.default?.colorMode ?? 'system', - useSystemColorMode: false, - disableTransitionOnChange: false, -}; - -export default config; diff --git a/theme/foundations/borders.ts b/theme/foundations/borders.ts deleted file mode 100644 index 4307c548c1..0000000000 --- a/theme/foundations/borders.ts +++ /dev/null @@ -1,13 +0,0 @@ -const borders = { - radii: { - none: '0', - sm: '4px', - base: '8px', - md: '12px', - lg: '16px', - xl: '24px', - full: '9999px', - }, -}; - -export default borders; diff --git a/theme/foundations/colors.ts b/theme/foundations/colors.ts deleted file mode 100644 index d1e3dae714..0000000000 --- a/theme/foundations/colors.ts +++ /dev/null @@ -1,66 +0,0 @@ -const colors = { - green: { - '100': '#C6F6D5', - '400': '#48BB78', - '500': '#38A169', - '600': '#25855A', - }, - blue: { - '50': '#EBF8FF', - '100': '#BEE3F8', - '200': '#90CDF4', - '300': '#63B3ED', - '400': '#4299E1', - '500': '#3182CE', - '600': '#2B6CB0', - '700': '#2C5282', - '800': '#2A4365', - '900': '#1A365D', - }, - red: { - '500': '#E53E3E', - '100': '#FED7D7', - }, - orange: { - '100': '#FEEBCB', - }, - gray: { - '50': '#F7FAFC', // <- - '100': '#EDF2F7', - '200': '#E2E8F0', - '300': '#CBD5E0', - '400': '#A0AEC0', - '500': '#718096', - '600': '#4A5568', - '700': '#2D3748', - '800': '#1A202C', - '900': '#171923', - }, - black: '#101112', - white: '#ffffff', - blackAlpha: { - '50': 'RGBA(16, 17, 18, 0.04)', - '100': 'RGBA(16, 17, 18, 0.06)', - '200': 'RGBA(16, 17, 18, 0.08)', - '300': 'RGBA(16, 17, 18, 0.16)', - '400': 'RGBA(16, 17, 18, 0.24)', - '500': 'RGBA(16, 17, 18, 0.36)', - '600': 'RGBA(16, 17, 18, 0.48)', - '700': 'RGBA(16, 17, 18, 0.64)', - '800': 'RGBA(16, 17, 18, 0.80)', - '900': 'RGBA(16, 17, 18, 0.92)', - }, - github: '#171923', - telegram: '#2775CA', - linkedin: '#1564BA', - discord: '#9747FF', - slack: '#1BA27A', - twitter: '#000000', - opensea: '#2081E2', - facebook: '#4460A0', - medium: '#231F20', - reddit: '#FF4500', - celo: '#FCFF52', -}; - -export default colors; diff --git a/theme/foundations/scrollbar.ts b/theme/foundations/scrollbar.ts deleted file mode 100644 index 3d9d68bf11..0000000000 --- a/theme/foundations/scrollbar.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { getCSSVar } from '@chakra-ui/styled-system'; -import { mode } from '@chakra-ui/theme-tools'; -import type { StyleFunctionProps } from '@chakra-ui/theme-tools'; - -const scrollbar = (props: StyleFunctionProps) => { - const bgColor = mode('blackAlpha.300', 'whiteAlpha.300')(props); - const resizerUrl = mode('url(/static/resizer_light.png)', 'url(/static/resizer_dark.png)')(props); - - return { - 'body *::-webkit-scrollbar': { - width: '20px', - }, - 'body *::-webkit-scrollbar-track': { - backgroundColor: 'transparent', - }, - 'body *::-webkit-scrollbar-thumb': { - backgroundColor: bgColor, - borderRadius: '20px', - border: `8px solid rgba(0,0,0,0)`, - backgroundClip: 'content-box', - minHeight: '32px', - }, - 'body *::-webkit-scrollbar-button': { - display: 'none', - }, - 'body *::-webkit-scrollbar-corner': { - backgroundColor: 'transparent', - }, - 'body *::-webkit-resizer': { - backgroundImage: resizerUrl, - backgroundSize: '20px', - }, - 'body *': { - scrollbarWidth: 'thin', - scrollbarColor: `${ getCSSVar(props.theme, 'colors', bgColor) } transparent`, - }, - }; -}; - -export default scrollbar; diff --git a/theme/foundations/semanticTokens.ts b/theme/foundations/semanticTokens.ts deleted file mode 100644 index 0c772a6539..0000000000 --- a/theme/foundations/semanticTokens.ts +++ /dev/null @@ -1,44 +0,0 @@ -const semanticTokens = { - colors: { - divider: { - 'default': 'blackAlpha.200', - _dark: 'whiteAlpha.200', - }, - text: { - 'default': 'blackAlpha.800', - _dark: 'whiteAlpha.800', - }, - text_secondary: { - 'default': 'gray.500', - _dark: 'gray.400', - }, - link: { - 'default': 'blue.600', - _dark: 'blue.300', - }, - link_hovered: { - 'default': 'blue.400', - }, - icon_link_external: { - 'default': 'gray.300', - _dark: 'gray.500', - }, - icon_info: { - 'default': 'gray.400', - _dark: 'gray.500', - }, - error: { - 'default': 'red.500', - _dark: 'red.500', - }, - dialog_bg: { - 'default': 'white', - _dark: 'gray.900', - }, - }, - shadows: { - action_bar: '0 4px 4px -4px rgb(0 0 0 / 10%), 0 2px 4px -4px rgb(0 0 0 / 6%)', - }, -}; - -export default semanticTokens; diff --git a/theme/foundations/transition.ts b/theme/foundations/transition.ts deleted file mode 100644 index b120e48137..0000000000 --- a/theme/foundations/transition.ts +++ /dev/null @@ -1,15 +0,0 @@ -const transitionDuration = { - 'ultra-fast': '50ms', - faster: '100ms', - fast: '150ms', - normal: '200ms', - slow: '300ms', - slower: '400ms', - 'ultra-slow': '500ms', -}; - -const transition = { - duration: transitionDuration, -}; - -export default transition; diff --git a/theme/foundations/typography.ts b/theme/foundations/typography.ts deleted file mode 100644 index b129403a93..0000000000 --- a/theme/foundations/typography.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { theme } from '@chakra-ui/react'; - -import config from 'configs/app'; - -export const BODY_TYPEFACE = config.UI.fonts.body?.name ?? 'Inter'; -export const HEADING_TYPEFACE = config.UI.fonts.heading?.name ?? 'Poppins'; - -const typography = { - fonts: { - heading: `${ HEADING_TYPEFACE }, ${ theme.fonts.heading }`, - body: `${ BODY_TYPEFACE }, ${ theme.fonts.body }`, - }, - textStyles: { - h2: { - fontSize: [ '32px' ], - fontWeight: '500', - lineHeight: '40px', - fontFamily: 'heading', - }, - h3: { - fontSize: '24px', - fontWeight: '500', - lineHeight: '32px', - fontFamily: 'heading', - }, - h4: { - fontSize: 'md', - fontWeight: '500', - lineHeight: '24px', - fontFamily: 'heading', - }, - }, -}; - -export default typography; diff --git a/theme/foundations/zIndices.ts b/theme/foundations/zIndices.ts deleted file mode 100644 index 84d6f039f8..0000000000 --- a/theme/foundations/zIndices.ts +++ /dev/null @@ -1,19 +0,0 @@ -const zIndices = { - hide: -1, - auto: 'auto', - base: 0, - docked: 10, - dropdown: 1000, - sticky: 1100, - sticky1: 1101, - sticky2: 1102, - banner: 1200, - overlay: 1300, - modal: 1400, - popover: 1500, - tooltip: 1550, // otherwise tooltips will not be visible in modals - skipLink: 1600, - toast: 1700, -}; - -export default zIndices; diff --git a/theme/global.ts b/theme/global.ts deleted file mode 100644 index acdbc2fecb..0000000000 --- a/theme/global.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { StyleFunctionProps } from '@chakra-ui/theme-tools'; -import { mode } from '@chakra-ui/theme-tools'; - -import scrollbar from './foundations/scrollbar'; -import addressEntity from './globals/address-entity'; -import recaptcha from './globals/recaptcha'; -import getDefaultTransitionProps from './utils/getDefaultTransitionProps'; - -const global = (props: StyleFunctionProps) => ({ - body: { - bg: mode('white', 'black')(props), - ...getDefaultTransitionProps(), - '-webkit-tap-highlight-color': 'transparent', - 'font-variant-ligatures': 'no-contextual', - }, - mark: { - bgColor: mode('green.100', 'green.800')(props), - color: 'inherit', - }, - 'svg *::selection': { - color: 'none', - background: 'none', - }, - form: { - w: '100%', - }, - ...scrollbar(props), - ...addressEntity(props), - ...recaptcha(), -}); - -export default global; diff --git a/theme/globals/address-entity.ts b/theme/globals/address-entity.ts deleted file mode 100644 index 25641c3020..0000000000 --- a/theme/globals/address-entity.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { mode } from '@chakra-ui/theme-tools'; -import type { StyleFunctionProps } from '@chakra-ui/theme-tools'; - -const styles = (props: StyleFunctionProps) => { - return { - '.address-entity': { - '&.address-entity_highlighted': { - _before: { - content: `" "`, - position: 'absolute', - py: 1, - pl: 1, - pr: 0, - top: '-5px', - left: '-5px', - width: `100%`, - height: '100%', - borderRadius: 'base', - borderColor: mode('blue.200', 'blue.600')(props), - borderWidth: '1px', - borderStyle: 'dashed', - bgColor: mode('blue.50', 'blue.900')(props), - zIndex: -1, - }, - }, - }, - '.address-entity_no-copy': { - '&.address-entity_highlighted': { - _before: { - pr: 2, - }, - }, - }, - }; -}; - -export default styles; diff --git a/theme/globals/recaptcha.ts b/theme/globals/recaptcha.ts deleted file mode 100644 index c551eb4ae2..0000000000 --- a/theme/globals/recaptcha.ts +++ /dev/null @@ -1,22 +0,0 @@ -const styles = () => { - return { - '.grecaptcha-badge': { - visibility: 'hidden', - }, - 'div:has(div):has(iframe[title="recaptcha challenge expires in two minutes"])': { - '&::after': { - content: `" "`, - display: 'block', - position: 'fixed', - top: 0, - left: 0, - width: '100vw', - height: '100vh', - zIndex: 100000, - bgColor: 'blackAlpha.300', - }, - }, - }; -}; - -export default styles; diff --git a/theme/package.json b/theme/package.json deleted file mode 100644 index 9f4fe3a558..0000000000 --- a/theme/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "@blockscout/chakra-theme", - "version": "1.32.0", - "main": "./dist/index.js", - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/blockscout/frontend.git", - "directory": "theme" - }, - "scripts": { - "build": "yarn webpack-cli -c ./webpack.config.js" - }, - "devDependencies": { - "typescript": "5.4.2", - "ts-loader": "^9.4.4", - "webpack": "^5.94.0", - "webpack-cli": "^5.1.4", - "tsconfig-paths-webpack-plugin": "^4.1.0" - }, - "files": [ - "/dist" - ] -} diff --git a/theme/theme.ts b/theme/theme.ts deleted file mode 100644 index 7d391fe6d6..0000000000 --- a/theme/theme.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { extendTheme } from '@chakra-ui/react'; - -import components from './components/index'; -import config from './config'; -import borders from './foundations/borders'; -import breakpoints from './foundations/breakpoints'; -import colors from './foundations/colors'; -import semanticTokens from './foundations/semanticTokens'; -import transition from './foundations/transition'; -import typography from './foundations/typography'; -import zIndices from './foundations/zIndices'; -import global from './global'; - -const overrides = { - ...typography, - ...borders, - colors, - components, - config, - styles: { - global, - }, - breakpoints, - transition, - zIndices, - semanticTokens, -}; - -export default extendTheme(overrides); diff --git a/theme/tsconfig.json b/theme/tsconfig.json deleted file mode 100644 index 153821f077..0000000000 --- a/theme/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "noEmit": false, - "target": "es2016", - "module": "CommonJS", - "moduleResolution": "node", - "paths": { - "nextjs-routes": ["./nextjs/nextjs-routes.d.ts"], - } - }, - "include": [ - "../types/**/*.ts", - "../configs/app/**/*.ts", - "../global.d.ts", - "./theme.ts", - "./components/**/*.pw.tsx", - ], - "tsc-alias": { - "verbose": true, - "resolveFullPaths": true, - } -} - \ No newline at end of file diff --git a/theme/utils/getDefaultTransitionProps.ts b/theme/utils/getDefaultTransitionProps.ts deleted file mode 100644 index 2f7d60e7a7..0000000000 --- a/theme/utils/getDefaultTransitionProps.ts +++ /dev/null @@ -1,7 +0,0 @@ -export default function getDefaultTransitionProps(props?: { transitionProperty: string }) { - return { - transitionProperty: `background-color, color, border-color${ props?.transitionProperty ? ', ' + props.transitionProperty : '' }`, - transitionDuration: 'normal', - transitionTimingFunction: 'ease', - }; -} diff --git a/theme/utils/getFormStyles.ts b/theme/utils/getFormStyles.ts deleted file mode 100644 index 5dda16db55..0000000000 --- a/theme/utils/getFormStyles.ts +++ /dev/null @@ -1,55 +0,0 @@ -import type { StyleFunctionProps } from '@chakra-ui/theme-tools'; -import { mode, transparentize } from '@chakra-ui/theme-tools'; - -export default function getFormStyles(props: StyleFunctionProps) { - return { - input: { - empty: { - // there is no text in the empty input - // color: ???, - bgColor: props.bgColor || mode('white', 'black')(props), - borderColor: mode('gray.100', 'gray.700')(props), - }, - hover: { - color: mode('gray.800', 'gray.50')(props), - bgColor: props.bgColor || mode('white', 'black')(props), - borderColor: mode('gray.200', 'gray.500')(props), - }, - focus: { - color: mode('gray.800', 'gray.50')(props), - bgColor: props.bgColor || mode('white', 'black')(props), - borderColor: mode('blue.400', 'blue.400')(props), - }, - filled: { - color: mode('gray.800', 'gray.50')(props), - bgColor: props.bgColor || mode('white', 'black')(props), - borderColor: mode('gray.300', 'gray.600')(props), - }, - readOnly: { - color: mode('gray.800', 'gray.50')(props), - bgColor: mode('gray.200', 'gray.800')(props), - borderColor: mode('gray.200', 'gray.800')(props), - }, - // we use opacity to show the disabled state - disabled: { - opacity: 0.2, - }, - error: { - color: mode('gray.800', 'gray.50')(props), - bgColor: props.bgColor || mode('white', 'black')(props), - borderColor: mode('red.500', 'red.500')(props), - }, - }, - placeholder: { - 'default': { - color: mode('gray.500', 'gray.500')(props), - }, - disabled: { - color: transparentize('gray.500', 0.2)(props.theme), - }, - error: { - color: mode('red.500', 'red.500')(props), - }, - }, - }; -} diff --git a/theme/utils/getOutlinedFieldStyles.ts b/theme/utils/getOutlinedFieldStyles.ts deleted file mode 100644 index c26ed15d11..0000000000 --- a/theme/utils/getOutlinedFieldStyles.ts +++ /dev/null @@ -1,68 +0,0 @@ -import type { StyleFunctionProps } from '@chakra-ui/theme-tools'; -import { mode } from '@chakra-ui/theme-tools'; - -import getDefaultTransitionProps from './getDefaultTransitionProps'; -import getFormStyles from './getFormStyles'; - -export default function getOutlinedFieldStyles(props: StyleFunctionProps) { - const formStyles = getFormStyles(props); - const transitionProps = getDefaultTransitionProps(); - - return { - border: '2px solid', - // filled input - ...formStyles.input.filled, - ...transitionProps, - _hover: { - ...formStyles.input.hover, - }, - _readOnly: { - boxShadow: 'none !important', - userSelect: 'all', - pointerEvents: 'none', - ...formStyles.input.readOnly, - _hover: { - ...formStyles.input.readOnly, - }, - _focus: { - ...formStyles.input.readOnly, - }, - }, - _disabled: { - ...formStyles.input.disabled, - cursor: 'not-allowed', - ':-webkit-autofill': { - // background color for disabled input which value was selected from browser autocomplete popup - '-webkit-box-shadow': `0 0 0px 1000px ${ mode('rgba(16, 17, 18, 0.08)', 'rgba(255, 255, 255, 0.08)')(props) } inset`, - }, - }, - _invalid: { - ...formStyles.input.error, - boxShadow: `none`, - _placeholder: { - color: formStyles.placeholder.error.color, - }, - }, - _focusVisible: { - ...formStyles.input.focus, - zIndex: 1, - boxShadow: 'md', - }, - _placeholder: { - color: formStyles.placeholder.default.color, - }, - // not filled input - ':placeholder-shown:not(:focus-visible):not(:hover):not([aria-invalid=true]):not([aria-readonly=true])': { - ...formStyles.input.empty, - }, - - // not filled input with type="date" - ':not(:placeholder-shown)[value=""]:not(:focus-visible):not(:hover):not([aria-invalid=true]):not([aria-readonly=true])': { - ...formStyles.input.empty, - }, - - ':-webkit-autofill': { transition: 'background-color 5000s ease-in-out 0s' }, - ':-webkit-autofill:hover': { transition: 'background-color 5000s ease-in-out 0s' }, - ':-webkit-autofill:focus': { transition: 'background-color 5000s ease-in-out 0s' }, - }; -} diff --git a/theme/webpack.config.js b/theme/webpack.config.js deleted file mode 100644 index 146517ea3e..0000000000 --- a/theme/webpack.config.js +++ /dev/null @@ -1,32 +0,0 @@ -const path = require('path'); -const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); - -const config = { - mode: 'production', - entry: path.resolve(__dirname) + '/theme.ts', - module: { - rules: [ - { - test: /\.tsx?$/, - use: 'ts-loader', - exclude: /node_modules/, - }, - ], - }, - resolve: { - extensions: [ '.tsx', '.ts', '.js' ], - plugins: [ new TsconfigPathsPlugin({ configFile: './tsconfig.json' }) ], - }, - output: { - filename: 'index.js', - path: path.resolve(__dirname) + '/dist', - library: { - type: 'commonjs', - }, - }, - optimization: { - minimize: false, - }, -}; - -module.exports = config; diff --git a/theme/yarn.lock b/theme/yarn.lock deleted file mode 100644 index baa25d2dd7..0000000000 --- a/theme/yarn.lock +++ /dev/null @@ -1,918 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@discoveryjs/json-ext@^0.5.0": - version "0.5.7" - resolved "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz" - integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== - -"@jridgewell/gen-mapping@^0.3.5": - version "0.3.5" - resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz" - integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== - dependencies: - "@jridgewell/set-array" "^1.2.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.24" - -"@jridgewell/resolve-uri@^3.1.0": - version "3.1.2" - resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz" - integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== - -"@jridgewell/set-array@^1.2.1": - version "1.2.1" - resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz" - integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== - -"@jridgewell/source-map@^0.3.3": - version "0.3.6" - resolved "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz" - integrity sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": - version "1.5.0" - resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz" - integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== - -"@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": - version "0.3.25" - resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz" - integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@types/estree@^1.0.5": - version "1.0.5" - resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz" - integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== - -"@types/json-schema@^7.0.8": - version "7.0.15" - resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz" - integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== - -"@types/node@*": - version "20.14.11" - resolved "https://registry.npmjs.org/@types/node/-/node-20.14.11.tgz" - integrity sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA== - dependencies: - undici-types "~5.26.4" - -"@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.12.1": - version "1.12.1" - resolved "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz" - integrity sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg== - dependencies: - "@webassemblyjs/helper-numbers" "1.11.6" - "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - -"@webassemblyjs/floating-point-hex-parser@1.11.6": - version "1.11.6" - resolved "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz" - integrity sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw== - -"@webassemblyjs/helper-api-error@1.11.6": - version "1.11.6" - resolved "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz" - integrity sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q== - -"@webassemblyjs/helper-buffer@1.12.1": - version "1.12.1" - resolved "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz" - integrity sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw== - -"@webassemblyjs/helper-numbers@1.11.6": - version "1.11.6" - resolved "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz" - integrity sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g== - dependencies: - "@webassemblyjs/floating-point-hex-parser" "1.11.6" - "@webassemblyjs/helper-api-error" "1.11.6" - "@xtuc/long" "4.2.2" - -"@webassemblyjs/helper-wasm-bytecode@1.11.6": - version "1.11.6" - resolved "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz" - integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA== - -"@webassemblyjs/helper-wasm-section@1.12.1": - version "1.12.1" - resolved "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz" - integrity sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g== - dependencies: - "@webassemblyjs/ast" "1.12.1" - "@webassemblyjs/helper-buffer" "1.12.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/wasm-gen" "1.12.1" - -"@webassemblyjs/ieee754@1.11.6": - version "1.11.6" - resolved "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz" - integrity sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg== - dependencies: - "@xtuc/ieee754" "^1.2.0" - -"@webassemblyjs/leb128@1.11.6": - version "1.11.6" - resolved "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz" - integrity sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ== - dependencies: - "@xtuc/long" "4.2.2" - -"@webassemblyjs/utf8@1.11.6": - version "1.11.6" - resolved "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz" - integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA== - -"@webassemblyjs/wasm-edit@^1.12.1": - version "1.12.1" - resolved "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz" - integrity sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g== - dependencies: - "@webassemblyjs/ast" "1.12.1" - "@webassemblyjs/helper-buffer" "1.12.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/helper-wasm-section" "1.12.1" - "@webassemblyjs/wasm-gen" "1.12.1" - "@webassemblyjs/wasm-opt" "1.12.1" - "@webassemblyjs/wasm-parser" "1.12.1" - "@webassemblyjs/wast-printer" "1.12.1" - -"@webassemblyjs/wasm-gen@1.12.1": - version "1.12.1" - resolved "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz" - integrity sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w== - dependencies: - "@webassemblyjs/ast" "1.12.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/ieee754" "1.11.6" - "@webassemblyjs/leb128" "1.11.6" - "@webassemblyjs/utf8" "1.11.6" - -"@webassemblyjs/wasm-opt@1.12.1": - version "1.12.1" - resolved "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz" - integrity sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg== - dependencies: - "@webassemblyjs/ast" "1.12.1" - "@webassemblyjs/helper-buffer" "1.12.1" - "@webassemblyjs/wasm-gen" "1.12.1" - "@webassemblyjs/wasm-parser" "1.12.1" - -"@webassemblyjs/wasm-parser@1.12.1", "@webassemblyjs/wasm-parser@^1.12.1": - version "1.12.1" - resolved "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz" - integrity sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ== - dependencies: - "@webassemblyjs/ast" "1.12.1" - "@webassemblyjs/helper-api-error" "1.11.6" - "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/ieee754" "1.11.6" - "@webassemblyjs/leb128" "1.11.6" - "@webassemblyjs/utf8" "1.11.6" - -"@webassemblyjs/wast-printer@1.12.1": - version "1.12.1" - resolved "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz" - integrity sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA== - dependencies: - "@webassemblyjs/ast" "1.12.1" - "@xtuc/long" "4.2.2" - -"@webpack-cli/configtest@^2.1.1": - version "2.1.1" - resolved "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz" - integrity sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw== - -"@webpack-cli/info@^2.0.2": - version "2.0.2" - resolved "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz" - integrity sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A== - -"@webpack-cli/serve@^2.0.5": - version "2.0.5" - resolved "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz" - integrity sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ== - -"@xtuc/ieee754@^1.2.0": - version "1.2.0" - resolved "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz" - integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== - -"@xtuc/long@4.2.2": - version "4.2.2" - resolved "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz" - integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== - -acorn-import-attributes@^1.9.5: - version "1.9.5" - resolved "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz" - integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== - -acorn@^8.7.1, acorn@^8.8.2: - version "8.12.1" - resolved "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz" - integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== - -ajv-keywords@^3.5.2: - version "3.5.2" - resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz" - integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== - -ajv@^6.12.5: - version "6.12.6" - resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - 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" - -ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -braces@^3.0.3: - version "3.0.3" - resolved "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz" - integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== - dependencies: - fill-range "^7.1.1" - -browserslist@^4.21.10: - version "4.23.2" - resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz" - integrity sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA== - dependencies: - caniuse-lite "^1.0.30001640" - electron-to-chromium "^1.4.820" - node-releases "^2.0.14" - update-browserslist-db "^1.1.0" - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -caniuse-lite@^1.0.30001640: - version "1.0.30001643" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz" - integrity sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg== - -chalk@^4.1.0: - version "4.1.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chrome-trace-event@^1.0.2: - version "1.0.4" - resolved "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz" - integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ== - -clone-deep@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz" - integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== - dependencies: - is-plain-object "^2.0.4" - kind-of "^6.0.2" - shallow-clone "^3.0.0" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -colorette@^2.0.14: - version "2.0.20" - resolved "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz" - integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== - -commander@^10.0.1: - version "10.0.1" - resolved "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz" - integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== - -commander@^2.20.0: - version "2.20.3" - resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -cross-spawn@^7.0.3: - version "7.0.6" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" - integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -electron-to-chromium@^1.4.820: - version "1.5.0" - resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.0.tgz" - integrity sha512-Vb3xHHYnLseK8vlMJQKJYXJ++t4u1/qJ3vykuVrVjvdiOEhYyT1AuP4x03G8EnPmYvYOhe9T+dADTmthjRQMkA== - -enhanced-resolve@^5.0.0, enhanced-resolve@^5.17.1, enhanced-resolve@^5.7.0: - version "5.17.1" - resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz" - integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg== - dependencies: - graceful-fs "^4.2.4" - tapable "^2.2.0" - -envinfo@^7.7.3: - version "7.13.0" - resolved "https://registry.npmjs.org/envinfo/-/envinfo-7.13.0.tgz" - integrity sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q== - -es-module-lexer@^1.2.1: - version "1.5.4" - resolved "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz" - integrity sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw== - -escalade@^3.1.2: - version "3.1.2" - resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz" - integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== - -eslint-scope@5.1.1: - version "5.1.1" - resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -events@^3.2.0: - version "3.3.0" - resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz" - integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== - -fast-deep-equal@^3.1.1: - version "3.1.3" - resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fastest-levenshtein@^1.0.12: - version "1.0.16" - resolved "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz" - integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== - -fill-range@^7.1.1: - version "7.1.1" - resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz" - integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== - dependencies: - to-regex-range "^5.0.1" - -find-up@^4.0.0: - version "4.1.0" - resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -flat@^5.0.2: - version "5.0.2" - resolved "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz" - integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== - -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - -glob-to-regexp@^0.4.1: - version "0.4.1" - resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz" - integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== - -graceful-fs@^4.1.2, graceful-fs@^4.2.11, graceful-fs@^4.2.4: - version "4.2.11" - resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -hasown@^2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== - dependencies: - function-bind "^1.1.2" - -import-local@^3.0.2: - version "3.2.0" - resolved "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz" - integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== - dependencies: - pkg-dir "^4.2.0" - resolve-cwd "^3.0.0" - -interpret@^3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz" - integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== - -is-core-module@^2.13.0: - version "2.15.0" - resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz" - integrity sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA== - dependencies: - hasown "^2.0.2" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz" - integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== - -jest-worker@^27.4.5: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz" - integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -json-parse-even-better-errors@^2.3.1: - version "2.3.1" - resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json5@^2.2.2: - version "2.2.3" - resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - -kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -loader-runner@^4.2.0: - version "4.3.0" - resolved "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz" - integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -micromatch@^4.0.0: - version "4.0.8" - resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz" - integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== - dependencies: - braces "^3.0.3" - picomatch "^2.3.1" - -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.27: - version "2.1.35" - resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -minimist@^1.2.6: - version "1.2.8" - resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== - -neo-async@^2.6.2: - version "2.6.2" - resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz" - integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== - -node-releases@^2.0.14: - version "2.0.18" - resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz" - integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -picocolors@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz" - integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== - -picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -pkg-dir@^4.2.0: - version "4.2.0" - resolved "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - -punycode@^2.1.0: - version "2.3.1" - resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" - integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== - -randombytes@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - -rechoir@^0.8.0: - version "0.8.0" - resolved "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz" - integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ== - dependencies: - resolve "^1.20.0" - -resolve-cwd@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz" - integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== - dependencies: - resolve-from "^5.0.0" - -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - -resolve@^1.20.0: - version "1.22.8" - resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz" - integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== - dependencies: - is-core-module "^2.13.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -safe-buffer@^5.1.0: - version "5.2.1" - resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -schema-utils@^3.1.1, schema-utils@^3.2.0: - version "3.3.0" - resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz" - integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== - dependencies: - "@types/json-schema" "^7.0.8" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - -semver@^7.3.4: - version "7.6.3" - resolved "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz" - integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== - -serialize-javascript@^6.0.1: - version "6.0.2" - resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz" - integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== - dependencies: - randombytes "^2.1.0" - -shallow-clone@^3.0.0: - version "3.0.1" - resolved "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz" - integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== - dependencies: - kind-of "^6.0.2" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -source-map-support@~0.5.20: - version "0.5.21" - resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0: - version "0.6.1" - resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -source-map@^0.7.4: - version "0.7.4" - resolved "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz" - integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== - -strip-bom@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz" - integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -tapable@^2.1.1, tapable@^2.2.0: - version "2.2.1" - resolved "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz" - integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== - -terser-webpack-plugin@^5.3.10: - version "5.3.10" - resolved "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz" - integrity sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w== - dependencies: - "@jridgewell/trace-mapping" "^0.3.20" - jest-worker "^27.4.5" - schema-utils "^3.1.1" - serialize-javascript "^6.0.1" - terser "^5.26.0" - -terser@^5.26.0: - version "5.31.3" - resolved "https://registry.npmjs.org/terser/-/terser-5.31.3.tgz" - integrity sha512-pAfYn3NIZLyZpa83ZKigvj6Rn9c/vd5KfYGX7cN1mnzqgDcxWvrU5ZtAfIKhEXz9nRecw4z3LXkjaq96/qZqAA== - dependencies: - "@jridgewell/source-map" "^0.3.3" - acorn "^8.8.2" - commander "^2.20.0" - source-map-support "~0.5.20" - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -ts-loader@^9.4.4: - version "9.5.1" - resolved "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz" - integrity sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg== - dependencies: - chalk "^4.1.0" - enhanced-resolve "^5.0.0" - micromatch "^4.0.0" - semver "^7.3.4" - source-map "^0.7.4" - -tsconfig-paths-webpack-plugin@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.1.0.tgz" - integrity sha512-xWFISjviPydmtmgeUAuXp4N1fky+VCtfhOkDUFIv5ea7p4wuTomI4QTrXvFBX2S4jZsmyTSrStQl+E+4w+RzxA== - dependencies: - chalk "^4.1.0" - enhanced-resolve "^5.7.0" - tsconfig-paths "^4.1.2" - -tsconfig-paths@^4.1.2: - version "4.2.0" - resolved "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz" - integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== - dependencies: - json5 "^2.2.2" - minimist "^1.2.6" - strip-bom "^3.0.0" - -typescript@5.4.2: - version "5.4.2" - resolved "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz" - integrity sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ== - -undici-types@~5.26.4: - version "5.26.5" - resolved "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz" - integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== - -update-browserslist-db@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz" - integrity sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ== - dependencies: - escalade "^3.1.2" - picocolors "^1.0.1" - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -watchpack@^2.4.1: - version "2.4.1" - resolved "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz" - integrity sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg== - dependencies: - glob-to-regexp "^0.4.1" - graceful-fs "^4.1.2" - -webpack-cli@^5.1.4: - version "5.1.4" - resolved "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz" - integrity sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg== - dependencies: - "@discoveryjs/json-ext" "^0.5.0" - "@webpack-cli/configtest" "^2.1.1" - "@webpack-cli/info" "^2.0.2" - "@webpack-cli/serve" "^2.0.5" - colorette "^2.0.14" - commander "^10.0.1" - cross-spawn "^7.0.3" - envinfo "^7.7.3" - fastest-levenshtein "^1.0.12" - import-local "^3.0.2" - interpret "^3.1.1" - rechoir "^0.8.0" - webpack-merge "^5.7.3" - -webpack-merge@^5.7.3: - version "5.10.0" - resolved "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz" - integrity sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA== - dependencies: - clone-deep "^4.0.1" - flat "^5.0.2" - wildcard "^2.0.0" - -webpack-sources@^3.2.3: - version "3.2.3" - resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz" - integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== - -webpack@^5.94.0: - version "5.94.0" - resolved "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz" - integrity sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg== - dependencies: - "@types/estree" "^1.0.5" - "@webassemblyjs/ast" "^1.12.1" - "@webassemblyjs/wasm-edit" "^1.12.1" - "@webassemblyjs/wasm-parser" "^1.12.1" - acorn "^8.7.1" - acorn-import-attributes "^1.9.5" - browserslist "^4.21.10" - chrome-trace-event "^1.0.2" - enhanced-resolve "^5.17.1" - es-module-lexer "^1.2.1" - eslint-scope "5.1.1" - events "^3.2.0" - glob-to-regexp "^0.4.1" - graceful-fs "^4.2.11" - json-parse-even-better-errors "^2.3.1" - loader-runner "^4.2.0" - mime-types "^2.1.27" - neo-async "^2.6.2" - schema-utils "^3.2.0" - tapable "^2.1.1" - terser-webpack-plugin "^5.3.10" - watchpack "^2.4.1" - webpack-sources "^3.2.3" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -wildcard@^2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz" - integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== diff --git a/toolkit/chakra/accordion.tsx b/toolkit/chakra/accordion.tsx new file mode 100644 index 0000000000..55d926ea52 --- /dev/null +++ b/toolkit/chakra/accordion.tsx @@ -0,0 +1,91 @@ +import { Accordion } from '@chakra-ui/react'; +import * as React from 'react'; + +import IconSvg from 'ui/shared/IconSvg'; + +interface AccordionItemTriggerProps extends Accordion.ItemTriggerProps { + indicatorPlacement?: 'start' | 'end'; + noIndicator?: boolean; + variant?: Accordion.RootProps['variant']; +} + +export const AccordionItemTrigger = React.forwardRef< + HTMLButtonElement, + AccordionItemTriggerProps +>(function AccordionItemTrigger(props, ref) { + const { children, indicatorPlacement: indicatorPlacementProp, variant, noIndicator, ...rest } = props; + + const indicatorPlacement = variant === 'faq' ? 'start' : (indicatorPlacementProp ?? 'end'); + + const indicator = variant === 'faq' ? ( + +
+ + ) : ( + + + + ); + + return ( + + { indicatorPlacement === 'start' && !noIndicator && indicator } + { children } + { indicatorPlacement === 'end' && !noIndicator && indicator } + + ); +}); + +interface AccordionItemContentProps extends Accordion.ItemContentProps {} + +export const AccordionItemContent = React.forwardRef< + HTMLDivElement, + AccordionItemContentProps +>(function AccordionItemContent(props, ref) { + return ( + + + + ); +}); + +export const AccordionRoot = (props: Accordion.RootProps) => { + const { multiple = true, ...rest } = props; + return ; +}; + +export const AccordionItem = Accordion.Item; diff --git a/toolkit/chakra/alert.tsx b/toolkit/chakra/alert.tsx new file mode 100644 index 0000000000..bb509d3265 --- /dev/null +++ b/toolkit/chakra/alert.tsx @@ -0,0 +1,88 @@ +import type { AlertDescriptionProps } from '@chakra-ui/react'; +import { Alert as ChakraAlert } from '@chakra-ui/react'; +import * as React from 'react'; + +import IconSvg from 'ui/shared/IconSvg'; + +import { CloseButton } from './close-button'; +import { Skeleton } from './skeleton'; + +export interface AlertProps extends Omit { + startElement?: React.ReactNode; + endElement?: React.ReactNode; + descriptionProps?: AlertDescriptionProps; + title?: React.ReactNode; + icon?: React.ReactElement; + closable?: boolean; + onClose?: () => void; + loading?: boolean; + showIcon?: boolean; +} + +export const Alert = React.forwardRef( + function Alert(props, ref) { + const { + title, + children, + icon, + closable, + onClose, + startElement, + endElement, + loading, + showIcon = false, + descriptionProps, + ...rest + } = props; + + const [ isOpen, setIsOpen ] = React.useState(true); + + const defaultIcon = ; + + const iconElement = (() => { + if (startElement !== undefined) { + return startElement; + } + + if (!showIcon && icon === undefined) { + return null; + } + + return { icon || defaultIcon }; + })(); + + const handleClose = React.useCallback(() => { + setIsOpen(false); + onClose?.(); + }, [ onClose ]); + + if (closable && !isOpen) { + return null; + } + + return ( + + + { iconElement } + { children ? ( + + { title && { title } } + { children } + + ) : ( + { title } + ) } + { endElement } + { closable && ( + + ) } + + + ); + }, +); diff --git a/toolkit/chakra/avatar.tsx b/toolkit/chakra/avatar.tsx new file mode 100644 index 0000000000..9d323b2e8d --- /dev/null +++ b/toolkit/chakra/avatar.tsx @@ -0,0 +1,74 @@ +'use client'; + +import type { GroupProps, SlotRecipeProps } from '@chakra-ui/react'; +import { Avatar as ChakraAvatar, Group } from '@chakra-ui/react'; +import * as React from 'react'; + +type ImageProps = React.ImgHTMLAttributes; + +export interface AvatarProps extends ChakraAvatar.RootProps { + name?: string; + src?: string; + srcSet?: string; + loading?: ImageProps['loading']; + icon?: React.ReactElement; + fallback?: React.ReactNode; +} + +export const Avatar = React.forwardRef( + function Avatar(props, ref) { + const { name, src, srcSet, loading, icon, fallback, children, ...rest } = + props; + return ( + + + { fallback } + + + { children } + + ); + }, +); + +interface AvatarFallbackProps extends ChakraAvatar.FallbackProps { + name?: string; + icon?: React.ReactElement; +} + +const AvatarFallback = React.forwardRef( + function AvatarFallback(props, ref) { + const { name, icon, children, ...rest } = props; + return ( + + { children } + { name != null && children == null && <>{ getInitials(name) } } + { name == null && children == null && ( + { icon } + ) } + + ); + }, +); + +function getInitials(name: string) { + const names = name.trim().split(' '); + const firstName = names[0] != null ? names[0] : ''; + const lastName = names.length > 1 ? names[names.length - 1] : ''; + return firstName && lastName ? + `${ firstName.charAt(0) }${ lastName.charAt(0) }` : + firstName.charAt(0); +} + +interface AvatarGroupProps extends GroupProps, SlotRecipeProps<'avatar'> {} + +export const AvatarGroup = React.forwardRef( + function AvatarGroup(props, ref) { + const { size, variant, borderless, ...rest } = props; + return ( + + + + ); + }, +); diff --git a/toolkit/chakra/badge.tsx b/toolkit/chakra/badge.tsx new file mode 100644 index 0000000000..868866b0fc --- /dev/null +++ b/toolkit/chakra/badge.tsx @@ -0,0 +1,39 @@ +import type { BadgeProps as ChakraBadgeProps } from '@chakra-ui/react'; +import { chakra, Badge as ChakraBadge } from '@chakra-ui/react'; +import React from 'react'; + +import type { IconName } from 'ui/shared/IconSvg'; +import IconSvg from 'ui/shared/IconSvg'; +import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip'; + +import { Skeleton } from './skeleton'; + +export interface BadgeProps extends Omit { + loading?: boolean; + iconStart?: IconName; + endElement?: React.ReactNode; + truncated?: boolean; +} + +export const Badge = React.forwardRef( + function Badge(props, ref) { + const { loading, iconStart, children, asChild = true, truncated = false, endElement, ...rest } = props; + + const child = { children }; + + const childrenElement = truncated ? ( + + { child } + + ) : child; + + return ( + + + { iconStart && } + { childrenElement } + { endElement } + + + ); + }); diff --git a/toolkit/chakra/button.tsx b/toolkit/chakra/button.tsx new file mode 100644 index 0000000000..91cb0dc63a --- /dev/null +++ b/toolkit/chakra/button.tsx @@ -0,0 +1,134 @@ +import type { ButtonProps as ChakraButtonProps, ButtonGroupProps as ChakraButtonGroupProps } from '@chakra-ui/react'; +import { + AbsoluteCenter, + Button as ChakraButton, + ButtonGroup as ChakraButtonGroup, + Span, + Spinner, +} from '@chakra-ui/react'; +import * as React from 'react'; + +import { Skeleton } from './skeleton'; + +interface ButtonLoadingProps { + loading?: boolean; + loadingText?: React.ReactNode; + loadingSkeleton?: boolean; +} + +export interface ButtonProps extends ChakraButtonProps, ButtonLoadingProps { + expanded?: boolean; + selected?: boolean; + highlighted?: boolean; +} + +export const Button = React.forwardRef( + function Button(props, ref) { + const { loading, disabled, loadingText, children, expanded, selected, highlighted, loadingSkeleton = false, ...rest } = props; + + const content = (() => { + if (loading && !loadingText) { + return ( + <> + + + + { children } + + ); + } + + if (loading && loadingText) { + return ( + <> + + { loadingText } + + ); + } + + return children; + })(); + + return ( + }> + + { content } + + + ); + }, +); + +export interface ButtonGroupProps extends ChakraButtonGroupProps {} + +export const ButtonGroup = React.forwardRef( + function ButtonGroup(props, ref) { + const { ...rest } = props; + + return ( + + ); + }, +); + +export interface ButtonGroupRadioProps extends Omit { + children: Array>; + onChange?: (value: string) => void; + defaultValue?: string; + loading?: boolean; + equalWidth?: boolean; +} + +export const ButtonGroupRadio = React.forwardRef( + function ButtonGroupRadio(props, ref) { + const { children, onChange, variant = 'segmented', defaultValue, loading = false, equalWidth = false, ...rest } = props; + + const firstChildValue = React.useMemo(() => { + const firstChild = Array.isArray(children) ? children[0] : undefined; + return typeof firstChild?.props.value === 'string' ? firstChild.props.value : undefined; + }, [ children ]); + + const [ value, setValue ] = React.useState(defaultValue ?? firstChildValue); + + const handleItemClick = React.useCallback((event: React.MouseEvent) => { + const value = event.currentTarget.value; + setValue(value); + onChange?.(value); + }, [ onChange ]); + + const clonedChildren = React.Children.map(children, (child: React.ReactElement) => { + return React.cloneElement(child, { + onClick: handleItemClick, + selected: value === child.props.value, + variant, + }); + }); + + const childrenLength = React.Children.count(children); + + return ( + + + { clonedChildren } + + + ); + }, +); diff --git a/toolkit/chakra/checkbox.tsx b/toolkit/chakra/checkbox.tsx new file mode 100644 index 0000000000..ed01adff12 --- /dev/null +++ b/toolkit/chakra/checkbox.tsx @@ -0,0 +1,49 @@ +import type { Checkbox as ArkCheckbox } from '@ark-ui/react/checkbox'; +import type { HTMLChakraProps } from '@chakra-ui/react'; +import { Checkbox as ChakraCheckbox, CheckboxGroup as ChakraCheckboxGroup } from '@chakra-ui/react'; +import * as React from 'react'; + +export interface CheckboxProps extends ChakraCheckbox.RootProps { + icon?: React.ReactNode; + inputProps?: React.InputHTMLAttributes; + rootRef?: React.Ref; +} + +export const Checkbox = React.forwardRef( + function Checkbox(props, ref) { + const { icon, children, inputProps, rootRef, ...rest } = props; + return ( + + + + { icon || } + + { children != null && ( + { children } + ) } + + ); + }, +); + +export interface CheckboxGroupProps extends HTMLChakraProps<'div', ArkCheckbox.GroupProps> { + orientation?: 'vertical' | 'horizontal'; +} + +export const CheckboxGroup = React.forwardRef( + function CheckboxGroup(props, ref) { + const { children, orientation = 'vertical', ...rest } = props; + return ( + + { children } + + ); + }, +); diff --git a/toolkit/chakra/close-button.tsx b/toolkit/chakra/close-button.tsx new file mode 100644 index 0000000000..cdd4b7b116 --- /dev/null +++ b/toolkit/chakra/close-button.tsx @@ -0,0 +1,27 @@ +import type { ButtonProps } from '@chakra-ui/react'; +import { useRecipe } from '@chakra-ui/react'; +import * as React from 'react'; + +import IconSvg from 'ui/shared/IconSvg'; + +import { recipe as closeButtonRecipe } from '../theme/recipes/close-button.recipe'; +import { IconButton } from './icon-button'; +export interface CloseButtonProps extends Omit { + variant?: 'plain'; + size?: 'md'; +} + +export const CloseButton = React.forwardRef< + HTMLButtonElement, + CloseButtonProps +>(function CloseButton(props, ref) { + const recipe = useRecipe({ recipe: closeButtonRecipe }); + const [ recipeProps, restProps ] = recipe.splitVariantProps(props); + const styles = recipe(recipeProps); + + return ( + + { props.children ?? } + + ); +}); diff --git a/toolkit/chakra/collapsible.tsx b/toolkit/chakra/collapsible.tsx new file mode 100644 index 0000000000..cb6b2cc1d7 --- /dev/null +++ b/toolkit/chakra/collapsible.tsx @@ -0,0 +1,100 @@ +import { Flex, type FlexProps } from '@chakra-ui/react'; +import React from 'react'; +import { scroller, Element } from 'react-scroll'; + +import useUpdateEffect from 'lib/hooks/useUpdateEffect'; + +import type { LinkProps } from './link'; +import { Link } from './link'; + +interface CollapsibleDetailsProps extends LinkProps { + children: React.ReactNode; + id?: string; + isExpanded?: boolean; + text?: [string, string]; + noScroll?: boolean; +} + +const SCROLL_CONFIG = { + duration: 500, + smooth: true, +}; + +const CUT_ID = 'CollapsibleDetails'; + +export const CollapsibleDetails = (props: CollapsibleDetailsProps) => { + + const { children, id = CUT_ID, onClick, isExpanded: isExpandedProp = false, text: textProp, loading, noScroll, ...rest } = props; + + const [ isExpanded, setIsExpanded ] = React.useState(isExpandedProp); + + const handleClick = React.useCallback((event: React.MouseEvent) => { + setIsExpanded((flag) => !flag); + if (!noScroll) { + scroller.scrollTo(id, SCROLL_CONFIG); + } + onClick?.(event); + }, [ id, noScroll, onClick ]); + + useUpdateEffect(() => { + setIsExpanded(isExpandedProp); + isExpandedProp && !noScroll && scroller.scrollTo(id, SCROLL_CONFIG); + }, [ isExpandedProp, id, noScroll ]); + + const text = isExpanded ? (textProp?.[1] ?? 'Hide details') : (textProp?.[0] ?? 'View details'); + + return ( + <> + + { text } + + { isExpanded && children } + + ); +}; + +interface CollapsibleListProps extends FlexProps { + items: Array; + renderItem: (item: T, index: number) => React.ReactNode; + triggerProps?: LinkProps; + cutLength?: number; +} + +export const CollapsibleList = (props: CollapsibleListProps) => { + const CUT_LENGTH = 3; + + const { items, renderItem, triggerProps, cutLength = CUT_LENGTH, ...rest } = props; + + const [ isExpanded, setIsExpanded ] = React.useState(false); + + const handleToggle = React.useCallback(() => { + setIsExpanded((flag) => !flag); + }, []); + + return ( + + { items.slice(0, isExpanded ? undefined : cutLength).map(renderItem) } + { items.length > cutLength && ( + + { isExpanded ? 'Hide' : 'Show all' } + + ) } + + ); +}; diff --git a/toolkit/chakra/color-mode.tsx b/toolkit/chakra/color-mode.tsx new file mode 100644 index 0000000000..ea39a3afb1 --- /dev/null +++ b/toolkit/chakra/color-mode.tsx @@ -0,0 +1,40 @@ +'use client'; + +import { ThemeProvider, useTheme } from 'next-themes'; +import type { ThemeProviderProps } from 'next-themes'; +import * as React from 'react'; + +import config from 'configs/app'; + +export interface ColorModeProviderProps extends ThemeProviderProps {} + +export type ColorMode = 'light' | 'dark'; + +export function ColorModeProvider(props: ColorModeProviderProps) { + return ( + + ); +} + +export function useColorMode() { + const { resolvedTheme, setTheme } = useTheme(); + const toggleColorMode = () => { + setTheme(resolvedTheme === 'light' ? 'dark' : 'light'); + }; + return { + colorMode: resolvedTheme as ColorMode, + setColorMode: setTheme, + toggleColorMode, + }; +} + +export function useColorModeValue(light: T, dark: T) { + const { colorMode } = useColorMode(); + return colorMode === 'light' ? light : dark; +} diff --git a/toolkit/chakra/dialog.tsx b/toolkit/chakra/dialog.tsx new file mode 100644 index 0000000000..3f93e301f1 --- /dev/null +++ b/toolkit/chakra/dialog.tsx @@ -0,0 +1,83 @@ +import { Dialog as ChakraDialog, Portal } from '@chakra-ui/react'; +import * as React from 'react'; + +import ButtonBackTo from 'ui/shared/buttons/ButtonBackTo'; + +import { CloseButton } from './close-button'; + +interface DialogContentProps extends ChakraDialog.ContentProps { + portalled?: boolean; + portalRef?: React.RefObject; + backdrop?: boolean; +} + +export const DialogContent = React.forwardRef< + HTMLDivElement, + DialogContentProps +>(function DialogContent(props, ref) { + const { + children, + portalled = true, + portalRef, + backdrop = true, + ...rest + } = props; + + return ( + + { backdrop && } + + + { children } + + + + ); +}); + +export const DialogCloseTrigger = React.forwardRef< + HTMLButtonElement, + ChakraDialog.CloseTriggerProps +>(function DialogCloseTrigger(props, ref) { + return ( + + + { props.children } + + + ); +}); + +export interface DialogHeaderProps extends ChakraDialog.HeaderProps { + startElement?: React.ReactNode; + onBackToClick?: () => void; +} + +export const DialogHeader = React.forwardRef< + HTMLDivElement, + DialogHeaderProps +>(function DialogHeader(props, ref) { + const { startElement: startElementProp, onBackToClick, ...rest } = props; + + const startElement = startElementProp ?? (onBackToClick && ); + + return ( + + { startElement } + { props.children } + + + ); +}); + +export const DialogRoot = ChakraDialog.Root; +export const DialogFooter = ChakraDialog.Footer; +export const DialogBody = ChakraDialog.Body; +export const DialogBackdrop = ChakraDialog.Backdrop; +export const DialogTitle = ChakraDialog.Title; +export const DialogDescription = ChakraDialog.Description; +export const DialogTrigger = ChakraDialog.Trigger; +export const DialogActionTrigger = ChakraDialog.ActionTrigger; diff --git a/toolkit/chakra/drawer.tsx b/toolkit/chakra/drawer.tsx new file mode 100644 index 0000000000..75081fa3d0 --- /dev/null +++ b/toolkit/chakra/drawer.tsx @@ -0,0 +1,64 @@ +import { Drawer as ChakraDrawer, Portal } from '@chakra-ui/react'; +import * as React from 'react'; + +import { CloseButton } from './close-button'; + +interface DrawerContentProps extends ChakraDrawer.ContentProps { + portalled?: boolean; + portalRef?: React.RefObject; + offset?: ChakraDrawer.ContentProps['padding']; + backdrop?: boolean; +} + +export const DrawerContent = React.forwardRef< + HTMLDivElement, + DrawerContentProps +>(function DrawerContent(props, ref) { + const { children, portalled = true, portalRef, offset, backdrop = true, ...rest } = props; + return ( + + { backdrop && } + + + { children } + + + + ); +}); + +export const DrawerCloseTrigger = React.forwardRef< + HTMLButtonElement, + ChakraDrawer.CloseTriggerProps +>(function DrawerCloseTrigger(props, ref) { + return ( + + + + ); +}); + +const EMPTY_ELEMENT = () => null; + +export const DrawerRoot = (props: ChakraDrawer.RootProps) => { + const { initialFocusEl = EMPTY_ELEMENT, lazyMount = true, unmountOnExit = true, ...rest } = props; + return ; +}; + +export const DrawerTrigger = (props: ChakraDrawer.TriggerProps) => { + const { asChild = true, ...rest } = props; + return ; +}; + +export const DrawerFooter = ChakraDrawer.Footer; +export const DrawerHeader = ChakraDrawer.Header; +export const DrawerBody = ChakraDrawer.Body; +export const DrawerDescription = ChakraDrawer.Description; +export const DrawerTitle = ChakraDrawer.Title; +export const DrawerActionTrigger = ChakraDrawer.ActionTrigger; diff --git a/toolkit/chakra/field.tsx b/toolkit/chakra/field.tsx new file mode 100644 index 0000000000..e5c8db38bb --- /dev/null +++ b/toolkit/chakra/field.tsx @@ -0,0 +1,108 @@ +import { Field as ChakraField } from '@chakra-ui/react'; +import * as React from 'react'; + +import { space } from 'lib/html-entities'; + +import getComponentDisplayName from '../utils/getComponentDisplayName'; +import type { InputProps } from './input'; +import type { InputGroupProps } from './input-group'; + +export interface FieldProps extends Omit { + label?: React.ReactNode; + helperText?: React.ReactNode; + errorText?: React.ReactNode; + optionalText?: React.ReactNode; + children: React.ReactElement | React.ReactElement; + size?: 'sm' | 'md' | 'lg' | '2xl'; +} + +export const Field = React.forwardRef( + function Field(props, ref) { + const { label, children, helperText, errorText, optionalText, ...rest } = props; + + // A floating field cannot be without a label. + if (rest.floating && label) { + const injectedProps = { + className: 'peer', + placeholder: ' ', + size: rest.size, + floating: rest.floating, + bgColor: rest.bgColor, + disabled: rest.disabled, + readOnly: rest.readOnly, + }; + + const labelElement = ( + + { label } + + { errorText && ( + -{ space }{ errorText } + ) } + + ); + + const helperTextElement = helperText && ( + { helperText } + ); + + const child = React.Children.only>(children); + const isInputGroup = getComponentDisplayName(child.type) === 'InputGroup'; + + if (isInputGroup) { + const inputElement = React.cloneElement( + React.Children.only>(child.props.children as React.ReactElement), + injectedProps, + ); + + const groupInputElement = React.cloneElement(child, + {}, + inputElement, + labelElement, + ); + + return ( + + { groupInputElement } + { helperTextElement } + + ); + } + + const inputElement = React.cloneElement(child, injectedProps); + + return ( + + { inputElement } + { labelElement } + { helperTextElement } + + ); + } + + // Pass size value to the input component + const injectedProps = { + size: rest.size, + }; + const child = React.Children.only>(children); + const clonedChild = React.cloneElement(child, injectedProps); + + return ( + + { label && ( + + { label } + + + ) } + { clonedChild } + { helperText && ( + { helperText } + ) } + { errorText && ( + { errorText } + ) } + + ); + }, +); diff --git a/toolkit/chakra/heading.tsx b/toolkit/chakra/heading.tsx new file mode 100644 index 0000000000..b32af9d94f --- /dev/null +++ b/toolkit/chakra/heading.tsx @@ -0,0 +1,38 @@ +import type { HeadingProps as ChakraHeadingProps } from '@chakra-ui/react'; +import { Heading as ChakraHeading } from '@chakra-ui/react'; +import React from 'react'; + +export interface HeadingProps extends ChakraHeadingProps { + level?: '1' | '2' | '3'; +} + +export const Heading = React.forwardRef( + function Heading(props, ref) { + const { level, ...rest } = props; + + const textStyle = (() => { + if (level === '1') { + return { base: 'heading.md', lg: 'heading.xl' }; + } + + if (level === '2') { + return { base: 'heading.sm', lg: 'heading.lg' }; + } + + return { base: 'heading.xs', lg: 'heading.md' }; + })(); + + const as = (() => { + if (level === '1') { + return 'h1'; + } + + if (level === '2') { + return 'h2'; + } + + return 'h3'; + })(); + + return ; + }); diff --git a/toolkit/chakra/icon-button.tsx b/toolkit/chakra/icon-button.tsx new file mode 100644 index 0000000000..fb31b0387f --- /dev/null +++ b/toolkit/chakra/icon-button.tsx @@ -0,0 +1,58 @@ +import React from 'react'; + +import getComponentDisplayName from '../utils/getComponentDisplayName'; +import { Button, type ButtonProps } from './button'; + +export interface IconButtonProps extends Omit { + size?: '2xs' | 'md'; +} + +export const IconButton = React.forwardRef( + function IconButton(props, ref) { + const { size, variant = 'plain', children, ...rest } = props; + + // FIXME: I have to clone the children instead of using _icon props because of style overrides + // in some pw tests for some reason the _icon style will be applied before the style of child (IconSvg component) + const child = React.Children.only(children as React.ReactElement); + const clonedChildren = size && getComponentDisplayName(child.type) === 'IconSvg' ? + React.cloneElement(child, { boxSize: 5 }) : + child; + + const sizeStyle = (() => { + switch (size) { + case '2xs': { + return { + _icon: { boxSize: 5 }, + boxSize: 5, + borderRadius: 'sm', + }; + } + case 'md': { + return { + _icon: { boxSize: 5 }, + boxSize: 8, + }; + } + default: + return {}; + } + })(); + + return ( + + ); + }, +); diff --git a/toolkit/chakra/image.tsx b/toolkit/chakra/image.tsx new file mode 100644 index 0000000000..60118e7590 --- /dev/null +++ b/toolkit/chakra/image.tsx @@ -0,0 +1,51 @@ +import type { BoxProps, ImageProps as ChakraImageProps } from '@chakra-ui/react'; +import { Image as ChakraImage } from '@chakra-ui/react'; +import React from 'react'; + +import { Skeleton } from './skeleton'; + +export interface ImageProps extends ChakraImageProps { + fallback?: React.ReactNode; +} + +export const Image = React.forwardRef( + function Image(props, ref) { + const { fallback, src, onLoad, onError, ...rest } = props; + + const [ loading, setLoading ] = React.useState(true); + const [ error, setError ] = React.useState(false); + + const handleLoadError = React.useCallback((event: React.SyntheticEvent) => { + setError(true); + setLoading(false); + onError?.(event); + }, [ onError ]); + + const handleLoadSuccess = React.useCallback((event: React.SyntheticEvent) => { + setLoading(false); + onLoad?.(event); + }, [ onLoad ]); + + if (!src && fallback) { + return fallback; + } + + if (error) { + return fallback; + } + + return ( + <> + { loading && } + + + ); + }, +); diff --git a/toolkit/chakra/input-group.tsx b/toolkit/chakra/input-group.tsx new file mode 100644 index 0000000000..7f493ade9e --- /dev/null +++ b/toolkit/chakra/input-group.tsx @@ -0,0 +1,88 @@ +import type { BoxProps, InputElementProps } from '@chakra-ui/react'; +import { Group, InputElement } from '@chakra-ui/react'; +import { debounce } from 'es-toolkit'; +import * as React from 'react'; + +import getComponentDisplayName from '../utils/getComponentDisplayName'; +import type { InputProps } from './input'; + +export interface InputGroupProps extends BoxProps { + startElementProps?: InputElementProps; + endElementProps?: InputElementProps; + startElement?: React.ReactNode; + endElement?: React.ReactNode; + children: React.ReactElement; + startOffset?: InputElementProps['paddingStart']; + endOffset?: InputElementProps['paddingEnd']; +} + +export const InputGroup = React.forwardRef( + function InputGroup(props, ref) { + const { + startElement, + startElementProps, + endElement, + endElementProps, + children, + startOffset, + endOffset, + ...rest + } = props; + + const startElementRef = React.useRef(null); + const endElementRef = React.useRef(null); + + const [ inlinePaddings, setInlinePaddings ] = React.useState<{ start?: number; end?: number }>(); + + const calculateInlinePaddings = React.useCallback(() => { + const { width: endWidth } = endElementRef?.current?.getBoundingClientRect() ?? {}; + const { width: startWidth } = startElementRef?.current?.getBoundingClientRect() ?? {}; + + setInlinePaddings({ + start: startWidth ?? 0, + end: endWidth ?? 0, + }); + }, []); + + React.useEffect(() => { + calculateInlinePaddings(); + + const resizeHandler = debounce(calculateInlinePaddings, 300); + const resizeObserver = new ResizeObserver(resizeHandler); + resizeObserver.observe(window.document.body); + + return function cleanup() { + resizeObserver.unobserve(window.document.body); + }; + }, [ calculateInlinePaddings ]); + + return ( + + { startElement && ( + + { startElement } + + ) } + { React.Children.map(children, (child: React.ReactElement) => { + if (getComponentDisplayName(child.type) !== 'FieldInput') { + return child; + } + return React.cloneElement(child, { + ...(startElement && { ps: startOffset ?? (inlinePaddings?.start ? `${ inlinePaddings.start }px` : undefined) }), + ...(endElement && { pe: endOffset ?? (inlinePaddings?.end ? `${ inlinePaddings.end }px` : undefined) }), + // hide input value and placeholder for the first render + value: inlinePaddings ? child.props.value : undefined, + placeholder: inlinePaddings ? child.props.placeholder : undefined, + }); + }) } + { endElement && ( + + { endElement } + + ) } + + ); + }, +); + +InputGroup.displayName = 'InputGroup'; diff --git a/toolkit/chakra/input.tsx b/toolkit/chakra/input.tsx new file mode 100644 index 0000000000..5c7dad5b31 --- /dev/null +++ b/toolkit/chakra/input.tsx @@ -0,0 +1,8 @@ +import type { InputProps as ChakraInputProps } from '@chakra-ui/react'; +import { Input as ChakraInput } from '@chakra-ui/react'; + +export interface InputProps extends Omit { + size?: 'sm' | 'md' | 'lg' | '2xl'; +} + +export const Input = ChakraInput; diff --git a/toolkit/chakra/link.tsx b/toolkit/chakra/link.tsx new file mode 100644 index 0000000000..d548faeebd --- /dev/null +++ b/toolkit/chakra/link.tsx @@ -0,0 +1,114 @@ +import type { LinkProps as ChakraLinkProps } from '@chakra-ui/react'; +import { Link as ChakraLink, LinkBox as ChakraLinkBox, LinkOverlay as ChakraLinkOverlay } from '@chakra-ui/react'; +import NextLink from 'next/link'; +import type { LinkProps as NextLinkProps } from 'next/link'; +import React from 'react'; + +import IconSvg from 'ui/shared/IconSvg'; + +import { Skeleton } from './skeleton'; + +export const LinkExternalIcon = ({ color }: { color?: ChakraLinkProps['color'] }) => ( + +); + +interface LinkPropsChakra extends ChakraLinkProps { + loading?: boolean; + external?: boolean; + iconColor?: ChakraLinkProps['color']; + noIcon?: boolean; + disabled?: boolean; +} + +interface LinkPropsNext extends Pick {} + +export interface LinkProps extends LinkPropsChakra, LinkPropsNext {} + +const splitProps = (props: LinkProps): { chakra: LinkPropsChakra; next: NextLinkProps } => { + const { scroll = true, shallow = false, prefetch = false, ...rest } = props; + + return { + chakra: rest, + next: { + href: rest.href as NextLinkProps['href'], + scroll, + shallow, + prefetch, + }, + }; +}; + +export const Link = React.forwardRef( + function Link(props, ref) { + const { chakra, next } = splitProps(props); + const { external, loading, href, children, disabled, noIcon, iconColor, ...rest } = chakra; + + if (external) { + return ( + } asChild> + + { children } + { !noIcon && } + + + ); + } + + return ( + } asChild> + + { next.href ? ( + + { children } + + ) : + { children } + } + + + ); + }, +); + +export const LinkBox = ChakraLinkBox; + +export const LinkOverlay = React.forwardRef( + function LinkOverlay(props, ref) { + const { chakra, next } = splitProps(props); + const { children, external, ...rest } = chakra; + + if (external) { + return ( + + { children } + + ); + } + + return ( + + { next.href ? { children } : children } + + ); + }, +); diff --git a/toolkit/chakra/menu.tsx b/toolkit/chakra/menu.tsx new file mode 100644 index 0000000000..358dca612c --- /dev/null +++ b/toolkit/chakra/menu.tsx @@ -0,0 +1,124 @@ +'use client'; + +import { AbsoluteCenter, Menu as ChakraMenu, Portal } from '@chakra-ui/react'; +import * as React from 'react'; +import { LuCheck, LuChevronRight } from 'react-icons/lu'; + +interface MenuContentProps extends ChakraMenu.ContentProps { + portalled?: boolean; + portalRef?: React.RefObject; +} + +export const MenuContent = React.forwardRef( + function MenuContent(props, ref) { + const { portalled = true, portalRef, ...rest } = props; + return ( + + + + + + ); + }, +); + +export const MenuArrow = React.forwardRef< + HTMLDivElement, + ChakraMenu.ArrowProps +>(function MenuArrow(props, ref) { + return ( + + + + ); +}); + +export const MenuCheckboxItem = React.forwardRef< + HTMLDivElement, + ChakraMenu.CheckboxItemProps +>(function MenuCheckboxItem(props, ref) { + return ( + + + + + + + { props.children } + + ); +}); + +export const MenuRadioItem = React.forwardRef< + HTMLDivElement, + ChakraMenu.RadioItemProps +>(function MenuRadioItem(props, ref) { + const { children, ...rest } = props; + return ( + + + + + + + { children } + + ); +}); + +export const MenuItemGroup = React.forwardRef< + HTMLDivElement, + ChakraMenu.ItemGroupProps +>(function MenuItemGroup(props, ref) { + const { title, children, ...rest } = props; + return ( + + { title && ( + + { title } + + ) } + { children } + + ); +}); + +export interface MenuTriggerItemProps extends ChakraMenu.ItemProps { + startIcon?: React.ReactNode; +} + +export const MenuTriggerItem = React.forwardRef< + HTMLDivElement, + MenuTriggerItemProps +>(function MenuTriggerItem(props, ref) { + const { startIcon, children, ...rest } = props; + return ( + + { startIcon } + { children } + + + ); +}); + +export const MenuRadioItemGroup = ChakraMenu.RadioItemGroup; +export const MenuContextTrigger = ChakraMenu.ContextTrigger; +export const MenuRoot = (props: ChakraMenu.RootProps) => { + const { lazyMount = true, unmountOnExit = true, ...rest } = props; + const positioning = { + placement: 'bottom-start' as const, + ...props.positioning, + offset: { + mainAxis: 4, + ...props.positioning?.offset, + }, + }; + + return ; +}; +export const MenuSeparator = ChakraMenu.Separator; + +export const MenuItem = ChakraMenu.Item; +export const MenuItemText = ChakraMenu.ItemText; +export const MenuItemCommand = ChakraMenu.ItemCommand; +export const MenuTrigger = ChakraMenu.Trigger; diff --git a/toolkit/chakra/pin-input.tsx b/toolkit/chakra/pin-input.tsx new file mode 100644 index 0000000000..8f5f923939 --- /dev/null +++ b/toolkit/chakra/pin-input.tsx @@ -0,0 +1,27 @@ +import { PinInput as ChakraPinInput, Group } from '@chakra-ui/react'; +import * as React from 'react'; + +export interface PinInputProps extends ChakraPinInput.RootProps { + rootRef?: React.Ref; + count?: number; + inputProps?: React.InputHTMLAttributes; + attached?: boolean; +} + +export const PinInput = React.forwardRef( + function PinInput(props, ref) { + const { count = 6, inputProps, rootRef, attached, placeholder = ' ', bgColor, ...rest } = props; + return ( + + + + + { Array.from({ length: count }).map((_, index) => ( + + )) } + + + + ); + }, +); diff --git a/toolkit/chakra/popover.tsx b/toolkit/chakra/popover.tsx new file mode 100644 index 0000000000..e79a4f8fc8 --- /dev/null +++ b/toolkit/chakra/popover.tsx @@ -0,0 +1,108 @@ +import { Popover as ChakraPopover, Portal } from '@chakra-ui/react'; +import * as React from 'react'; + +import { CloseButton } from './close-button'; + +interface PopoverContentProps extends ChakraPopover.ContentProps { + portalled?: boolean; + portalRef?: React.RefObject; +} + +export const PopoverContent = React.forwardRef< + HTMLDivElement, + PopoverContentProps +>(function PopoverContent(props, ref) { + const { portalled = true, portalRef, ...rest } = props; + return ( + + + + + + ); +}); + +export const PopoverArrow = React.forwardRef< + HTMLDivElement, + ChakraPopover.ArrowProps +>(function PopoverArrow(props, ref) { + return ( + + + + ); +}); + +export const PopoverCloseTrigger = React.forwardRef< + HTMLButtonElement, + ChakraPopover.CloseTriggerProps +>(function PopoverCloseTrigger(props, ref) { + return ( + + + + ); +}); + +export const PopoverCloseTriggerWrapper = React.forwardRef< + HTMLButtonElement, + ChakraPopover.CloseTriggerProps +>(function PopoverCloseTriggerWrapper(props, ref) { + const { disabled, ...rest } = props; + + if (disabled) { + return props.children; + } + + return ( + + ); +}); + +export const PopoverRoot = (props: ChakraPopover.RootProps) => { + const positioning = { + placement: 'bottom-start' as const, + overflowPadding: 4, + ...props.positioning, + offset: { + mainAxis: 4, + ...props.positioning?.offset, + }, + }; + const { lazyMount = true, unmountOnExit = true, ...rest } = props; + + return ( + + ); +}; + +export const PopoverTrigger = React.forwardRef< + HTMLButtonElement, + ChakraPopover.TriggerProps +>(function PopoverTrigger(props, ref) { + const { asChild = true, ...rest } = props; + return ; +}); + +export const PopoverTitle = ChakraPopover.Title; +export const PopoverDescription = ChakraPopover.Description; +export const PopoverFooter = ChakraPopover.Footer; +export const PopoverHeader = ChakraPopover.Header; +export const PopoverBody = ChakraPopover.Body; diff --git a/toolkit/chakra/progress-circle.tsx b/toolkit/chakra/progress-circle.tsx new file mode 100644 index 0000000000..1f6b31fd47 --- /dev/null +++ b/toolkit/chakra/progress-circle.tsx @@ -0,0 +1,38 @@ +import type { SystemStyleObject } from '@chakra-ui/react'; +import { + AbsoluteCenter, + ProgressCircle as ChakraProgressCircle, +} from '@chakra-ui/react'; +import * as React from 'react'; + +export interface ProgressCircleRingProps extends ChakraProgressCircle.CircleProps { + trackColor?: SystemStyleObject['stroke']; + cap?: SystemStyleObject['strokeLinecap']; +} + +export const ProgressCircleRing = React.forwardRef< + SVGSVGElement, + ProgressCircleRingProps +>(function ProgressCircleRing(props, ref) { + const { trackColor, cap, color, ...rest } = props; + return ( + + + + + ); +}); + +export const ProgressCircleValueText = React.forwardRef< + HTMLDivElement, + ChakraProgressCircle.ValueTextProps +>(function ProgressCircleValueText(props, ref) { + return ( + + + + ); +}); + +export interface ProgressCircleRootProps extends ChakraProgressCircle.RootProps {} +export const ProgressCircleRoot = ChakraProgressCircle.Root; diff --git a/toolkit/chakra/provider.tsx b/toolkit/chakra/provider.tsx new file mode 100644 index 0000000000..637d105681 --- /dev/null +++ b/toolkit/chakra/provider.tsx @@ -0,0 +1,19 @@ +'use client'; + +import { ChakraProvider } from '@chakra-ui/react'; +import React from 'react'; + +import theme from 'toolkit/theme/theme'; + +import { + ColorModeProvider, + type ColorModeProviderProps, +} from './color-mode'; + +export function Provider(props: ColorModeProviderProps) { + return ( + + + + ); +} diff --git a/toolkit/chakra/radio.tsx b/toolkit/chakra/radio.tsx new file mode 100644 index 0000000000..832f75f9fb --- /dev/null +++ b/toolkit/chakra/radio.tsx @@ -0,0 +1,26 @@ +import { RadioGroup as ChakraRadioGroup } from '@chakra-ui/react'; +import * as React from 'react'; + +export interface RadioProps extends ChakraRadioGroup.ItemProps { + rootRef?: React.Ref; + inputProps?: React.InputHTMLAttributes; +} + +export const Radio = React.forwardRef( + function Radio(props, ref) { + const { children, inputProps, rootRef, ...rest } = props; + return ( + + + + { children && ( + { children } + ) } + + ); + }, +); + +export interface RadioGroupProps extends ChakraRadioGroup.RootProps {} + +export const RadioGroup = ChakraRadioGroup.Root; diff --git a/toolkit/chakra/rating.tsx b/toolkit/chakra/rating.tsx new file mode 100644 index 0000000000..3895192f93 --- /dev/null +++ b/toolkit/chakra/rating.tsx @@ -0,0 +1,40 @@ +import { RatingGroup, useRatingGroup } from '@chakra-ui/react'; +import * as React from 'react'; + +import IconSvg from 'ui/shared/IconSvg'; + +export interface RatingProps extends Omit { + count?: number; + label?: string | Array; + defaultValue?: number; + onValueChange?: ({ value }: { value: number }) => void; + readOnly?: boolean; +} + +export const Rating = React.forwardRef( + function Rating(props, ref) { + const { count = 5, label: labelProp, defaultValue, onValueChange, readOnly, ...rest } = props; + const store = useRatingGroup({ count, defaultValue, onValueChange, readOnly }); + + const highlightedIndex = store.hovering && !readOnly ? store.hoveredValue : store.value; + const label = Array.isArray(labelProp) ? labelProp[highlightedIndex - 1] : labelProp; + + return ( + + + + { Array.from({ length: count }).map((_, index) => { + const icon = index < highlightedIndex ? : ; + + return ( + + + + ); + }) } + + { label && { label } } + + ); + }, +); diff --git a/toolkit/chakra/select.tsx b/toolkit/chakra/select.tsx new file mode 100644 index 0000000000..83d46745c2 --- /dev/null +++ b/toolkit/chakra/select.tsx @@ -0,0 +1,341 @@ +'use client'; + +import type { ListCollection } from '@chakra-ui/react'; +import { Box, Select as ChakraSelect, createListCollection, Flex, Portal, useSelectContext } from '@chakra-ui/react'; +import { useDebounce } from '@uidotdev/usehooks'; +import * as React from 'react'; + +import FilterInput from 'ui/shared/filters/FilterInput'; +import type { IconName } from 'ui/shared/IconSvg'; +import IconSvg from 'ui/shared/IconSvg'; + +import { CloseButton } from './close-button'; +import { Skeleton } from './skeleton'; + +export interface SelectOption { + value: Value; + label: string; + icon?: IconName | React.ReactNode; +} + +export interface SelectControlProps extends ChakraSelect.ControlProps { + noIndicator?: boolean; + triggerProps?: ChakraSelect.TriggerProps; + loading?: boolean; +} + +export const SelectControl = React.forwardRef< + HTMLButtonElement, + SelectControlProps +>(function SelectControl(props, ref) { + // NOTE: here defaultValue means the "default" option of the select, not its initial value + const { children, noIndicator, triggerProps, loading, defaultValue, ...rest } = props; + + const context = useSelectContext(); + const isDefaultValue = Array.isArray(defaultValue) ? context.value.every((item) => defaultValue.includes(item)) : context.value === defaultValue; + + return ( + + + { children } + { (!noIndicator) && ( + + { !noIndicator && ( + + + + ) } + + ) } + + + ); +}); + +export const SelectClearTrigger = React.forwardRef< + HTMLButtonElement, + ChakraSelect.ClearTriggerProps +>(function SelectClearTrigger(props, ref) { + return ( + + + + ); +}); + +interface SelectContentProps extends ChakraSelect.ContentProps { + portalled?: boolean; + portalRef?: React.RefObject; +} + +export const SelectContent = React.forwardRef< + HTMLDivElement, + SelectContentProps +>(function SelectContent(props, ref) { + const { portalled = true, portalRef, ...rest } = props; + return ( + + + + + + ); +}); + +export const SelectItem = React.forwardRef< + HTMLDivElement, + ChakraSelect.ItemProps +>(function SelectItem(props, ref) { + const { item, children, ...rest } = props; + + const startElement = (() => { + if (item.icon) { + return typeof item.icon === 'string' ? : item.icon; + } + + return null; + })(); + + return ( + + { startElement } + { children } + + + + + ); +}); + +interface SelectValueTextProps extends Omit { + children?(items: Array): React.ReactNode; + size?: SelectRootProps['size']; + required?: boolean; + invalid?: boolean; + errorText?: string; +} + +export const SelectValueText = React.forwardRef< + HTMLSpanElement, + SelectValueTextProps +>(function SelectValueText(props, ref) { + const { children, size, required, invalid, errorText, ...rest } = props; + const context = useSelectContext(); + + const content = (() => { + const items = context.selectedItems; + + const placeholder = `${ props.placeholder }${ required ? '*' : '' }${ invalid && errorText ? ` - ${ errorText }` : '' }`; + + if (items.length === 0) return placeholder; + + if (children) return children(items); + + if (items.length === 1) { + const item = items[0] as SelectOption; + + if (!item) return placeholder; + + const icon = (() => { + if (item.icon) { + return typeof item.icon === 'string' ? : item.icon; + } + + return null; + })(); + + const label = size === 'lg' ? ( + + { placeholder } + + ) : null; + + return ( + <> + { label } + + { icon } + + { context.collection.stringifyItem(item) } + + + + ); + } + + // FIXME: we don't have multiple selection in the select yet + return `${ items.length } selected`; + })(); + + return ( + + { content } + + ); +}); + +export interface SelectRootProps extends ChakraSelect.RootProps {} + +export const SelectRoot = React.forwardRef< + HTMLDivElement, + ChakraSelect.RootProps +>(function SelectRoot(props, ref) { + const { lazyMount = true, unmountOnExit = true, ...rest } = props; + return ( + + { props.asChild ? ( + props.children + ) : ( + <> + + { props.children } + + ) } + + ); +}) as ChakraSelect.RootComponent; + +interface SelectItemGroupProps extends ChakraSelect.ItemGroupProps { + label: React.ReactNode; +} + +export const SelectItemGroup = React.forwardRef< + HTMLDivElement, + SelectItemGroupProps +>(function SelectItemGroup(props, ref) { + const { children, label, ...rest } = props; + return ( + + { label } + { children } + + ); +}); + +export const SelectLabel = ChakraSelect.Label; +export const SelectItemText = ChakraSelect.ItemText; + +export interface SelectProps extends SelectRootProps { + collection: ListCollection; + placeholder: string; + portalled?: boolean; + loading?: boolean; + errorText?: string; +} + +export const Select = React.forwardRef((props, ref) => { + const { collection, placeholder, portalled = true, loading, errorText, ...rest } = props; + return ( + + + + + + { collection.items.map((item: SelectOption) => ( + + { item.label } + + )) } + + + ); +}); + +export interface SelectAsyncProps extends Omit { + placeholder: string; + portalled?: boolean; + loading?: boolean; + loadOptions: (input: string, currentValue: Array) => Promise>; + extraControls?: React.ReactNode; +} + +export const SelectAsync = React.forwardRef((props, ref) => { + const { placeholder, portalled = true, loading, loadOptions, extraControls, onValueChange, errorText, ...rest } = props; + + const [ collection, setCollection ] = React.useState>(createListCollection({ items: [] })); + const [ inputValue, setInputValue ] = React.useState(''); + const [ value, setValue ] = React.useState>([]); + + const debouncedInputValue = useDebounce(inputValue, 300); + + React.useEffect(() => { + loadOptions(debouncedInputValue, value).then(setCollection); + }, [ debouncedInputValue, loadOptions, value ]); + + const handleFilterChange = React.useCallback((value: string) => { + setInputValue(value); + }, [ ]); + + const handleValueChange = React.useCallback(({ value, items }: { value: Array; items: Array }) => { + setValue(value); + onValueChange?.({ value, items }); + }, [ onValueChange ]); + + return ( + + + + + + + + { extraControls } + + { collection.items.map((item) => ( + + { item.label } + + )) } + + + ); +}); diff --git a/toolkit/chakra/skeleton.tsx b/toolkit/chakra/skeleton.tsx new file mode 100644 index 0000000000..b4da4d8f0a --- /dev/null +++ b/toolkit/chakra/skeleton.tsx @@ -0,0 +1,62 @@ +import type { + SkeletonProps as ChakraSkeletonProps, + CircleProps, +} from '@chakra-ui/react'; +import { Skeleton as ChakraSkeleton, Circle, Stack } from '@chakra-ui/react'; +import * as React from 'react'; + +export interface SkeletonCircleProps extends ChakraSkeletonProps { + size?: CircleProps['size']; +} + +export const SkeletonCircle = React.forwardRef< + HTMLDivElement, + SkeletonCircleProps +>(function SkeletonCircle(props, ref) { + const { size, ...rest } = props; + return ( + + + + ); +}); + +export interface SkeletonTextProps extends ChakraSkeletonProps { + noOfLines?: number; +} + +export const SkeletonText = React.forwardRef( + function SkeletonText(props, ref) { + const { noOfLines = 3, gap, ...rest } = props; + return ( + + { Array.from({ length: noOfLines }).map((_, index) => ( + + )) } + + ); + }, +); + +export interface SkeletonProps extends Omit { + loading: boolean | undefined; +} + +export const Skeleton = React.forwardRef( + function Skeleton(props, ref) { + const { loading = false, ...rest } = props; + return ( + + ); + }, +); diff --git a/toolkit/chakra/slider.tsx b/toolkit/chakra/slider.tsx new file mode 100644 index 0000000000..5e1fddca22 --- /dev/null +++ b/toolkit/chakra/slider.tsx @@ -0,0 +1,82 @@ +import { Slider as ChakraSlider, For, HStack } from '@chakra-ui/react'; +import * as React from 'react'; + +export interface SliderProps extends ChakraSlider.RootProps { + marks?: Array; + label?: React.ReactNode; + showValue?: boolean; +} + +export const Slider = React.forwardRef( + function Slider(props, ref) { + const { marks: marksProp, label, showValue, ...rest } = props; + const value = props.defaultValue ?? props.value; + + const marks = marksProp?.map((mark) => { + if (typeof mark === 'number') return { value: mark, label: undefined }; + return mark; + }); + + const hasMarkLabel = Boolean(marks?.some((mark) => mark.label)); + + return ( + + { label && !showValue && ( + { label } + ) } + { label && showValue && ( + + { label } + + + ) } + + + + + + + + + ); + }, +); + +function SliderThumbs(props: { value?: Array }) { + const { value } = props; + return ( + + { (_, index) => ( + + + + ) } + + ); +} + +interface SliderMarksProps { + marks?: Array; +} + +const SliderMarks = React.forwardRef( + function SliderMarks(props, ref) { + const { marks } = props; + if (!marks?.length) return null; + + return ( + + { marks.map((mark, index) => { + const value = typeof mark === 'number' ? mark : mark.value; + const label = typeof mark === 'number' ? undefined : mark.label; + return ( + + + { label } + + ); + }) } + + ); + }, +); diff --git a/toolkit/chakra/switch.tsx b/toolkit/chakra/switch.tsx new file mode 100644 index 0000000000..ea48c54bb5 --- /dev/null +++ b/toolkit/chakra/switch.tsx @@ -0,0 +1,40 @@ +import { Switch as ChakraSwitch } from '@chakra-ui/react'; +import * as React from 'react'; + +export interface SwitchProps extends ChakraSwitch.RootProps { + inputProps?: React.InputHTMLAttributes; + labelProps?: ChakraSwitch.LabelProps; + rootRef?: React.Ref; + trackLabel?: { on: React.ReactNode; off: React.ReactNode }; + thumbLabel?: { on: React.ReactNode; off: React.ReactNode }; +} + +export const Switch = React.forwardRef( + function Switch(props, ref) { + const { inputProps, children, rootRef, trackLabel, thumbLabel, labelProps, ...rest } = + props; + + return ( + + + + + { thumbLabel && ( + + { thumbLabel?.on } + + ) } + + { trackLabel && ( + + { trackLabel.on } + + ) } + + { children != null && ( + { children } + ) } + + ); + }, +); diff --git a/toolkit/chakra/table.tsx b/toolkit/chakra/table.tsx new file mode 100644 index 0000000000..c970792347 --- /dev/null +++ b/toolkit/chakra/table.tsx @@ -0,0 +1,111 @@ +import { Table as ChakraTable } from '@chakra-ui/react'; +import { throttle } from 'es-toolkit'; +import * as React from 'react'; + +import IconSvg from 'ui/shared/IconSvg'; + +import { Link } from './link'; + +export const TableRoot = ChakraTable.Root; +export const TableBody = ChakraTable.Body; +export const TableHeader = ChakraTable.Header; +export const TableRow = ChakraTable.Row; + +export interface TableCellProps extends ChakraTable.CellProps { + isNumeric?: boolean; +} + +export const TableCell = (props: TableCellProps) => { + const { isNumeric, ...rest } = props; + + return ; +}; + +export interface TableColumnHeaderProps extends ChakraTable.ColumnHeaderProps { + isNumeric?: boolean; +} + +export const TableColumnHeader = (props: TableColumnHeaderProps) => { + const { isNumeric, ...rest } = props; + + return ; +}; + +export interface TableColumnHeaderSortableProps extends TableColumnHeaderProps { + sortField: F; + sortValue: string; + onSortToggle: (sortField: F) => void; + disabled?: boolean; + indicatorPosition?: 'left' | 'right'; +} + +export const TableColumnHeaderSortable = (props: TableColumnHeaderSortableProps) => { + const { sortField, sortValue, onSortToggle, children, disabled, indicatorPosition = 'left', ...rest } = props; + + const handleSortToggle = React.useCallback(() => { + onSortToggle(sortField); + }, [ onSortToggle, sortField ]); + + return ( + + + { sortValue.includes(sortField) && ( + + ) } + { children } + + + ); +}; + +export interface TableHeaderProps extends ChakraTable.HeaderProps { + top?: number; +} + +export const TableHeaderSticky = (props: TableHeaderProps) => { + const { top, children, ...rest } = props; + + const ref = React.useRef(null); + const [ isStuck, setIsStuck ] = React.useState(false); + + const handleScroll = React.useCallback(() => { + if (Number(ref.current?.getBoundingClientRect().y) <= (top || 0)) { + setIsStuck(true); + } else { + setIsStuck(false); + } + }, [ top ]); + + React.useEffect(() => { + const throttledHandleScroll = throttle(handleScroll, 300); + + window.addEventListener('scroll', throttledHandleScroll); + + return () => { + window.removeEventListener('scroll', throttledHandleScroll); + }; + }, [ handleScroll ]); + + return ( + + { children } + + ); +}; diff --git a/toolkit/chakra/tabs.tsx b/toolkit/chakra/tabs.tsx new file mode 100644 index 0000000000..905057b3c5 --- /dev/null +++ b/toolkit/chakra/tabs.tsx @@ -0,0 +1,46 @@ +import { Tabs as ChakraTabs, chakra } from '@chakra-ui/react'; +import * as React from 'react'; + +export interface TabsProps extends ChakraTabs.RootProps {} + +export const TabsRoot = React.forwardRef( + function TabsRoot(props, ref) { + const { lazyMount = true, unmountOnExit = true, ...rest } = props; + return ; + }, +); + +export const TabsList = ChakraTabs.List; + +export interface TabsTriggerProps extends ChakraTabs.TriggerProps {} + +export const TabsTrigger = React.forwardRef( + function TabsTrigger(props, ref) { + return ; + }, +); + +export const TabsContent = ChakraTabs.Content; + +export interface TabsCounterProps { + count?: number | null; +} + +export const TabsCounter = ({ count }: TabsCounterProps) => { + const COUNTER_OVERLOAD = 50; + + if (count === undefined || count === null) { + return null; + } + + return ( + 0 ? 'text.secondary' : { _light: 'blackAlpha.400', _dark: 'whiteAlpha.400' } } + _groupHover={{ + color: 'inherit', + }} + > + { count > COUNTER_OVERLOAD ? `${ COUNTER_OVERLOAD }+` : count } + + ); +}; diff --git a/toolkit/chakra/tag.tsx b/toolkit/chakra/tag.tsx new file mode 100644 index 0000000000..cbe82ae7c4 --- /dev/null +++ b/toolkit/chakra/tag.tsx @@ -0,0 +1,73 @@ +import { chakra, Tag as ChakraTag } from '@chakra-ui/react'; +import * as React from 'react'; + +import { nbsp } from 'lib/html-entities'; +import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip'; + +import { CloseButton } from './close-button'; +import { Skeleton } from './skeleton'; + +export interface TagProps extends ChakraTag.RootProps { + startElement?: React.ReactNode; + endElement?: React.ReactNode; + endElementProps?: ChakraTag.EndElementProps; + label?: string; + onClose?: VoidFunction; + closable?: boolean; + truncated?: boolean; + loading?: boolean; + selected?: boolean; +} + +export const Tag = React.forwardRef( + function Tag(props, ref) { + const { + startElement, + endElement, + endElementProps, + label, + onClose, + closable = Boolean(onClose), + children, + truncated = false, + loading, + selected, + ...rest + } = props; + + const labelElement = label ? ( + { label }:{ nbsp } + ) : null; + + const contentElement = truncated ? ( + + { labelElement }{ children } + + ) : { labelElement }{ children }; + + return ( + + + { startElement && ( + { startElement } + ) } + { contentElement } + { endElement && ( + { endElement } + ) } + { closable && ( + + + + + + ) } + + + ); + }, +); diff --git a/toolkit/chakra/textarea.tsx b/toolkit/chakra/textarea.tsx new file mode 100644 index 0000000000..f870da4b2a --- /dev/null +++ b/toolkit/chakra/textarea.tsx @@ -0,0 +1,6 @@ +import type { TextareaProps as ChakraTextareaProps } from '@chakra-ui/react'; +import { Textarea as ChakraTextarea } from '@chakra-ui/react'; + +export interface TextareaProps extends ChakraTextareaProps {} + +export const Textarea = ChakraTextarea; diff --git a/toolkit/chakra/toaster.tsx b/toolkit/chakra/toaster.tsx new file mode 100644 index 0000000000..aebe75b6ef --- /dev/null +++ b/toolkit/chakra/toaster.tsx @@ -0,0 +1,60 @@ +'use client'; + +import { + Toaster as ChakraToaster, + Portal, + Spinner, + Stack, + Toast, + createToaster, +} from '@chakra-ui/react'; + +import { SECOND } from 'lib/consts'; + +import { CloseButton } from './close-button'; + +export const toaster = createToaster({ + placement: 'top-end', + pauseOnPageIdle: true, + duration: 10 * SECOND, + offsets: { + top: '12px', + right: '12px', + bottom: '12px', + left: '12px', + }, +}); + +export const Toaster = () => { + return ( + + + { (toast) => { + const closable = toast.meta?.closable !== undefined ? toast.meta.closable : true; + + return ( + + { toast.type === 'loading' ? ( + + ) : null } + + { toast.title && { toast.title } } + { toast.description && ( + { toast.description } + ) } + + { toast.action && ( + { toast.action.label } + ) } + { closable && ( + + + + ) } + + ); + } } + + + ); +}; diff --git a/toolkit/chakra/tooltip.tsx b/toolkit/chakra/tooltip.tsx new file mode 100644 index 0000000000..e74461004f --- /dev/null +++ b/toolkit/chakra/tooltip.tsx @@ -0,0 +1,112 @@ +import { Tooltip as ChakraTooltip, Portal } from '@chakra-ui/react'; +import { useClickAway } from '@uidotdev/usehooks'; +import * as React from 'react'; + +import config from 'configs/app'; +import useIsMobile from 'lib/hooks/useIsMobile'; + +export interface TooltipProps extends ChakraTooltip.RootProps { + selected?: boolean; + showArrow?: boolean; + portalled?: boolean; + portalRef?: React.RefObject; + content: React.ReactNode; + contentProps?: ChakraTooltip.ContentProps; + triggerProps?: ChakraTooltip.TriggerProps; + disabled?: boolean; + disableOnMobile?: boolean; +} + +export const Tooltip = React.forwardRef( + function Tooltip(props, ref) { + const { + showArrow: showArrowProp, + onOpenChange, + variant, + selected, + children, + disabled, + disableOnMobile, + portalled = true, + content, + contentProps, + portalRef, + defaultOpen = false, + lazyMount = true, + unmountOnExit = true, + triggerProps, + ...rest + } = props; + + const [ open, setOpen ] = React.useState(defaultOpen); + + const isMobile = useIsMobile(); + const triggerRef = useClickAway(() => setOpen(false)); + + const handleOpenChange = React.useCallback((details: { open: boolean }) => { + setOpen(details.open); + onOpenChange?.(details); + }, [ onOpenChange ]); + + const handleTriggerClick = React.useCallback(() => { + setOpen((prev) => !prev); + }, [ ]); + + if (disabled || (disableOnMobile && isMobile)) return children; + + const defaultShowArrow = variant === 'popover' ? false : true; + const showArrow = showArrowProp !== undefined ? showArrowProp : defaultShowArrow; + + const positioning = { + ...rest.positioning, + overflowPadding: 4, + offset: { + mainAxis: 4, + ...rest.positioning?.offset, + }, + }; + + return ( + + + { children } + + + + + { showArrow && ( + + + + ) } + { content } + + + + + ); + }, +); diff --git a/toolkit/components/AdaptiveTabs/AdaptiveTabs.tsx b/toolkit/components/AdaptiveTabs/AdaptiveTabs.tsx new file mode 100644 index 0000000000..634f0c6e42 --- /dev/null +++ b/toolkit/components/AdaptiveTabs/AdaptiveTabs.tsx @@ -0,0 +1,88 @@ +import React from 'react'; + +import type { TabsProps } from 'toolkit/chakra/tabs'; +import { TabsContent, TabsRoot } from 'toolkit/chakra/tabs'; +import useViewportSize from 'toolkit/hooks/useViewportSize'; + +import AdaptiveTabsList, { type BaseProps as AdaptiveTabsListProps } from './AdaptiveTabsList'; +import { getTabValue } from './utils'; + +export interface Props extends TabsProps, AdaptiveTabsListProps { } + +const AdaptiveTabs = (props: Props) => { + const { + tabs, + onValueChange, + defaultValue, + isLoading, + listProps, + rightSlot, + rightSlotProps, + leftSlot, + leftSlotProps, + stickyEnabled, + size, + variant, + ...rest + } = props; + + const [ activeTab, setActiveTab ] = React.useState(defaultValue || getTabValue(tabs[0])); + + const handleTabChange = React.useCallback(({ value }: { value: string }) => { + if (isLoading) { + return; + } + onValueChange ? onValueChange({ value }) : setActiveTab(value); + }, [ isLoading, onValueChange ]); + + const viewportSize = useViewportSize(); + + React.useEffect(() => { + if (defaultValue) { + setActiveTab(defaultValue); + } + }, [ defaultValue ]); + + if (tabs.length === 1) { + return
{ tabs[0].component }
; + } + + return ( + + tab.id).join(':') } + tabs={ tabs } + listProps={ listProps } + leftSlot={ leftSlot } + leftSlotProps={ leftSlotProps } + rightSlot={ rightSlot } + rightSlotProps={ rightSlotProps } + stickyEnabled={ stickyEnabled } + activeTab={ activeTab } + isLoading={ isLoading } + /> + { tabs.map((tab) => { + const value = getTabValue(tab); + return ( + + { tab.component } + + ); + }) } + + ); +}; + +export default React.memo(AdaptiveTabs); diff --git a/toolkit/components/AdaptiveTabs/AdaptiveTabsList.tsx b/toolkit/components/AdaptiveTabs/AdaptiveTabsList.tsx new file mode 100644 index 0000000000..27a2106d0b --- /dev/null +++ b/toolkit/components/AdaptiveTabs/AdaptiveTabsList.tsx @@ -0,0 +1,198 @@ +import type { HTMLChakraProps } from '@chakra-ui/react'; +import { Box } from '@chakra-ui/react'; +import React from 'react'; + +import type { TabItemRegular } from './types'; + +import { useScrollDirection } from 'lib/contexts/scrollDirection'; +import useIsMobile from 'lib/hooks/useIsMobile'; +import useIsSticky from 'lib/hooks/useIsSticky'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import { TabsCounter, TabsList, TabsTrigger } from 'toolkit/chakra/tabs'; + +import AdaptiveTabsMenu from './AdaptiveTabsMenu'; +import useAdaptiveTabs from './useAdaptiveTabs'; +import useScrollToActiveTab from './useScrollToActiveTab'; +import { menuButton, getTabValue } from './utils'; + +export interface SlotProps extends HTMLChakraProps<'div'> { + widthAllocation?: 'available' | 'fixed'; +} + +export interface BaseProps { + tabs: Array; + listProps?: HTMLChakraProps<'div'> | (({ isSticky, activeTab }: { isSticky: boolean; activeTab: string }) => HTMLChakraProps<'div'>); + rightSlot?: React.ReactNode; + rightSlotProps?: SlotProps; + leftSlot?: React.ReactNode; + leftSlotProps?: SlotProps; + stickyEnabled?: boolean; + isLoading?: boolean; +} + +interface Props extends BaseProps { + activeTab: string; +} + +const HIDDEN_ITEM_STYLES: HTMLChakraProps<'button'> = { + position: 'absolute', + top: '-9999px', + left: '-9999px', + visibility: 'hidden', +}; + +const getItemStyles = (index: number, tabsCut: number | undefined) => { + if (tabsCut === undefined) { + return HIDDEN_ITEM_STYLES as never; + } + + return index < tabsCut ? {} : HIDDEN_ITEM_STYLES as never; +}; + +const getMenuStyles = (tabsLength: number, tabsCut: number | undefined) => { + if (tabsCut === undefined) { + return { + opacity: 0, + }; + } + + return tabsCut >= tabsLength ? HIDDEN_ITEM_STYLES : {}; +}; + +const AdaptiveTabsList = (props: Props) => { + + const { + tabs, + activeTab, + listProps, + rightSlot, + rightSlotProps, + leftSlot, + leftSlotProps, + stickyEnabled, + isLoading, + } = props; + + const scrollDirection = useScrollDirection(); + const isMobile = useIsMobile(); + + const tabsList = React.useMemo(() => { + return [ ...tabs, menuButton ]; + }, [ tabs ]); + + const { tabsCut, tabsRefs, listRef, rightSlotRef, leftSlotRef } = useAdaptiveTabs(tabsList, isMobile); + const isSticky = useIsSticky(listRef, 5, stickyEnabled); + const activeTabIndex = tabsList.findIndex((tab) => getTabValue(tab) === activeTab) ?? 0; + useScrollToActiveTab({ activeTabIndex, listRef, tabsRefs, isMobile, isLoading }); + + return ( + + { leftSlot && ( + + { leftSlot } + + ) + } + { tabsList.slice(0, isLoading ? 5 : Infinity).map((tab, index) => { + const value = getTabValue(tab); + const ref = tabsRefs[index]; + + if (tab.id === 'menu') { + if (isLoading) { + return null; + } + + return ( + 0 && tabsCut !== undefined && tabsCut > 0 && activeTabIndex >= tabsCut } + { ...getMenuStyles(tabs.length, tabsCut) } + /> + ); + } + + return ( + + { isLoading ? ( + + { typeof tab.title === 'function' ? tab.title() : tab.title } + + + ) : ( + <> + { typeof tab.title === 'function' ? tab.title() : tab.title } + + + ) } + + ); + }) } + { + rightSlot ? ( + + { rightSlot } + + ) : + null + } + + ); +}; + +export default React.memo(AdaptiveTabsList); diff --git a/toolkit/components/AdaptiveTabs/AdaptiveTabsMenu.tsx b/toolkit/components/AdaptiveTabs/AdaptiveTabsMenu.tsx new file mode 100644 index 0000000000..7675a8d07a --- /dev/null +++ b/toolkit/components/AdaptiveTabs/AdaptiveTabsMenu.tsx @@ -0,0 +1,75 @@ +import React from 'react'; + +import type { TabItem } from './types'; + +import { PopoverBody, PopoverCloseTriggerWrapper, PopoverContent, PopoverRoot, PopoverTrigger } from 'toolkit/chakra/popover'; +import { TabsCounter, TabsTrigger } from 'toolkit/chakra/tabs'; +import IconSvg from 'ui/shared/IconSvg'; + +import { IconButton } from '../../chakra/icon-button'; +import type { IconButtonProps } from '../../chakra/icon-button'; +import { getTabValue } from './utils'; + +interface Props extends IconButtonProps { + tabs: Array; + tabsCut: number; + isActive: boolean; +} + +const AdaptiveTabsMenu = ({ tabs, tabsCut, isActive, ...props }: Props, ref: React.Ref) => { + + return ( + + + + + + + + + { tabs.slice(tabsCut).map((tab) => { + const value = getTabValue(tab); + + return ( + + + { typeof tab.title === 'function' ? tab.title() : tab.title } + + + + ); + }) } + + + + ); +}; + +export default React.memo(React.forwardRef(AdaptiveTabsMenu)); diff --git a/ui/shared/Tabs/types.ts b/toolkit/components/AdaptiveTabs/types.ts similarity index 64% rename from ui/shared/Tabs/types.ts rename to toolkit/components/AdaptiveTabs/types.ts index f15bb79e6f..bc3132ad24 100644 --- a/ui/shared/Tabs/types.ts +++ b/toolkit/components/AdaptiveTabs/types.ts @@ -1,21 +1,22 @@ import type React from 'react'; -export interface TabItem { +export interface TabItemRegular { // NOTE, in case of array of ids, when switching tabs, the first id will be used // switching between other ids should be handled in the underlying component id: string | Array; title: string | (() => React.ReactNode); count?: number | null; component: React.ReactNode; + subTabs?: Array; } -export type RoutedTab = TabItem & { subTabs?: Array }; - -export type RoutedSubTab = Omit; - -export interface MenuButton { - id: null; +export interface TabItemMenu { + id: 'menu'; title: string; count?: never; component: null; } + +export type TabItem = TabItemRegular | TabItemMenu; + +export type SubTabItem = Omit; diff --git a/ui/shared/Tabs/useAdaptiveTabs.tsx b/toolkit/components/AdaptiveTabs/useAdaptiveTabs.tsx similarity index 87% rename from ui/shared/Tabs/useAdaptiveTabs.tsx rename to toolkit/components/AdaptiveTabs/useAdaptiveTabs.tsx index a8c944a39e..bf3be30f82 100644 --- a/ui/shared/Tabs/useAdaptiveTabs.tsx +++ b/toolkit/components/AdaptiveTabs/useAdaptiveTabs.tsx @@ -1,11 +1,11 @@ import React from 'react'; -import type { MenuButton, RoutedTab } from './types'; +import type { TabItem } from './types'; -export default function useAdaptiveTabs(tabs: Array, disabled?: boolean) { - // to avoid flickering we set initial value to 0 +export default function useAdaptiveTabs(tabs: Array, disabled?: boolean) { + // to avoid flickering we set initial value to undefined // so there will be no displayed tabs initially - const [ tabsCut, setTabsCut ] = React.useState(disabled ? tabs.length : 0); + const [ tabsCut, setTabsCut ] = React.useState(disabled ? tabs.length : undefined); const [ tabsRefs, setTabsRefs ] = React.useState>>([]); const listRef = React.useRef(null); const rightSlotRef = React.useRef(null); @@ -52,7 +52,7 @@ export default function useAdaptiveTabs(tabs: Array, dis React.useEffect(() => { setTabsRefs(tabs.map((_, index) => tabsRefs[index] || React.createRef())); - setTabsCut(disabled ? tabs.length : 0); + setTabsCut(disabled ? tabs.length : undefined); // update refs only when disabled prop changes // eslint-disable-next-line react-hooks/exhaustive-deps }, [ disabled ]); diff --git a/ui/shared/Tabs/useScrollToActiveTab.tsx b/toolkit/components/AdaptiveTabs/useScrollToActiveTab.tsx similarity index 96% rename from ui/shared/Tabs/useScrollToActiveTab.tsx rename to toolkit/components/AdaptiveTabs/useScrollToActiveTab.tsx index 8d729899d1..777aece430 100644 --- a/ui/shared/Tabs/useScrollToActiveTab.tsx +++ b/toolkit/components/AdaptiveTabs/useScrollToActiveTab.tsx @@ -30,6 +30,7 @@ export default function useScrollToActiveTab({ activeTabIndex, tabsRefs, listRef const isWithinFirstPage = containerWidth > left + activeTabWidth; if (isWithinFirstPage) { + listRef.current.scrollTo({ left: 0 }); return; } diff --git a/toolkit/components/AdaptiveTabs/utils.ts b/toolkit/components/AdaptiveTabs/utils.ts new file mode 100644 index 0000000000..cae9be4bbe --- /dev/null +++ b/toolkit/components/AdaptiveTabs/utils.ts @@ -0,0 +1,17 @@ +import type { TabItem, TabItemMenu } from './types'; + +import { middot } from 'lib/html-entities'; + +export const menuButton: TabItemMenu = { + id: 'menu', + title: `${ middot }${ middot }${ middot }`, + component: null, +}; + +export const getTabValue = (tab: TabItem): string => { + if (Array.isArray(tab.id)) { + return tab.id[0]; + } + + return tab.id; +}; diff --git a/toolkit/components/RoutedTabs/RoutedTabs.tsx b/toolkit/components/RoutedTabs/RoutedTabs.tsx new file mode 100644 index 0000000000..078a76e124 --- /dev/null +++ b/toolkit/components/RoutedTabs/RoutedTabs.tsx @@ -0,0 +1,63 @@ +import { pickBy } from 'es-toolkit'; +import { useRouter } from 'next/router'; +import React from 'react'; + +import type { Props as AdaptiveTabsProps } from '../AdaptiveTabs/AdaptiveTabs'; +import AdaptiveTabs from '../AdaptiveTabs/AdaptiveTabs'; +import { getTabValue } from '../AdaptiveTabs/utils'; +import useActiveTabFromQuery from './useActiveTabFromQuery'; + +interface Props extends AdaptiveTabsProps {} + +const RoutedTabs = (props: Props) => { + const { tabs, onValueChange, ...rest } = props; + + const router = useRouter(); + const activeTab = useActiveTabFromQuery(props.tabs); + const tabsRef = React.useRef(null); + + const handleValueChange = React.useCallback(({ value }: { value: string }) => { + const nextTab = tabs.find((tab) => getTabValue(tab) === value); + + if (!nextTab) { + return; + } + + const queryForPathname = pickBy(router.query, (value, key) => router.pathname.includes(`[${ key }]`)); + router.push( + { pathname: router.pathname, query: { ...queryForPathname, tab: value } }, + undefined, + { shallow: true }, + ); + + onValueChange?.({ value }); + }, [ tabs, router, onValueChange ]); + + React.useEffect(() => { + if (router.query.scroll_to_tabs) { + tabsRef?.current?.scrollIntoView(true); + delete router.query.scroll_to_tabs; + router.push( + { + pathname: router.pathname, + query: router.query, + }, + undefined, + { shallow: true }, + ); + } + // replicate componentDidMount + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + + ); +}; + +export default React.memo(RoutedTabs); diff --git a/ui/shared/Tabs/TabsSkeleton.tsx b/toolkit/components/RoutedTabs/RoutedTabsSkeleton.tsx similarity index 54% rename from ui/shared/Tabs/TabsSkeleton.tsx rename to toolkit/components/RoutedTabs/RoutedTabsSkeleton.tsx index eabc8fd352..6c2ec742b6 100644 --- a/ui/shared/Tabs/TabsSkeleton.tsx +++ b/toolkit/components/RoutedTabs/RoutedTabsSkeleton.tsx @@ -1,20 +1,21 @@ -import { Flex, chakra, Box, useColorModeValue } from '@chakra-ui/react'; +import { Flex, chakra, Box } from '@chakra-ui/react'; import React from 'react'; -import type { RoutedTab } from '../Tabs/types'; +import type { TabItemRegular } from '../AdaptiveTabs/types'; -import Skeleton from 'ui/shared/chakra/Skeleton'; -import useTabIndexFromQuery from 'ui/shared/Tabs/useTabIndexFromQuery'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import type { TabsProps } from 'toolkit/chakra/tabs'; -type TabSize = 'sm' | 'md'; +import useActiveTabFromQuery from './useActiveTabFromQuery'; -const SkeletonTabText = ({ size, title }: { size: TabSize; title: RoutedTab['title'] }) => ( +const SkeletonTabText = ({ size, title }: { size: TabsProps['size']; title: TabItemRegular['title'] }) => ( { typeof title === 'string' ? title : title() } @@ -22,18 +23,19 @@ const SkeletonTabText = ({ size, title }: { size: TabSize; title: RoutedTab['tit interface Props { className?: string; - tabs: Array; + tabs: Array; size?: 'sm' | 'md'; } -const TabsSkeleton = ({ className, tabs, size = 'md' }: Props) => { - const bgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50'); - const tabIndex = useTabIndexFromQuery(tabs || []); +const RoutedTabsSkeleton = ({ className, tabs, size = 'md' }: Props) => { + const activeTab = useActiveTabFromQuery(tabs); if (tabs.length === 1) { return null; } + const tabIndex = activeTab ? tabs.findIndex((tab) => tab.id === activeTab.id) : 0; + return ( { tabs.slice(0, tabIndex).map(({ title, id }) => ( @@ -44,7 +46,13 @@ const TabsSkeleton = ({ className, tabs, size = 'md' }: Props) => { /> )) } { tabs.slice(tabIndex, tabIndex + 1).map(({ title, id }) => ( - + { ); }; -export default chakra(TabsSkeleton); +export default chakra(RoutedTabsSkeleton); diff --git a/toolkit/components/RoutedTabs/useActiveTabFromQuery.tsx b/toolkit/components/RoutedTabs/useActiveTabFromQuery.tsx new file mode 100644 index 0000000000..14e88412d0 --- /dev/null +++ b/toolkit/components/RoutedTabs/useActiveTabFromQuery.tsx @@ -0,0 +1,22 @@ +import { useRouter } from 'next/router'; + +import type { TabItem } from '../AdaptiveTabs/types'; + +import getQueryParamString from 'lib/router/getQueryParamString'; + +export default function useActiveTabFromQuery(tabs: Array) { + const router = useRouter(); + const tabFromQuery = getQueryParamString(router.query.tab); + + if (!tabFromQuery) { + return; + } + + return tabs.find((tab) => { + if (Array.isArray(tab.id)) { + return tab.id.includes(tabFromQuery); + } + + return tab.id === tabFromQuery || ('subTabs' in tab && tab.subTabs?.some((id) => id === tabFromQuery)); + }); +} diff --git a/toolkit/hooks/useClipboard.tsx b/toolkit/hooks/useClipboard.tsx new file mode 100644 index 0000000000..e7f1ba9d30 --- /dev/null +++ b/toolkit/hooks/useClipboard.tsx @@ -0,0 +1,56 @@ +import { useCopyToClipboard } from '@uidotdev/usehooks'; +import React from 'react'; + +import { SECOND } from 'lib/consts'; +import useIsMobile from 'lib/hooks/useIsMobile'; + +import { useDisclosure } from './useDisclosure'; + +// NOTE: If you don't need the disclosure and the timeout features, please use the useCopyToClipboard hook directly +export default function useClipboard(text: string, timeout = SECOND) { + const flagTimeoutRef = React.useRef(null); + const disclosureTimeoutRef = React.useRef(null); + const [ hasCopied, setHasCopied ] = React.useState(false); + + const isMobile = useIsMobile(); + const [ , copyToClipboard ] = useCopyToClipboard(); + const { open, onOpenChange } = useDisclosure(); + + const copy = React.useCallback(() => { + copyToClipboard(text); + setHasCopied(true); + // there is no hover on mobile, so we need to open the disclosure manually after click + isMobile && onOpenChange({ open: true }); + + disclosureTimeoutRef.current = window.setTimeout(() => { + onOpenChange({ open: false }); + }, timeout); + + // We need to wait for the disclosure to close before setting the flag to false + flagTimeoutRef.current = window.setTimeout(() => { + setHasCopied(false); + }, timeout + 200); + }, [ text, copyToClipboard, timeout, onOpenChange, isMobile ]); + + React.useEffect(() => { + return () => { + if (disclosureTimeoutRef.current) { + window.clearTimeout(disclosureTimeoutRef.current); + } + if (flagTimeoutRef.current) { + window.clearTimeout(flagTimeoutRef.current); + } + }; + }, []); + + return React.useMemo(() => { + return { + hasCopied, + copy, + disclosure: { + open, + onOpenChange, + }, + }; + }, [ hasCopied, copy, open, onOpenChange ]); +} diff --git a/toolkit/hooks/useDisclosure.tsx b/toolkit/hooks/useDisclosure.tsx new file mode 100644 index 0000000000..6e4cbc6f65 --- /dev/null +++ b/toolkit/hooks/useDisclosure.tsx @@ -0,0 +1,24 @@ +/* eslint-disable no-restricted-imports */ +import type { UseDisclosureProps } from '@chakra-ui/react'; +import { useDisclosure as useDisclosureChakra } from '@chakra-ui/react'; +import React from 'react'; + +export function useDisclosure(props?: UseDisclosureProps) { + const { open, onOpen, onClose, onToggle } = useDisclosureChakra(props); + + const onOpenChange = React.useCallback(({ open }: { open: boolean }) => { + if (open) { + onOpen(); + } else { + onClose(); + } + }, [ onOpen, onClose ]); + + return React.useMemo(() => ({ + open, + onOpenChange, + onClose, + onOpen, + onToggle, + }), [ open, onOpenChange, onClose, onOpen, onToggle ]); +} diff --git a/toolkit/hooks/useViewportSize.tsx b/toolkit/hooks/useViewportSize.tsx new file mode 100644 index 0000000000..d494bf806b --- /dev/null +++ b/toolkit/hooks/useViewportSize.tsx @@ -0,0 +1,22 @@ +import { debounce } from 'es-toolkit'; +import { useEffect, useState } from 'react'; + +export default function useViewportSize(debounceTime = 100) { + const [ viewportSize, setViewportSize ] = useState({ width: 0, height: 0 }); + + useEffect(() => { + setViewportSize({ width: window.innerWidth, height: window.innerHeight }); + + const resizeHandler = debounce(() => { + setViewportSize({ width: window.innerWidth, height: window.innerHeight }); + }, debounceTime); + const resizeObserver = new ResizeObserver(resizeHandler); + + resizeObserver.observe(document.body); + return function cleanup() { + resizeObserver.unobserve(document.body); + }; + }, [ debounceTime ]); + + return viewportSize; +} diff --git a/toolkit/theme/foundations/animations.ts b/toolkit/theme/foundations/animations.ts new file mode 100644 index 0000000000..0d46030a62 --- /dev/null +++ b/toolkit/theme/foundations/animations.ts @@ -0,0 +1,12 @@ +export const keyframes = { + fromLeftToRight: { + from: { + left: '0%', + transform: 'translateX(0%)', + }, + to: { + left: '100%', + transform: 'translateX(-100%)', + }, + }, +}; diff --git a/toolkit/theme/foundations/borders.ts b/toolkit/theme/foundations/borders.ts new file mode 100644 index 0000000000..e048050ca2 --- /dev/null +++ b/toolkit/theme/foundations/borders.ts @@ -0,0 +1,13 @@ +import type { ThemingConfig } from '@chakra-ui/react'; + +import type { ExcludeUndefined } from 'types/utils'; + +export const radii: ExcludeUndefined['radii'] = { + none: { value: '0' }, + sm: { value: '4px' }, + base: { value: '8px' }, + md: { value: '12px' }, + lg: { value: '16px' }, + xl: { value: '24px' }, + full: { value: '9999px' }, +}; diff --git a/theme/foundations/breakpoints.ts b/toolkit/theme/foundations/breakpoints.ts similarity index 79% rename from theme/foundations/breakpoints.ts rename to toolkit/theme/foundations/breakpoints.ts index ece6201c12..3515f2c3ed 100644 --- a/theme/foundations/breakpoints.ts +++ b/toolkit/theme/foundations/breakpoints.ts @@ -1,7 +1,5 @@ const breakpoints = { -// maybe we need them in future sm: '415px', - // md: '768px', lg: '1000px', xl: '1440px', '2xl': '1920px', diff --git a/toolkit/theme/foundations/colors.ts b/toolkit/theme/foundations/colors.ts new file mode 100644 index 0000000000..a957d1c37c --- /dev/null +++ b/toolkit/theme/foundations/colors.ts @@ -0,0 +1,162 @@ +const colors = { + green: { + '50': { value: '#F0FFF4' }, + '100': { value: '#C6F6D5' }, + '200': { value: '#9AE6B4' }, + '300': { value: '#68D391' }, + '400': { value: '#48BB78' }, + '500': { value: '#38A169' }, + '600': { value: '#25855A' }, + '700': { value: '#276749' }, + '800': { value: '#22543D' }, + '900': { value: '#1C4532' }, + }, + blue: { + '50': { value: '#EBF8FF' }, + '100': { value: '#BEE3F8' }, + '200': { value: '#90CDF4' }, + '300': { value: '#63B3ED' }, + '400': { value: '#4299E1' }, + '500': { value: '#3182CE' }, + '600': { value: '#2B6CB0' }, + '700': { value: '#2C5282' }, + '800': { value: '#2A4365' }, + '900': { value: '#1A365D' }, + }, + red: { + '50': { value: '#FFF5F5' }, + '100': { value: '#FED7D7' }, + '200': { value: '#FEB2B2' }, + '300': { value: '#FC8181' }, + '400': { value: '#F56565' }, + '500': { value: '#E53E3E' }, + '600': { value: '#C53030' }, + '700': { value: '#9B2C2C' }, + '800': { value: '#822727' }, + '900': { value: '#63171B' }, + }, + orange: { + '50': { value: '#FFFAF0' }, + '100': { value: '#FEEBCB' }, + '200': { value: '#FBD38D' }, + '300': { value: '#F6AD55' }, + '400': { value: '#ED8936' }, + '500': { value: '#DD6B20' }, + '600': { value: '#C05621' }, + '700': { value: '#9C4221' }, + '800': { value: '#7B341E' }, + '900': { value: '#652B19' }, + }, + yellow: { + '50': { value: '#FFFFF0' }, + '100': { value: '#FEFCBF' }, + '200': { value: '#FAF089' }, + '300': { value: '#F6E05E' }, + '400': { value: '#ECC94B' }, + '500': { value: '#D69E2E' }, + '600': { value: '#B7791F' }, + '700': { value: '#975A16' }, + '800': { value: '#744210' }, + '900': { value: '#5F370E' }, + }, + gray: { + '50': { value: '#F7FAFC' }, + '100': { value: '#EDF2F7' }, + '200': { value: '#E2E8F0' }, + '300': { value: '#CBD5E0' }, + '400': { value: '#A0AEC0' }, + '500': { value: '#718096' }, + '600': { value: '#4A5568' }, + '700': { value: '#2D3748' }, + '800': { value: '#1A202C' }, + '900': { value: '#171923' }, + }, + teal: { + '50': { value: '#E6FFFA' }, + '100': { value: '#B2F5EA' }, + '200': { value: '#81E6D9' }, + '300': { value: '#4FD1C5' }, + '400': { value: '#38B2AC' }, + '500': { value: '#319795' }, + '600': { value: '#2C7A7B' }, + '700': { value: '#285E61' }, + '800': { value: '#234E52' }, + '900': { value: '#1D4044' }, + }, + cyan: { + '50': { value: '#EDFDFD' }, + '100': { value: '#C4F1F9' }, + '200': { value: '#9DECF9' }, + '300': { value: '#76E4F7' }, + '400': { value: '#0BC5EA' }, + '500': { value: '#00B5D8' }, + '600': { value: '#00A3C4' }, + '700': { value: '#0987A0' }, + '800': { value: '#086F83' }, + '900': { value: '#065666' }, + }, + purple: { + '50': { value: '#FAF5FF' }, + '100': { value: '#E9D8FD' }, + '200': { value: '#D6BCFA' }, + '300': { value: '#B794F4' }, + '400': { value: '#9F7AEA' }, + '500': { value: '#805AD5' }, + '600': { value: '#6B46C1' }, + '700': { value: '#553C9A' }, + '800': { value: '#44337A' }, + '900': { value: '#322659' }, + }, + pink: { + '50': { value: '#FFF5F7' }, + '100': { value: '#FED7E2' }, + '200': { value: '#FBB6CE' }, + '300': { value: '#F687B3' }, + '400': { value: '#ED64A6' }, + '500': { value: '#D53F8C' }, + '600': { value: '#B83280' }, + '700': { value: '#97266D' }, + '800': { value: '#702459' }, + '900': { value: '#521B41' }, + }, + black: { value: '#101112' }, + white: { value: '#ffffff' }, + whiteAlpha: { + '50': { value: 'RGBA(255, 255, 255, 0.04)' }, + '100': { value: 'RGBA(255, 255, 255, 0.06)' }, + '200': { value: 'RGBA(255, 255, 255, 0.08)' }, + '300': { value: 'RGBA(255, 255, 255, 0.16)' }, + '400': { value: 'RGBA(255, 255, 255, 0.24)' }, + '500': { value: 'RGBA(255, 255, 255, 0.36)' }, + '600': { value: 'RGBA(255, 255, 255, 0.48)' }, + '700': { value: 'RGBA(255, 255, 255, 0.64)' }, + '800': { value: 'RGBA(255, 255, 255, 0.80)' }, + '900': { value: 'RGBA(255, 255, 255, 0.92)' }, + }, + blackAlpha: { + '50': { value: 'RGBA(16, 17, 18, 0.04)' }, + '100': { value: 'RGBA(16, 17, 18, 0.06)' }, + '200': { value: 'RGBA(16, 17, 18, 0.08)' }, + '300': { value: 'RGBA(16, 17, 18, 0.16)' }, + '400': { value: 'RGBA(16, 17, 18, 0.24)' }, + '500': { value: 'RGBA(16, 17, 18, 0.36)' }, + '600': { value: 'RGBA(16, 17, 18, 0.48)' }, + '700': { value: 'RGBA(16, 17, 18, 0.64)' }, + '800': { value: 'RGBA(16, 17, 18, 0.80)' }, + '900': { value: 'RGBA(16, 17, 18, 0.92)' }, + }, + // BRAND COLORS + github: { value: '#171923' }, + telegram: { value: '#2775CA' }, + linkedin: { value: '#1564BA' }, + discord: { value: '#9747FF' }, + slack: { value: '#1BA27A' }, + twitter: { value: '#000000' }, + opensea: { value: '#2081E2' }, + facebook: { value: '#4460A0' }, + medium: { value: '#231F20' }, + reddit: { value: '#FF4500' }, + celo: { value: '#FCFF52' }, +}; + +export default colors; diff --git a/toolkit/theme/foundations/durations.ts b/toolkit/theme/foundations/durations.ts new file mode 100644 index 0000000000..bc60ecad4a --- /dev/null +++ b/toolkit/theme/foundations/durations.ts @@ -0,0 +1,15 @@ +import type { ThemingConfig } from '@chakra-ui/react'; + +import type { ExcludeUndefined } from 'types/utils'; + +const durations: ExcludeUndefined['durations'] = { + 'ultra-fast': { value: '50ms' }, + faster: { value: '100ms' }, + fast: { value: '150ms' }, + normal: { value: '200ms' }, + slow: { value: '300ms' }, + slower: { value: '400ms' }, + 'ultra-slow': { value: '500ms' }, +}; + +export default durations; diff --git a/toolkit/theme/foundations/semanticTokens.ts b/toolkit/theme/foundations/semanticTokens.ts new file mode 100644 index 0000000000..b0b43dbe55 --- /dev/null +++ b/toolkit/theme/foundations/semanticTokens.ts @@ -0,0 +1,471 @@ +import type { ThemingConfig } from '@chakra-ui/react'; + +import config from 'configs/app'; + +const heroBannerButton = config.UI.homepage.heroBanner?.button; + +const semanticTokens: ThemingConfig['semanticTokens'] = { + colors: { + button: { + outline: { + fg: { + DEFAULT: { value: { _light: '{colors.blue.600}', _dark: '{colors.blue.600}' } }, + }, + }, + subtle: { + fg: { + DEFAULT: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.whiteAlpha.800}' } }, + }, + bg: { + DEFAULT: { value: { _light: '{colors.blackAlpha.200}', _dark: '{colors.whiteAlpha.200}' } }, + }, + }, + dropdown: { + fg: { + DEFAULT: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.whiteAlpha.800}' } }, + selected: { value: { _light: '{colors.blue.700}', _dark: '{colors.whiteAlpha.800}' } }, + }, + bg: { + selected: { value: { _light: '{colors.blue.50}', _dark: '{colors.whiteAlpha.100}' } }, + }, + border: { + DEFAULT: { value: { _light: '{colors.gray.200}', _dark: '{colors.gray.600}' } }, + }, + }, + header: { + fg: { + DEFAULT: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.gray.400}' } }, + selected: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.whiteAlpha.800}' } }, + highlighted: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.whiteAlpha.800}' } }, + }, + bg: { + selected: { value: { _light: '{colors.blackAlpha.50}', _dark: '{colors.whiteAlpha.100}' } }, + highlighted: { value: { _light: '{colors.orange.100}', _dark: '{colors.orange.900}' } }, + }, + border: { + DEFAULT: { value: { _light: '{colors.gray.300}', _dark: '{colors.gray.600}' } }, + }, + }, + segmented: { + fg: { + DEFAULT: { value: { _light: '{colors.blue.600}', _dark: '{colors.blue.300}' } }, + selected: { value: { _light: '{colors.blue.700}', _dark: '{colors.gray.50}' } }, + }, + border: { + DEFAULT: { value: { _light: '{colors.blue.50}', _dark: '{colors.gray.800}' } }, + }, + }, + icon_secondary: { + fg: { + DEFAULT: { value: { _light: '{colors.gray.400}', _dark: '{colors.gray.500}' } }, + selected: { value: { _light: '{colors.blue.700}', _dark: '{colors.whiteAlpha.800}' } }, + }, + bg: { + selected: { value: { _light: '{colors.blue.50}', _dark: '{colors.whiteAlpha.100}' } }, + }, + }, + hero: { + bg: { + DEFAULT: { + value: { + _light: heroBannerButton?._default?.background?.[0] || '{colors.blue.600}', + _dark: heroBannerButton?._default?.background?.[1] || heroBannerButton?._default?.background?.[0] || '{colors.blue.600}', + }, + }, + hover: { + value: { + _light: heroBannerButton?._hover?.background?.[0] || '{colors.blue.400}', + _dark: heroBannerButton?._hover?.background?.[1] || heroBannerButton?._hover?.background?.[0] || '{colors.blue.400}', + }, + }, + selected: { + value: { + _light: heroBannerButton?._selected?.background?.[0] || '{colors.blue.50}', + _dark: heroBannerButton?._selected?.background?.[1] || heroBannerButton?._selected?.background?.[0] || '{colors.blue.50}', + }, + }, + }, + fg: { + DEFAULT: { + value: { + _light: heroBannerButton?._default?.text_color?.[0] || '{colors.white}', + _dark: heroBannerButton?._default?.text_color?.[1] || heroBannerButton?._default?.text_color?.[0] || '{colors.white}', + }, + }, + hover: { + value: { + _light: heroBannerButton?._hover?.text_color?.[0] || '{colors.white}', + _dark: heroBannerButton?._hover?.text_color?.[1] || heroBannerButton?._hover?.text_color?.[0] || '{colors.white}', + }, + }, + selected: { + value: { + _light: heroBannerButton?._selected?.text_color?.[0] || '{colors.blackAlpha.800}', + _dark: heroBannerButton?._selected?.text_color?.[1] || heroBannerButton?._selected?.text_color?.[0] || '{colors.blackAlpha.800}', + }, + }, + }, + }, + }, + closeButton: { + fg: { + DEFAULT: { value: { _light: '{colors.blackAlpha.500}', _dark: '{colors.whiteAlpha.500}' } }, + }, + }, + link: { + primary: { + DEFAULT: { value: { _light: '{colors.blue.600}', _dark: '{colors.blue.300}' } }, + hover: { value: { _light: '{colors.blue.400}' } }, + }, + secondary: { + DEFAULT: { value: { _light: '{colors.gray.500}', _dark: '{colors.gray.400}' } }, + }, + underlaid: { + bg: { value: { _light: '{colors.gray.100}', _dark: '{colors.gray.800}' } }, + }, + subtle: { + DEFAULT: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.gray.400}' } }, + hover: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.gray.400}' } }, + }, + navigation: { + fg: { + DEFAULT: { value: { _light: '{colors.gray.600}', _dark: '{colors.gray.400}' } }, + selected: { value: { _light: '{colors.blue.700}', _dark: '{colors.gray.50}' } }, + hover: { value: { _light: '{colors.link.primary.hover}' } }, + active: { value: { _light: '{colors.link.primary.hover}' } }, + }, + bg: { + selected: { value: { _light: '{colors.blue.50}', _dark: '{colors.gray.800}' } }, + group: { value: { _light: '{colors.white}', _dark: '{colors.black}' } }, + }, + }, + menu: { + DEFAULT: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.whiteAlpha.800}' } }, + }, + }, + tooltip: { + DEFAULT: { + bg: { value: '{colors.gray.900}' }, + fg: { value: '{colors.white}' }, + }, + navigation: { + bg: { value: { _light: '{colors.blue.50}', _dark: '{colors.gray.800}' } }, + fg: { + DEFAULT: { value: '{colors.blue.400}' }, + selected: { value: { _light: '{colors.blue.700}', _dark: '{colors.gray.50}' } }, + }, + }, + }, + popover: { + DEFAULT: { + bg: { value: { _light: '{colors.white}', _dark: '{colors.gray.900}' } }, + shadow: { value: { _light: '{colors.blackAlpha.200}', _dark: '{colors.whiteAlpha.300}' } }, + }, + }, + progressCircle: { + trackColor: { + DEFAULT: { value: { _light: '{colors.gray.100}', _dark: '{colors.whiteAlpha.100}' } }, + }, + }, + skeleton: { + bg: { + start: { value: { _light: '{colors.blackAlpha.50}', _dark: '{colors.whiteAlpha.50}' } }, + end: { value: { _light: '{colors.blackAlpha.100}', _dark: '{colors.whiteAlpha.100}' } }, + }, + }, + tabs: { + solid: { + fg: { + DEFAULT: { value: { _light: '{colors.blue.700}', _dark: '{colors.blue.100}' } }, + selected: { value: { _light: '{colors.blue.700}', _dark: '{colors.gray.50}' } }, + }, + bg: { + selected: { value: { _light: '{colors.blue.50}', _dark: '{colors.whiteAlpha.100}' } }, + }, + }, + secondary: { + fg: { + DEFAULT: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.whiteAlpha.800}' } }, + }, + bg: { + selected: { value: { _light: '{colors.blue.50}', _dark: '{colors.whiteAlpha.100}' } }, + }, + border: { + DEFAULT: { value: { _light: '{colors.gray.300}', _dark: '{colors.gray.600}' } }, + }, + }, + segmented: { + fg: { + DEFAULT: { value: { _light: '{colors.blue.600}', _dark: '{colors.blue.300}' } }, + selected: { value: { _light: '{colors.blue.700}', _dark: '{colors.gray.50}' } }, + }, + border: { + DEFAULT: { value: { _light: '{colors.blue.50}', _dark: '{colors.gray.800}' } }, + }, + }, + }, + 'switch': { + primary: { + bg: { + DEFAULT: { value: { _light: '{colors.gray.300}', _dark: '{colors.whiteAlpha.400}' } }, + checked: { value: { _light: '{colors.blue.500}', _dark: '{colors.blue.300}' } }, + hover: { value: { _light: '{colors.blue.600}', _dark: '{colors.blue.400}' } }, + }, + }, + }, + alert: { + fg: { + DEFAULT: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.whiteAlpha.800}' } }, + }, + bg: { + info: { value: { _light: '{colors.blackAlpha.50}', _dark: '{colors.whiteAlpha.100}' } }, + warning: { value: { _light: '{colors.orange.100}', _dark: '{colors.orange.800/60}' } }, + warning_table: { value: { _light: '{colors.orange.50}', _dark: '{colors.orange.800/60}' } }, + success: { value: { _light: '{colors.green.100}', _dark: '{colors.green.900}' } }, + error: { value: { _light: '{colors.red.100}', _dark: '{colors.red.900}' } }, + }, + }, + toast: { + fg: { + DEFAULT: { value: '{colors.alert.fg}' }, + }, + bg: { + DEFAULT: { value: '{colors.alert.bg.info}' }, + info: { value: { _light: '{colors.blue.100}', _dark: '{colors.blue.900}' } }, + warning: { value: '{colors.alert.bg.warning}' }, + success: { value: '{colors.alert.bg.success}' }, + error: { value: '{colors.alert.bg.error}' }, + loading: { value: { _light: '{colors.blue.100}', _dark: '{colors.blue.900}' } }, + }, + }, + input: { + fg: { + DEFAULT: { value: { _light: '{colors.gray.800}', _dark: '{colors.gray.50}' } }, + error: { value: '{colors.text.error}' }, + }, + bg: { + DEFAULT: { value: { _light: '{colors.white}', _dark: '{colors.black}' } }, + readOnly: { value: { _light: '{colors.gray.200}', _dark: '{colors.gray.800}' } }, + }, + border: { + DEFAULT: { value: { _light: '{colors.gray.100}', _dark: '{colors.gray.700}' } }, + hover: { value: { _light: '{colors.gray.200}', _dark: '{colors.gray.500}' } }, + focus: { value: '{colors.blue.400}' }, + filled: { value: { _light: '{colors.gray.300}', _dark: '{colors.gray.600}' } }, + readOnly: { value: { _light: '{colors.gray.200}', _dark: '{colors.gray.800}' } }, + error: { value: '{colors.red.500}' }, + }, + placeholder: { + DEFAULT: { value: '{colors.gray.500}' }, + error: { value: '{colors.red.500}' }, + }, + }, + field: { + placeholder: { + DEFAULT: { value: '{colors.gray.500}' }, + disabled: { value: '{colors.gray.500/20}' }, + error: { value: '{colors.red.500}' }, + }, + }, + dialog: { + bg: { + DEFAULT: { value: { _light: '{colors.white}', _dark: '{colors.gray.900}' } }, + }, + fg: { + DEFAULT: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.whiteAlpha.800}' } }, + }, + }, + drawer: { + bg: { + DEFAULT: { value: { _light: '{colors.white}', _dark: '{colors.gray.900}' } }, + }, + }, + select: { + trigger: { + outline: { + fg: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.whiteAlpha.800}' } }, + }, + }, + item: { + bg: { + highlighted: { value: { _light: '{colors.blue.50}', _dark: '{colors.whiteAlpha.100}' } }, + }, + }, + indicator: { + fg: { + DEFAULT: { value: '{colors.gray.500}' }, + }, + }, + placeholder: { + fg: { + DEFAULT: { value: '{colors.gray.500}' }, + error: { value: '{colors.red.500}' }, + }, + }, + }, + menu: { + item: { + bg: { + highlighted: { value: { _light: '{colors.blue.50}', _dark: '{colors.whiteAlpha.100}' } }, + }, + }, + }, + spinner: { + track: { + DEFAULT: { value: { _light: '{colors.blackAlpha.200}', _dark: '{colors.whiteAlpha.200}' } }, + }, + }, + badge: { + gray: { + bg: { value: { _light: '{colors.blackAlpha.50}', _dark: '{colors.whiteAlpha.100}' } }, + fg: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.whiteAlpha.800}' } }, + }, + green: { + bg: { value: { _light: '{colors.green.50}', _dark: '{colors.green.800}' } }, + fg: { value: { _light: '{colors.green.500}', _dark: '{colors.green.200}' } }, + }, + red: { + bg: { value: { _light: '{colors.red.50}', _dark: '{colors.red.800}' } }, + fg: { value: { _light: '{colors.red.500}', _dark: '{colors.red.200}' } }, + }, + purple: { + bg: { value: { _light: '{colors.purple.50}', _dark: '{colors.purple.800}' } }, + fg: { value: { _light: '{colors.purple.500}', _dark: '{colors.purple.100}' } }, + }, + purple_alt: { + bg: { value: { _light: '{colors.purple.100}', _dark: '{colors.purple.800}' } }, + fg: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.whiteAlpha.800}' } }, + }, + orange: { + bg: { value: { _light: '{colors.orange.50}', _dark: '{colors.orange.800}' } }, + fg: { value: { _light: '{colors.orange.500}', _dark: '{colors.orange.100}' } }, + }, + blue: { + bg: { value: { _light: '{colors.blue.50}', _dark: '{colors.blue.800}' } }, + fg: { value: { _light: '{colors.blue.500}', _dark: '{colors.blue.100}' } }, + }, + blue_alt: { + bg: { value: { _light: '{colors.blue.50}', _dark: '{colors.blue.800}' } }, + fg: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.whiteAlpha.800}' } }, + }, + yellow: { + bg: { value: { _light: '{colors.yellow.50}', _dark: '{colors.yellow.800}' } }, + fg: { value: { _light: '{colors.yellow.500}', _dark: '{colors.yellow.100}' } }, + }, + teal: { + bg: { value: { _light: '{colors.teal.50}', _dark: '{colors.teal.800}' } }, + fg: { value: { _light: '{colors.teal.500}', _dark: '{colors.teal.100}' } }, + }, + cyan: { + bg: { value: { _light: '{colors.cyan.50}', _dark: '{colors.cyan.800}' } }, + fg: { value: { _light: '{colors.cyan.500}', _dark: '{colors.cyan.100}' } }, + }, + }, + tag: { + root: { + subtle: { + bg: { value: { _light: '{colors.blackAlpha.50}', _dark: '{colors.whiteAlpha.100}' } }, + fg: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.whiteAlpha.800}' } }, + }, + clickable: { + bg: { value: { _light: '{colors.gray.100}', _dark: '{colors.gray.800}' } }, + fg: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.whiteAlpha.800}' } }, + }, + filter: { + bg: { value: { _light: '{colors.blue.50}', _dark: '{colors.blue.800}' } }, + }, + select: { + bg: { + DEFAULT: { value: { _light: '{colors.gray.100}', _dark: '{colors.gray.800}' } }, + selected: { value: { _light: '{colors.blue.500}', _dark: '{colors.blue.900}' } }, + }, + fg: { value: { _light: '{colors.gray.500}', _dark: '{colors.whiteAlpha.800}' } }, + }, + }, + }, + table: { + header: { + bg: { value: { _light: '{colors.blackAlpha.100}', _dark: '{colors.whiteAlpha.200}' } }, + fg: { value: { _light: '{colors.blackAlpha.700}', _dark: '{colors.whiteAlpha.700}' } }, + }, + }, + checkbox: { + control: { + border: { + DEFAULT: { value: { _light: '{colors.gray.100}', _dark: '{colors.gray.700}' } }, + hover: { value: { _light: '{colors.gray.200}', _dark: '{colors.gray.500}' } }, + readOnly: { value: { _light: '{colors.gray.200}', _dark: '{colors.gray.800}' } }, + }, + }, + }, + radio: { + control: { + border: { + DEFAULT: { value: { _light: '{colors.gray.100}', _dark: '{colors.gray.700}' } }, + hover: { value: { _light: '{colors.gray.200}', _dark: '{colors.gray.500}' } }, + readOnly: { value: { _light: '{colors.gray.200}', _dark: '{colors.gray.800}' } }, + }, + }, + }, + stat: { + indicator: { + up: { value: { _light: '{colors.green.500}', _dark: '{colors.green.400}' } }, + down: { value: { _light: '{colors.red.600}', _dark: '{colors.red.400}' } }, + }, + }, + rating: { + DEFAULT: { value: { _light: '{colors.gray.200}', _dark: '{colors.gray.700}' } }, + highlighted: { value: '{colors.yellow.400}' }, + }, + heading: { + DEFAULT: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.whiteAlpha.800}' } }, + }, + text: { + primary: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.whiteAlpha.800}' } }, + secondary: { value: { _light: '{colors.gray.500}', _dark: '{colors.gray.400}' } }, + error: { value: '{colors.red.500}' }, + }, + border: { + divider: { value: { _light: '{colors.blackAlpha.100}', _dark: '{colors.whiteAlpha.100}' } }, + error: { value: '{colors.red.500}' }, + }, + icon: { + backTo: { value: '{colors.gray.400}' }, + externalLink: { value: { _light: '{colors.gray.400}', _dark: '{colors.gray.500}' } }, + info: { value: { _light: '{colors.gray.400}', _dark: '{colors.gray.500}' } }, + }, + address: { + highlighted: { + bg: { value: { _light: '{colors.blue.50}', _dark: '{colors.blue.900}' } }, + border: { value: { _light: '{colors.blue.200}', _dark: '{colors.blue.600}' } }, + }, + }, + global: { + body: { + bg: { value: { _light: '{colors.white}', _dark: '{colors.black}' } }, + fg: { value: '{colors.text.primary}' }, + }, + mark: { + bg: { value: { _light: '{colors.green.100}', _dark: '{colors.green.800}' } }, + }, + scrollbar: { + thumb: { value: { _light: '{colors.blackAlpha.300}', _dark: '{colors.whiteAlpha.300}' } }, + }, + }, + }, + shadows: { + popover: { + DEFAULT: { value: { _light: '{shadows.size.2xl}', _dark: '{shadows.dark-lg}' } }, + }, + drawer: { + DEFAULT: { value: { _light: '{shadows.size.lg}', _dark: '{shadows.dark-lg}' } }, + }, + }, + opacity: { + control: { + disabled: { value: '0.2' }, + }, + }, +}; + +export default semanticTokens; diff --git a/toolkit/theme/foundations/shadows.ts b/toolkit/theme/foundations/shadows.ts new file mode 100644 index 0000000000..3d7b674e4a --- /dev/null +++ b/toolkit/theme/foundations/shadows.ts @@ -0,0 +1,19 @@ +import type { ThemingConfig } from '@chakra-ui/react'; + +import type { ExcludeUndefined } from 'types/utils'; + +const shadows: ExcludeUndefined['shadows'] = { + action_bar: { value: '0 4px 4px -4px rgb(0 0 0 / 10%), 0 2px 4px -4px rgb(0 0 0 / 6%)' }, + size: { + xs: { value: '0px 0px 0px 1px rgba(0, 0, 0, 0.05)' }, + sm: { value: '0px 1px 2px 0px rgba(0, 0, 0, 0.05)' }, + base: { value: '0px 1px 2px 0px rgba(0, 0, 0, 0.06), 0px 1px 3px 0px rgba(0, 0, 0, 0.1)' }, + md: { value: '0px 2px 4px -1px rgba(0, 0, 0, 0.06), 0px 4px 6px -1px rgba(0, 0, 0, 0.1)' }, + lg: { value: '0px 4px 6px -2px rgba(0, 0, 0, 0.05), 0px 10px 15px -3px rgba(0, 0, 0, 0.1)' }, + xl: { value: '0px 10px 10px -5px rgba(0, 0, 0, 0.04), 0px 20px 25px -5px rgba(0, 0, 0, 0.1)' }, + '2xl': { value: '0px 15px 50px -12px rgba(0, 0, 0, 0.25)' }, + }, + 'dark-lg': { value: '0px 15px 40px 0px rgba(0, 0, 0, 0.4), 0px 5px 10px 0px rgba(0, 0, 0, 0.2), 0px 0px 0px 1px rgba(0, 0, 0, 0.1)' }, +}; + +export default shadows; diff --git a/toolkit/theme/foundations/typography.ts b/toolkit/theme/foundations/typography.ts new file mode 100644 index 0000000000..8d859e7540 --- /dev/null +++ b/toolkit/theme/foundations/typography.ts @@ -0,0 +1,93 @@ +import type { ThemingConfig } from '@chakra-ui/react'; + +import type { ExcludeUndefined } from 'types/utils'; + +import config from 'configs/app'; + +export const BODY_TYPEFACE = config.UI.fonts.body?.name ?? 'Inter'; +export const HEADING_TYPEFACE = config.UI.fonts.heading?.name ?? 'Poppins'; + +export const fonts: ExcludeUndefined['fonts'] = { + heading: { value: `${ HEADING_TYPEFACE }, sans-serif` }, + body: { value: `${ BODY_TYPEFACE }, sans-serif` }, +}; + +export const textStyles: ThemingConfig['textStyles'] = { + heading: { + xl: { + value: { + fontSize: '32px', + lineHeight: '40px', + fontWeight: '500', + letterSpacing: '-0.5px', + fontFamily: 'heading', + }, + }, + lg: { + value: { + fontSize: '24px', + lineHeight: '32px', + fontWeight: '500', + fontFamily: 'heading', + }, + }, + md: { + value: { + fontSize: '18px', + lineHeight: '24px', + fontWeight: '500', + fontFamily: 'heading', + }, + }, + sm: { + value: { + fontSize: '16px', + lineHeight: '24px', + fontWeight: '500', + fontFamily: 'heading', + }, + }, + xs: { + value: { + fontSize: '14px', + lineHeight: '20px', + fontWeight: '600', + fontFamily: 'heading', + }, + }, + }, + text: { + xl: { + value: { + fontSize: '20px', + lineHeight: '28px', + fontWeight: '400', + fontFamily: 'body', + }, + }, + md: { + value: { + fontSize: '16px', + lineHeight: '24px', + fontWeight: '400', + fontFamily: 'body', + }, + }, + sm: { + value: { + fontSize: '14px', + lineHeight: '20px', + fontWeight: '400', + fontFamily: 'body', + }, + }, + xs: { + value: { + fontSize: '12px', + lineHeight: '16px', + fontWeight: '400', + fontFamily: 'body', + }, + }, + }, +}; diff --git a/toolkit/theme/foundations/zIndex.ts b/toolkit/theme/foundations/zIndex.ts new file mode 100644 index 0000000000..20d0f3f960 --- /dev/null +++ b/toolkit/theme/foundations/zIndex.ts @@ -0,0 +1,20 @@ +export const zIndex = { + hide: { value: -1 }, + auto: { value: 'auto' }, + base: { value: 0 }, + docked: { value: 10 }, + dropdown: { value: 1000 }, + sticky: { value: 1100 }, + sticky1: { value: 1101 }, + sticky2: { value: 1102 }, + banner: { value: 1200 }, + overlay: { value: 1300 }, + modal: { value: 1400 }, + popover: { value: 1500 }, + tooltip: { value: 1550 }, // otherwise tooltips will not be visible in modals + tooltip2: { value: 1551 }, // for tooltips in tooltips + skipLink: { value: 1600 }, + toast: { value: 1700 }, +}; + +export default zIndex; diff --git a/toolkit/theme/globalCss.ts b/toolkit/theme/globalCss.ts new file mode 100644 index 0000000000..5dec641b57 --- /dev/null +++ b/toolkit/theme/globalCss.ts @@ -0,0 +1,57 @@ +import type { SystemConfig } from '@chakra-ui/react'; + +import addressEntity from './globals/address-entity'; +import recaptcha from './globals/recaptcha'; +import scrollbar from './globals/scrollbar'; + +const webkitAutofillOverrides = { + WebkitTextFillColor: 'var(--chakra-colors-input-fg)', + '-webkit-box-shadow': '0 0 0px 1000px var(--chakra-colors-input-bg) inset', + transition: 'background-color 5000s ease-in-out 0s', +}; + +const webkitAutofillRules = { + '&:-webkit-autofill': webkitAutofillOverrides, + '&:-webkit-autofill:hover': webkitAutofillOverrides, + '&:-webkit-autofill:focus': webkitAutofillOverrides, +}; + +const globalCss: SystemConfig['globalCss'] = { + body: { + bg: 'global.body.bg', + color: 'global.body.fg', + WebkitTapHighlightColor: 'transparent', + fontVariantLigatures: 'no-contextual', + focusRingStyle: 'hidden', + }, + mark: { + bg: 'global.mark.bg', + color: 'inherit', + }, + 'svg *::selection': { + color: 'none', + background: 'none', + }, + form: { + w: '100%', + }, + input: { + // hide number input arrows in Google Chrome + '&::-webkit-outer-spin-button, &::-webkit-inner-spin-button': { + WebkitAppearance: 'none', + margin: 0, + }, + ...webkitAutofillRules, + }, + textarea: { + ...webkitAutofillRules, + }, + select: { + ...webkitAutofillRules, + }, + ...recaptcha, + ...scrollbar, + ...addressEntity, +}; + +export default globalCss; diff --git a/toolkit/theme/globals/address-entity.ts b/toolkit/theme/globals/address-entity.ts new file mode 100644 index 0000000000..4f1ee388e7 --- /dev/null +++ b/toolkit/theme/globals/address-entity.ts @@ -0,0 +1,33 @@ +const styles = { + '.address-entity': { + '&.address-entity_highlighted': { + _before: { + content: `" "`, + position: 'absolute', + py: 1, + pl: 1, + pr: 0, + top: '-5px', + left: '-5px', + width: `calc(100% + 6px)`, + height: 'calc(100% + 10px)', + borderRadius: 'base', + borderColor: 'address.highlighted.border', + borderWidth: '1px', + borderStyle: 'dashed', + bgColor: 'address.highlighted.bg', + zIndex: -1, + }, + }, + }, + '.address-entity_no-copy': { + '&.address-entity_highlighted': { + _before: { + pr: 2, + width: `calc(100% + 6px + 8px)`, + }, + }, + }, +}; + +export default styles; diff --git a/toolkit/theme/globals/recaptcha.ts b/toolkit/theme/globals/recaptcha.ts new file mode 100644 index 0000000000..838fb98d4d --- /dev/null +++ b/toolkit/theme/globals/recaptcha.ts @@ -0,0 +1,20 @@ +const styles = { + '.grecaptcha-badge': { + visibility: 'hidden', + }, + 'div:has(div):has(iframe[title="recaptcha challenge expires in two minutes"])': { + '&::after': { + content: `" "`, + display: 'block', + position: 'fixed', + top: 0, + left: 0, + width: '100vw', + height: '100vh', + zIndex: 100000, + bgColor: 'blackAlpha.300', + }, + }, +}; + +export default styles; diff --git a/toolkit/theme/globals/scrollbar.ts b/toolkit/theme/globals/scrollbar.ts new file mode 100644 index 0000000000..152145044b --- /dev/null +++ b/toolkit/theme/globals/scrollbar.ts @@ -0,0 +1,32 @@ +const scrollbar = { + 'body *::-webkit-scrollbar': { + width: '20px', + }, + 'body *::-webkit-scrollbar-track': { + backgroundColor: 'transparent', + }, + 'body *::-webkit-scrollbar-thumb': { + backgroundColor: '{colors.global.scrollbar.thumb}', + borderRadius: '20px', + border: `8px solid rgba(0,0,0,0)`, + backgroundClip: 'content-box', + minHeight: '32px', + }, + 'body *::-webkit-scrollbar-button': { + display: 'none', + }, + 'body *::-webkit-scrollbar-corner': { + backgroundColor: 'transparent', + }, + 'body *::-webkit-resizer': { + // FIXME for dark mode we need to use a different image - /static/resizer_dark.png + backgroundImage: 'url(/static/resizer_light.png)', + backgroundSize: '20px', + }, + 'body *': { + scrollbarWidth: 'thin', + scrollbarColor: `{colors.global.scrollbar.thumb} transparent`, + }, +}; + +export default scrollbar; diff --git a/toolkit/theme/recipes/accordion.recipe.ts b/toolkit/theme/recipes/accordion.recipe.ts new file mode 100644 index 0000000000..44d6e8c275 --- /dev/null +++ b/toolkit/theme/recipes/accordion.recipe.ts @@ -0,0 +1,145 @@ +import { defineSlotRecipe } from '@chakra-ui/react'; + +export const recipe = defineSlotRecipe({ + className: 'chakra-accordion', + slots: [ 'root', 'item', 'itemTrigger', 'itemContent', 'itemBody', 'itemIndicator' ], + base: { + root: { + width: 'full', + '--accordion-radius': 'none', + }, + item: { + overflowAnchor: 'none', + borderColor: 'border.divider', + }, + itemTrigger: { + display: 'flex', + alignItems: 'center', + width: 'full', + outline: '0', + gap: '1', + fontWeight: 'medium', + borderRadius: 'var(--accordion-radius)', + cursor: 'pointer', + _focusVisible: { + outline: '2px solid', + outlineColor: 'colorPalette.focusRing', + }, + }, + itemBody: { + pt: '0', + pb: 'var(--accordion-padding-y)', + }, + itemContent: { + overflow: 'hidden', + borderRadius: 'var(--accordion-radius)', + _open: { + animationName: 'expand-height, fade-in', + animationDuration: 'moderate', + }, + _closed: { + animationName: 'collapse-height, fade-out', + animationDuration: 'moderate', + }, + }, + itemIndicator: { + transition: 'rotate 0.2s ease-in-out', + transformOrigin: 'center', + }, + }, + + variants: { + noAnimation: { + 'true': { + itemContent: { + _open: { + animationName: 'none', + }, + _closed: { + animationName: 'none', + }, + }, + itemIndicator: { + transition: 'none', + }, + }, + }, + variant: { + outline: { + item: { + borderBottomWidth: '1px', + }, + itemIndicator: { + color: 'gray.500', + }, + }, + faq: { + item: { + borderBottomWidth: '1px', + }, + itemTrigger: { + textStyle: 'heading.md', + }, + itemIndicator: { + color: 'link.primary', + _groupHover: { + color: 'link.primary.hover', + }, + }, + }, + }, + + size: { + sm: { + root: { + '--accordion-padding-x': '0', + '--accordion-padding-y': 'spacing.2', + }, + itemTrigger: { + textStyle: 'sm', + py: 'var(--accordion-padding-y)', + }, + itemIndicator: { + boxSize: '5', + }, + }, + md: { + root: { + '--accordion-padding-x': '0', + '--accordion-padding-y': 'spacing.3', + }, + itemTrigger: { + textStyle: 'md', + py: 'var(--accordion-padding-y)', + }, + itemIndicator: { + boxSize: '5', + }, + }, + }, + }, + + compoundVariants: [ + { + variant: 'faq', + size: 'md', + css: { + itemIndicator: { + boxSize: '14px', + margin: '5px', + }, + itemBody: { + paddingLeft: '36px', + }, + itemTrigger: { + gap: '3', + }, + }, + }, + ], + + defaultVariants: { + size: 'md', + variant: 'outline', + }, +}); diff --git a/toolkit/theme/recipes/alert.recipe.ts b/toolkit/theme/recipes/alert.recipe.ts new file mode 100644 index 0000000000..955866d07f --- /dev/null +++ b/toolkit/theme/recipes/alert.recipe.ts @@ -0,0 +1,135 @@ +import { defineSlotRecipe } from '@chakra-ui/react'; + +export const recipe = defineSlotRecipe({ + slots: [ 'root', 'title', 'description', 'indicator', 'content' ], + + base: { + root: { + width: 'full', + display: 'flex', + alignItems: 'flex-start', + position: 'relative', + borderRadius: 'base', + color: 'alert.fg', + }, + title: { + fontWeight: '600', + }, + description: { + display: 'inline', + }, + indicator: { + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + flexShrink: '0', + width: '5', + height: '5', + _icon: { boxSize: 'full' }, + color: 'alert.fg', + }, + content: { + display: 'flex', + flex: '1', + }, + }, + + variants: { + status: { + info: {}, + warning: {}, + warning_table: {}, + success: {}, + error: {}, + }, + + variant: { + subtle: { + root: { + color: 'alert.fg', + }, + }, + }, + + inline: { + 'true': { + root: { + alignItems: 'flex-start', + }, + content: { + display: 'inline-flex', + flexDirection: 'row', + alignItems: 'center', + }, + }, + 'false': { + content: { + display: 'flex', + flexDirection: 'column', + }, + }, + }, + + size: { + md: { + root: { + gap: '2', + px: '3', + py: '2', + textStyle: 'md', + }, + indicator: { + boxSize: '5', + my: '2px', + }, + }, + }, + }, + + compoundVariants: [ + { + status: 'info', + variant: 'subtle', + css: { + root: { + bg: 'alert.bg.info', + }, + }, + }, + { + status: 'warning', + variant: 'subtle', + css: { + root: { bg: 'alert.bg.warning' }, + }, + }, + { + status: 'warning_table', + variant: 'subtle', + css: { + root: { bg: 'alert.bg.warning_table' }, + }, + }, + { + status: 'success', + variant: 'subtle', + css: { + root: { bg: 'alert.bg.success' }, + }, + }, + { + status: 'error', + variant: 'subtle', + css: { + root: { bg: 'alert.bg.error' }, + }, + }, + ], + + defaultVariants: { + status: 'info', + size: 'md', + inline: true, + variant: 'subtle', + }, +}); diff --git a/toolkit/theme/recipes/badge.recipe.ts b/toolkit/theme/recipes/badge.recipe.ts new file mode 100644 index 0000000000..c9cebae5e9 --- /dev/null +++ b/toolkit/theme/recipes/badge.recipe.ts @@ -0,0 +1,83 @@ +import { defineRecipe } from '@chakra-ui/react'; + +export const recipe = defineRecipe({ + base: { + display: 'inline-flex', + alignItems: 'center', + borderRadius: 'sm', + gap: '1', + fontWeight: '500', + width: 'fit-content', + maxWidth: '100%', + whiteSpace: 'nowrap', + fontVariantNumeric: 'normal', + userSelect: 'none', + _loading: { + borderRadius: 'sm', + }, + }, + variants: { + variant: { + subtle: {}, + }, + colorPalette: { + gray: { + bg: 'badge.gray.bg', + color: 'badge.gray.fg', + }, + green: { + bg: 'badge.green.bg', + color: 'badge.green.fg', + }, + red: { + bg: 'badge.red.bg', + color: 'badge.red.fg', + }, + purple: { + bg: 'badge.purple.bg', + color: 'badge.purple.fg', + }, + orange: { + bg: 'badge.orange.bg', + color: 'badge.orange.fg', + }, + blue: { + bg: 'badge.blue.bg', + color: 'badge.blue.fg', + }, + yellow: { + bg: 'badge.yellow.bg', + color: 'badge.yellow.fg', + }, + teal: { + bg: 'badge.teal.bg', + color: 'badge.teal.fg', + }, + cyan: { + bg: 'badge.cyan.bg', + color: 'badge.cyan.fg', + }, + purple_alt: { + bg: 'badge.purple_alt.bg', + color: 'badge.purple_alt.fg', + }, + blue_alt: { + bg: 'badge.blue_alt.bg', + color: 'badge.blue_alt.fg', + }, + }, + size: { + md: { + textStyle: 'sm', + px: '1', + py: '0.5', + minH: '6', + }, + }, + }, + defaultVariants: { + variant: 'subtle', + colorPalette: 'gray', + size: 'md', + }, +}); diff --git a/toolkit/theme/recipes/button.recipe.ts b/toolkit/theme/recipes/button.recipe.ts new file mode 100644 index 0000000000..24bfb1d016 --- /dev/null +++ b/toolkit/theme/recipes/button.recipe.ts @@ -0,0 +1,285 @@ +import { defineRecipe } from '@chakra-ui/react'; + +export const recipe = defineRecipe({ + base: { + display: 'flex', + gap: 0, + fontWeight: 600, + overflow: 'hidden', + _disabled: { + opacity: 'control.disabled', + }, + }, + variants: { + variant: { + solid: { + bg: 'blue.600', + color: 'white', + _hover: { + bg: 'blue.400', + }, + _loading: { + opacity: 1, + '& .chakra-spinner': { + borderColor: 'gray.200', + borderBottomColor: 'spinner.track', + borderInlineStartColor: 'spinner.track', + }, + }, + _expanded: { + bg: 'blue.400', + }, + }, + outline: { + borderWidth: '2px', + borderStyle: 'solid', + bg: 'transparent', + color: 'button.outline.fg', + borderColor: 'button.outline.fg', + _hover: { + bg: 'transparent', + color: 'blue.400', + borderColor: 'blue.400', + }, + _loading: { + opacity: 1, + '& .chakra-spinner': { + borderColor: 'button.outline.fg', + borderBottomColor: 'spinner.track', + borderInlineStartColor: 'spinner.track', + }, + }, + }, + dropdown: { + borderWidth: '2px', + borderStyle: 'solid', + bg: 'transparent', + color: 'button.dropdown.fg', + borderColor: 'button.dropdown.border', + _hover: { + bg: 'transparent', + color: 'blue.400', + borderColor: 'blue.400', + }, + _loading: { + opacity: 1, + '& .chakra-spinner': { + borderColor: 'blue.500', + borderBottomColor: 'spinner.track', + borderInlineStartColor: 'spinner.track', + }, + }, + // When the dropdown is open, the button should be active + _expanded: { + bg: 'transparent', + color: 'blue.400', + borderColor: 'blue.400', + }, + // We have a special state for this button variant that serves as a popover trigger. + // When any items (filters) are selected in the popover, the button should change its background and text color. + // The last CSS selector is for redefining styles for the TabList component. + _selected: { + bg: 'button.dropdown.bg.selected', + color: 'button.dropdown.fg.selected', + borderColor: 'transparent', + _hover: { + bg: 'button.dropdown.bg.selected', + color: 'button.dropdown.fg.selected', + borderColor: 'transparent', + }, + }, + }, + header: { + bg: 'transparent', + color: 'button.header.fg', + borderColor: 'button.header.border', + borderWidth: '2px', + borderStyle: 'solid', + _hover: { + bg: 'transparent', + color: 'blue.400', + borderColor: 'blue.400', + }, + _loading: { + opacity: 1, + '& .chakra-spinner': { + borderColor: 'blue.500', + borderBottomColor: 'spinner.track', + borderInlineStartColor: 'spinner.track', + }, + }, + _selected: { + bg: 'button.header.bg.selected', + color: 'button.header.fg.selected', + borderColor: 'transparent', + borderWidth: '0px', + _hover: { + bg: 'button.header.bg.selected', + color: 'button.header.fg.selected', + }, + _highlighted: { + bg: 'button.header.bg.highlighted', + color: 'button.header.fg.highlighted', + borderColor: 'transparent', + borderWidth: '0px', + _hover: { + bg: 'button.header.bg.highlighted', + color: 'button.header.fg.highlighted', + }, + }, + }, + }, + hero: { + bg: 'button.hero.bg', + color: 'button.hero.fg', + _loading: { + opacity: 1, + '& .chakra-spinner': { + borderColor: 'button.hero.fg', + borderBottomColor: 'spinner.track', + borderInlineStartColor: 'spinner.track', + }, + }, + _hover: { + bg: 'button.hero.bg.hover', + color: 'button.hero.fg.hover', + }, + _selected: { + bg: 'button.hero.bg.selected', + color: 'button.hero.fg.selected', + _hover: { + bg: 'button.hero.bg.selected', + color: 'button.hero.fg.selected', + }, + }, + }, + segmented: { + bg: 'transparent', + color: 'button.segmented.fg', + borderColor: 'button.segmented.border', + borderWidth: '2px', + borderStyle: 'solid', + borderRadius: 'none', + _hover: { + color: 'link.primary.hover', + }, + _selected: { + bg: 'button.segmented.border', + color: 'button.segmented.fg.selected', + _hover: { + bg: 'button.segmented.border', + color: 'button.segmented.fg.selected', + }, + }, + _notFirst: { + borderLeftWidth: '0', + }, + _first: { + borderTopLeftRadius: 'base', + borderBottomLeftRadius: 'base', + }, + _last: { + borderTopRightRadius: 'base', + borderBottomRightRadius: 'base', + }, + }, + plain: { + bg: 'transparent', + color: 'inherit', + border: 'none', + _hover: { + bg: 'transparent', + }, + }, + subtle: { + bg: 'button.subtle.bg', + color: 'button.subtle.fg', + _hover: { + bg: 'button.subtle.bg', + color: 'link.primary.hover', + }, + _disabled: { + bg: 'button.subtle.bg', + color: 'button.subtle.fg', + }, + }, + link: { + bg: 'transparent', + color: 'link.primary', + border: 'none', + fontWeight: '400', + px: 0, + h: 'auto', + _hover: { + bg: 'transparent', + color: 'link.primary.hover', + }, + _disabled: { + color: 'text.secondary', + }, + }, + icon_secondary: { + bg: 'transparent', + color: 'button.icon_secondary.fg', + border: 'none', + _hover: { + color: 'link.primary.hover', + }, + _selected: { + bg: 'button.icon_secondary.bg.selected', + color: 'button.icon_secondary.fg.selected', + _hover: { + bg: 'button.icon_secondary.bg.selected', + color: 'button.icon_secondary.fg.selected', + }, + }, + _expanded: { + color: 'link.primary.hover', + }, + }, + }, + size: { + '2xs': { + px: 2, + h: 5, + minW: 5, + textStyle: 'xs', + borderRadius: 'sm', + gap: 1, + _icon: { boxSize: 'auto' }, + }, + xs: { + px: 2, + h: 6, + minW: 6, + textStyle: 'sm', + borderRadius: 'sm', + gap: 1, + _icon: { boxSize: 'auto' }, + }, + sm: { + px: 3, + h: 8, + minW: 8, + textStyle: 'sm', + borderRadius: 'base', + gap: 1, + _icon: { boxSize: 'auto' }, + }, + md: { + px: 3, + h: 10, + minW: 10, + textStyle: 'md', + borderRadius: 'base', + gap: 2, + _icon: { boxSize: 'auto' }, + '& .chakra-spinner': { '--spinner-size': '20px' }, + }, + }, + }, + defaultVariants: { + size: 'md', + variant: 'solid', + }, +}); diff --git a/toolkit/theme/recipes/checkbox.recipe.ts b/toolkit/theme/recipes/checkbox.recipe.ts new file mode 100644 index 0000000000..9b9ef6a639 --- /dev/null +++ b/toolkit/theme/recipes/checkbox.recipe.ts @@ -0,0 +1,56 @@ +import { defineSlotRecipe } from '@chakra-ui/react'; + +import { recipe as checkmarkRecipe } from './checkmark.recipe'; + +export const recipe = defineSlotRecipe({ + slots: [ 'root', 'control', 'label' ], + className: 'chakra-checkbox', + base: { + root: { + display: 'inline-flex', + gap: '2', + alignItems: 'center', + verticalAlign: 'top', + position: 'relative', + cursor: 'pointer', + _disabled: { + cursor: 'disabled', + }, + _readOnly: { + cursor: 'default', + }, + }, + + control: checkmarkRecipe.base, + + label: { + fontWeight: 'normal', + userSelect: 'none', + flexGrow: 1, + _disabled: { + opacity: 'control.disabled', + }, + }, + }, + + variants: { + size: { + md: { + root: { gap: '2' }, + label: { textStyle: 'md' }, + control: checkmarkRecipe.variants?.size?.md, + }, + }, + + variant: { + solid: { + control: checkmarkRecipe.variants?.variant?.solid, + }, + }, + }, + + defaultVariants: { + variant: 'solid', + size: 'md', + }, +}); diff --git a/toolkit/theme/recipes/checkmark.recipe.ts b/toolkit/theme/recipes/checkmark.recipe.ts new file mode 100644 index 0000000000..00f3e6c105 --- /dev/null +++ b/toolkit/theme/recipes/checkmark.recipe.ts @@ -0,0 +1,73 @@ +import { defineRecipe } from '@chakra-ui/react'; + +export const recipe = defineRecipe({ + className: 'chakra-checkmark', + base: { + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + flexShrink: '0', + color: 'white', + borderWidth: '2px', + borderColor: 'transparent', + focusVisibleRing: 'outside', + _icon: { + boxSize: 'full', + }, + _disabled: { + opacity: 'control.disabled', + }, + }, + variants: { + size: { + xs: { + boxSize: '3', + borderRadius: '2px', + }, + sm: { + boxSize: '4', + borderRadius: '2px', + }, + md: { + boxSize: '5', + borderRadius: 'sm', + }, + }, + + variant: { + solid: { + borderColor: 'checkbox.control.border', + _hover: { + borderColor: 'checkbox.control.border.hover', + }, + _readOnly: { + borderColor: 'checkbox.control.border.readOnly', + _hover: { + borderColor: 'checkbox.control.border.readOnly', + }, + '&:is([data-state=checked], [data-state=indeterminate])': { + bg: 'checkbox.control.border.readOnly', + color: 'gray.500', + _hover: { + bg: 'checkbox.control.border.readOnly', + }, + }, + }, + '&:is([data-state=checked], [data-state=indeterminate])': { + bg: 'blue.500', + color: 'white', + borderColor: 'blue.500', + _hover: { + bg: 'blue.400', + borderColor: 'blue.400', + }, + }, + }, + }, + }, + + defaultVariants: { + variant: 'solid', + size: 'md', + }, +}); diff --git a/toolkit/theme/recipes/close-button.recipe.ts b/toolkit/theme/recipes/close-button.recipe.ts new file mode 100644 index 0000000000..8bb205ca78 --- /dev/null +++ b/toolkit/theme/recipes/close-button.recipe.ts @@ -0,0 +1,34 @@ +import { defineRecipe } from '@chakra-ui/react'; + +export const recipe = defineRecipe({ + base: { + display: 'flex', + gap: 0, + borderRadius: 'sm', + overflow: 'hidden', + _disabled: { + opacity: 'control.disabled', + }, + minWidth: 'auto', + }, + variants: { + visual: { + plain: { + bg: 'transparent', + color: 'closeButton.fg', + border: 'none', + _hover: { + bg: 'transparent', + color: 'link.primary.hover', + }, + }, + }, + size: { + md: { boxSize: 5 }, + }, + }, + defaultVariants: { + size: 'md', + visual: 'plain', + }, +}); diff --git a/toolkit/theme/recipes/dialog.recipe.ts b/toolkit/theme/recipes/dialog.recipe.ts new file mode 100644 index 0000000000..5747070737 --- /dev/null +++ b/toolkit/theme/recipes/dialog.recipe.ts @@ -0,0 +1,213 @@ +import { defineSlotRecipe } from '@chakra-ui/react'; + +export const recipe = defineSlotRecipe({ + slots: [ 'backdrop', 'positioner', 'content', 'header', 'body', 'footer', 'title', 'description' ], + base: { + backdrop: { + bg: 'blackAlpha.800', + pos: 'fixed', + left: 0, + top: 0, + w: '100vw', + h: '100dvh', + zIndex: 'modal', + _open: { + animationName: 'fade-in', + animationDuration: 'slow', + }, + _closed: { + animationName: 'fade-out', + animationDuration: 'moderate', + }, + }, + positioner: { + display: 'flex', + width: '100vw', + height: '100dvh', + position: 'fixed', + left: 0, + top: 0, + '--dialog-z-index': 'zIndex.modal', + zIndex: 'calc(var(--dialog-z-index) + var(--layer-index, 0))', + justifyContent: 'center', + overscrollBehaviorY: 'none', + }, + content: { + display: 'flex', + flexDirection: 'column', + position: 'relative', + width: '100%', + padding: 6, + outline: 0, + textStyle: 'md', + my: 'var(--dialog-margin, var(--dialog-base-margin))', + '--dialog-z-index': 'zIndex.modal', + zIndex: 'calc(var(--dialog-z-index) + var(--layer-index, 0))', + bg: 'dialog.bg', + color: 'dialog.fg', + boxShadow: 'size.lg', + borderRadius: 'xl', + _open: { + animationDuration: 'moderate', + }, + _closed: { + animationDuration: 'faster', + }, + }, + header: { + flex: 0, + p: 0, + mb: 2, + display: 'flex', + alignItems: 'center', + columnGap: 2, + minH: '40px', + }, + body: { + flex: '1', + p: 0, + }, + footer: { + display: 'flex', + alignItems: 'center', + justifyContent: 'flex-start', + gap: '6', + p: '0', + mt: '6', + }, + title: { + textStyle: 'heading.lg', + fontWeight: '500', + }, + description: { + color: 'dialog.fg', + }, + }, + + variants: { + placement: { + center: { + positioner: { + alignItems: 'center', + }, + content: { + '--dialog-base-margin': 'auto', + mx: 'auto', + }, + }, + top: { + positioner: { + alignItems: 'flex-start', + }, + content: { + '--dialog-base-margin': 'spacing.16', + mx: 'auto', + }, + }, + bottom: { + positioner: { + alignItems: 'flex-end', + }, + content: { + '--dialog-base-margin': 'spacing.16', + mx: 'auto', + }, + }, + }, + + scrollBehavior: { + inside: { + positioner: { + overflow: 'hidden', + }, + content: { + // source code has minH: 'auto', but I am not sure why + // anyway it will override the minH from the "full" size variant + // minH: 'auto', + maxH: 'calc(100% - 7.5rem)', + }, + body: { + overflow: 'auto', + }, + }, + outside: { + positioner: { + overflow: 'auto', + pointerEvents: 'auto', + }, + }, + }, + + size: { + sm: { + content: { + maxW: '400px', + }, + }, + md: { + content: { + maxW: '728px', + }, + }, + cover: { + positioner: { + padding: '10', + }, + content: { + width: '100%', + height: '100%', + '--dialog-margin': '0', + }, + }, + full: { + content: { + maxW: '100vw', + minH: '100vh', + '--dialog-margin': '0', + borderRadius: '0', + }, + }, + }, + + motionPreset: { + scale: { + content: { + _open: { animationName: 'scale-in, fade-in' }, + _closed: { animationName: 'scale-out, fade-out' }, + }, + }, + 'slide-in-bottom': { + content: { + _open: { animationName: 'slide-from-bottom, fade-in' }, + _closed: { animationName: 'slide-to-bottom, fade-out' }, + }, + }, + 'slide-in-top': { + content: { + _open: { animationName: 'slide-from-top, fade-in' }, + _closed: { animationName: 'slide-to-top, fade-out' }, + }, + }, + 'slide-in-left': { + content: { + _open: { animationName: 'slide-from-left, fade-in' }, + _closed: { animationName: 'slide-to-left, fade-out' }, + }, + }, + 'slide-in-right': { + content: { + _open: { animationName: 'slide-from-right, fade-in' }, + _closed: { animationName: 'slide-to-right, fade-out' }, + }, + }, + none: {}, + }, + }, + + defaultVariants: { + size: 'md', + scrollBehavior: 'inside', + placement: 'center', + motionPreset: 'scale', + }, +}); diff --git a/toolkit/theme/recipes/drawer.recipe.ts b/toolkit/theme/recipes/drawer.recipe.ts new file mode 100644 index 0000000000..61bdf6a34b --- /dev/null +++ b/toolkit/theme/recipes/drawer.recipe.ts @@ -0,0 +1,173 @@ +import { defineSlotRecipe } from '@chakra-ui/react'; + +export const recipe = defineSlotRecipe({ + slots: [ 'root', 'backdrop', 'trigger', 'content', 'header', 'body', 'footer', 'title', 'description', 'positioner' ], + className: 'chakra-drawer', + base: { + backdrop: { + bg: 'blackAlpha.800', + pos: 'fixed', + insetInlineStart: 0, + top: 0, + w: '100vw', + h: '100dvh', + zIndex: 'modal', + _open: { + animationName: 'fade-in', + animationDuration: 'slow', + }, + _closed: { + animationName: 'fade-out', + animationDuration: 'moderate', + }, + }, + positioner: { + display: 'flex', + width: '100vw', + height: '100dvh', + position: 'fixed', + insetInlineStart: 0, + top: 0, + zIndex: 'modal', + overscrollBehaviorY: 'none', + }, + content: { + display: 'flex', + flexDirection: 'column', + position: 'relative', + width: '100%', + outline: 0, + zIndex: 'modal', + textStyle: 'sm', + maxH: '100dvh', + color: 'inherit', + bg: 'drawer.bg', + boxShadow: 'drawer', + _open: { + animationDuration: 'slowest', + animationTimingFunction: 'ease-in-smooth', + }, + _closed: { + animationDuration: 'slower', + animationTimingFunction: 'ease-in-smooth', + }, + }, + header: { + flex: 0, + px: '6', + pt: '6', + pb: '4', + }, + body: { + p: '6', + flex: '1', + overflow: 'auto', + }, + footer: { + display: 'flex', + alignItems: 'center', + justifyContent: 'flex-end', + gap: '3', + px: '6', + pt: '2', + pb: '4', + }, + title: { + textStyle: 'lg', + fontWeight: 'semibold', + }, + description: { + color: 'fg.muted', + }, + }, + + variants: { + size: { + md: { + content: { + maxW: '300px', + }, + }, + }, + + placement: { + start: { + positioner: { + justifyContent: 'flex-start', + }, + content: { + _open: { + animationName: { + base: 'slide-from-left-full, fade-in', + _rtl: 'slide-from-right-full, fade-in', + }, + }, + _closed: { + animationName: { + base: 'slide-to-left-full, fade-out', + _rtl: 'slide-to-right-full, fade-out', + }, + }, + }, + }, + + end: { + positioner: { + justifyContent: 'flex-end', + }, + content: { + _open: { + animationName: { + base: 'slide-from-right-full, fade-in', + _rtl: 'slide-from-left-full, fade-in', + }, + }, + _closed: { + animationName: { + base: 'slide-to-right-full, fade-out', + _rtl: 'slide-to-right-full, fade-out', + }, + }, + }, + }, + + top: { + positioner: { + alignItems: 'flex-start', + }, + content: { + maxW: '100%', + _open: { animationName: 'slide-from-top-full, fade-in' }, + _closed: { animationName: 'slide-to-top-full, fade-out' }, + }, + }, + + bottom: { + positioner: { + alignItems: 'flex-end', + }, + content: { + maxW: '100%', + _open: { animationName: 'slide-from-bottom-full, fade-in' }, + _closed: { animationName: 'slide-to-bottom-full, fade-out' }, + }, + }, + }, + + contained: { + 'true': { + positioner: { + padding: '4', + }, + content: { + borderRadius: 'l3', + }, + }, + }, + }, + + defaultVariants: { + size: 'md', + placement: 'end', + }, +}); diff --git a/toolkit/theme/recipes/field.recipe.ts b/toolkit/theme/recipes/field.recipe.ts new file mode 100644 index 0000000000..7ac89a088b --- /dev/null +++ b/toolkit/theme/recipes/field.recipe.ts @@ -0,0 +1,175 @@ +import { defineSlotRecipe } from '@chakra-ui/react'; + +export const recipe = defineSlotRecipe({ + className: 'chakra-field', + slots: [ 'root', 'label', 'requiredIndicator', 'errorText', 'helperText' ], + base: { + requiredIndicator: { + color: 'inherit', + lineHeight: 'inherit', + }, + root: { + display: 'flex', + width: '100%', + position: 'relative', + gap: '1', + }, + label: { + display: 'flex', + alignItems: 'center', + textAlign: 'start', + textStyle: 'sm', + fontWeight: '500', + gap: '0', + userSelect: 'none', + zIndex: '1', + _disabled: { + opacity: 'control.disabled', + }, + _invalid: { + color: 'input.fg.error', + }, + }, + errorText: { + display: 'inline-flex', + alignItems: 'center', + fontWeight: 'medium', + gap: '1', + color: 'input.fg.error', + textStyle: 'sm', + }, + helperText: { + color: 'fg.muted', + textStyle: 'sm', + }, + }, + + variants: { + floating: { + 'true': { + label: { + pos: 'absolute', + bg: 'bg', + top: '2px', + left: '2px', + color: 'input.placeholder', + width: 'calc(100% - 4px)', + borderRadius: 'base', + pointerEvents: 'none', + transformOrigin: 'top left', + transitionProperty: 'font-size, line-height, padding, background-color', + transitionDuration: 'fast', + transitionTimingFunction: 'ease', + }, + }, + }, + size: { + sm: { + label: { + fontSize: 'sm', + }, + }, + md: { + label: { + fontSize: 'md', + }, + }, + lg: { + label: { + fontSize: 'md', + }, + }, + // special size for textarea + '2xl': { + label: { + fontSize: 'md', + }, + }, + }, + orientation: { + vertical: { + root: { + flexDirection: 'column', + alignItems: 'flex-start', + }, + }, + horizontal: { + root: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + }, + label: { + flex: '0 0 var(--field-label-width, 80px)', + }, + }, + }, + }, + + compoundVariants: [ + { + size: 'lg', + floating: true, + css: { + label: { + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + padding: '10px 16px 0px 16px', + textStyle: 'xs', + _peerPlaceholderShown: { + padding: '16px', + textStyle: 'md', + }, + _peerFocusVisible: { + padding: '10px 16px 0px 16px', + textStyle: 'xs', + }, + _readOnly: { + bg: 'input.bg.readOnly', + }, + }, + errorText: { + fontSize: 'inherit', + lineHeight: 'inherit', + }, + }, + }, + { + size: '2xl', + floating: true, + css: { + label: { + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + // 20px = scrollbar width + // 4px = border width + width: 'calc(100% - 4px - 20px)', + padding: '16px 16px 0px 16px', + textStyle: 'xs', + borderTopRightRadius: '0px', + borderBottomRightRadius: '0px', + _peerPlaceholderShown: { + textStyle: 'md', + }, + _peerFocusVisible: { + textStyle: 'xs', + }, + _readOnly: { + bg: 'input.bg.readOnly', + }, + }, + errorText: { + fontSize: 'inherit', + lineHeight: 'inherit', + }, + }, + }, + ], + + defaultVariants: { + floating: false, + orientation: 'vertical', + }, +}); diff --git a/toolkit/theme/recipes/index.ts b/toolkit/theme/recipes/index.ts new file mode 100644 index 0000000000..82dc25d60c --- /dev/null +++ b/toolkit/theme/recipes/index.ts @@ -0,0 +1,68 @@ +import { recipe as accordion } from './accordion.recipe'; +import { recipe as alert } from './alert.recipe'; +import { recipe as badge } from './badge.recipe'; +import { recipe as button } from './button.recipe'; +import { recipe as checkbox } from './checkbox.recipe'; +import { recipe as checkmark } from './checkmark.recipe'; +import { recipe as closeButton } from './close-button.recipe'; +import { recipe as dialog } from './dialog.recipe'; +import { recipe as drawer } from './drawer.recipe'; +import { recipe as field } from './field.recipe'; +import { recipe as input } from './input.recipe'; +import { recipe as link } from './link.recipe'; +import { recipe as list } from './list.recipe'; +import { recipe as menu } from './menu.recipe'; +import { recipe as pinInput } from './pin-input.recipe'; +import { recipe as popover } from './popover.recipe'; +import { recipe as progressCircle } from './progress-circle.recipe'; +import { recipe as radioGroup } from './radio-group.recipe'; +import { recipe as radiomark } from './radiomark.recipe'; +import { recipe as ratingGroup } from './rating-group.recipe'; +import { recipe as select } from './select.recipe'; +import { recipe as skeleton } from './skeleton.recipe'; +import { recipe as spinner } from './spinner.recipe'; +import { recipe as stat } from './stat.recipe'; +import { recipe as switchRecipe } from './switch.recipe'; +import { recipe as table } from './table.recipe'; +import { recipe as tabs } from './tabs.recipe'; +import { recipe as tag } from './tag.recipe'; +import { recipe as textarea } from './textarea.recipe'; +import { recipe as toast } from './toast.recipe'; +import { recipe as tooltip } from './tooltip.recipe'; + +export const recipes = { + badge, + button, + checkmark, + closeButton, + input, + link, + radiomark, + skeleton, + spinner, + textarea, +}; + +export const slotRecipes = { + accordion, + alert, + checkbox, + dialog, + drawer, + field, + list, + menu, + pinInput, + popover, + progressCircle, + radioGroup, + ratingGroup, + select, + stat, + 'switch': switchRecipe, + table, + tabs, + tag, + toast, + tooltip, +}; diff --git a/toolkit/theme/recipes/input.recipe.ts b/toolkit/theme/recipes/input.recipe.ts new file mode 100644 index 0000000000..b5af17ef30 --- /dev/null +++ b/toolkit/theme/recipes/input.recipe.ts @@ -0,0 +1,119 @@ +import { defineRecipe } from '@chakra-ui/react'; + +export const recipe = defineRecipe({ + base: { + width: '100%', + minWidth: '0', + outline: '0', + position: 'relative', + appearance: 'textfield', + textAlign: 'start', + borderRadius: 'base', + height: 'var(--input-height)', + minW: 'var(--input-height)', + color: 'input.fg', + '--focus-color': 'colors.border.error', + '--error-color': 'colors.border.error', + _invalid: { + focusRingColor: 'var(--error-color)', + borderColor: 'var(--error-color)', + }, + _readOnly: { + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + }, + }, + + variants: { + size: { + sm: { + textStyle: 'sm', + px: '2', + '--input-height': 'sizes.8', + }, + md: { + textStyle: 'sm', + px: '2', + '--input-height': 'sizes.10', + }, + lg: { + textStyle: 'md', + px: '4', + '--input-height': '60px', + }, + }, + + variant: { + outline: { + bg: 'input.bg', + borderWidth: '2px', + borderColor: 'input.border.filled', + focusVisibleRing: 'none', + _placeholder: { + color: 'input.placeholder', + }, + _placeholderShown: { + borderColor: 'input.border', + _invalid: { + borderColor: 'input.border.error', + }, + }, + _hover: { + borderColor: 'input.border.hover', + }, + _focus: { + borderColor: 'input.border.focus', + boxShadow: 'size.md', + _hover: { + borderColor: 'input.border.focus', + }, + }, + _readOnly: { + userSelect: 'all', + pointerEvents: 'none', + bg: 'input.bg.readOnly', + borderColor: 'input.border.readOnly', + _focus: { + borderColor: 'input.border.readOnly', + }, + _hover: { + borderColor: 'input.border.readOnly', + }, + }, + _disabled: { + opacity: 'control.disabled', + }, + _invalid: { + borderColor: 'input.border.error', + _placeholder: { + color: 'input.placeholder.error', + }, + _hover: { + borderColor: 'input.border.error', + }, + }, + }, + }, + + floating: { + 'true': {}, + }, + }, + + compoundVariants: [ + { + size: 'lg', + floating: true, + css: { + padding: '26px 10px 10px 16px', + }, + }, + ], + + defaultVariants: { + size: 'md', + variant: 'outline', + floating: false, + }, +}); diff --git a/toolkit/theme/recipes/link.recipe.ts b/toolkit/theme/recipes/link.recipe.ts new file mode 100644 index 0000000000..3b5f64c3de --- /dev/null +++ b/toolkit/theme/recipes/link.recipe.ts @@ -0,0 +1,78 @@ +import { defineRecipe } from '@chakra-ui/react'; + +export const recipe = defineRecipe({ + base: { + gap: 0, + _disabled: { + cursor: 'not-allowed', + }, + }, + variants: { + variant: { + primary: { + color: 'link.primary', + _hover: { + textDecoration: 'none', + color: 'link.primary.hover', + }, + }, + secondary: { + color: 'link.secondary', + _hover: { + textDecoration: 'none', + color: 'link.primary.hover', + }, + }, + subtle: { + color: 'link.subtle', + _hover: { + color: 'link.subtle.hover', + textDecorationLine: 'underline', + textDecorationColor: 'link.subtle.hover', + }, + }, + underlaid: { + color: 'link.primary', + bgColor: 'link.underlaid.bg', + px: '8px', + py: '6px', + borderRadius: 'base', + textStyle: 'sm', + _hover: { + color: 'link.primary.hover', + textDecoration: 'none', + }, + }, + menu: { + color: 'link.menu', + _hover: { + color: 'link.primary.hover', + textDecoration: 'none', + }, + }, + navigation: { + color: 'link.navigation.fg', + bg: 'transparent', + _hover: { + color: 'link.navigation.fg.hover', + textDecoration: 'none', + }, + _selected: { + color: 'link.navigation.fg.selected', + bg: 'link.navigation.bg.selected', + }, + _active: { + color: 'link.navigation.fg.active', + }, + }, + plain: { + _hover: { + textDecoration: 'none', + }, + }, + }, + }, + defaultVariants: { + variant: 'primary', + }, +}); diff --git a/toolkit/theme/recipes/list.recipe.ts b/toolkit/theme/recipes/list.recipe.ts new file mode 100644 index 0000000000..d66af9885e --- /dev/null +++ b/toolkit/theme/recipes/list.recipe.ts @@ -0,0 +1,67 @@ +import { defineSlotRecipe } from '@chakra-ui/react'; + +export const recipe = defineSlotRecipe({ + slots: [ 'root', 'item', 'indicator' ], + base: { + root: { + display: 'flex', + flexDirection: 'column', + gap: 'var(--list-gap)', + '& :where(ul, ol)': { + marginTop: 'var(--list-gap)', + }, + }, + item: { + whiteSpace: 'normal', + display: 'list-item', + '&::marker': { + color: 'inherit', + }, + }, + indicator: { + marginEnd: '2', + minHeight: '1lh', + flexShrink: 0, + display: 'inline-block', + verticalAlign: 'middle', + }, + }, + + variants: { + variant: { + marker: { + root: { + listStyle: 'revert', + }, + item: { + _marker: { + color: 'inherit', + }, + }, + }, + + plain: { + item: { + alignItems: 'flex-start', + display: 'inline-flex', + }, + }, + }, + + align: { + center: { + item: { alignItems: 'center' }, + }, + start: { + item: { alignItems: 'flex-start' }, + }, + end: { + item: { alignItems: 'flex-end' }, + }, + }, + }, + + defaultVariants: { + variant: 'marker', + }, +}); diff --git a/toolkit/theme/recipes/menu.recipe.ts b/toolkit/theme/recipes/menu.recipe.ts new file mode 100644 index 0000000000..c02d5968e6 --- /dev/null +++ b/toolkit/theme/recipes/menu.recipe.ts @@ -0,0 +1,105 @@ +import { defineSlotRecipe } from '@chakra-ui/react'; + +export const recipe = defineSlotRecipe({ + slots: [ 'content', 'item', 'itemText', 'itemGroupLabel', 'indicator', 'itemCommand', 'separator' ], + base: { + content: { + outline: 0, + bg: 'popover.bg', + boxShadow: 'popover', + color: 'initial', + maxHeight: 'var(--available-height)', + '--menu-z-index': 'zIndex.dropdown', + zIndex: 'calc(var(--menu-z-index) + var(--layer-index, 0))', + borderRadius: 'md', + overflow: 'hidden', + overflowY: 'auto', + _open: { + animationStyle: 'slide-fade-in', + animationDuration: 'fast', + }, + _closed: { + animationStyle: 'slide-fade-out', + animationDuration: 'faster', + }, + }, + item: { + textDecoration: 'none', + color: 'initial', + userSelect: 'none', + borderRadius: 'none', + width: '100%', + display: 'flex', + cursor: 'pointer', + alignItems: 'center', + textAlign: 'start', + position: 'relative', + flex: '0 0 auto', + outline: 0, + _disabled: { + layerStyle: 'disabled', + }, + }, + itemText: { + flex: '1', + }, + itemGroupLabel: { + px: '2', + py: '1.5', + fontWeight: 'semibold', + textStyle: 'sm', + }, + indicator: { + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + flexShrink: '0', + }, + itemCommand: { + opacity: '0.6', + textStyle: 'xs', + ms: 'auto', + ps: '4', + letterSpacing: 'widest', + }, + separator: { + height: '1px', + bg: 'bg.muted', + my: '1', + mx: '-1', + }, + }, + + variants: { + variant: { + subtle: { + item: { + _highlighted: { + bg: 'menu.item.bg.highlighted', + }, + }, + }, + }, + + size: { + md: { + content: { + minW: '150px', + py: '2', + px: '0', + }, + item: { + gap: '2', + textStyle: 'md', + py: '2', + px: '4', + }, + }, + }, + }, + + defaultVariants: { + size: 'md', + variant: 'subtle', + }, +}); diff --git a/toolkit/theme/recipes/pin-input.recipe.ts b/toolkit/theme/recipes/pin-input.recipe.ts new file mode 100644 index 0000000000..6a915e4d6b --- /dev/null +++ b/toolkit/theme/recipes/pin-input.recipe.ts @@ -0,0 +1,35 @@ +import { defineSlotRecipe } from '@chakra-ui/react'; + +import { mapEntries } from '../utils/entries'; +import { recipe as inputRecipe } from './input.recipe'; + +const { variants } = inputRecipe; + +export const recipe = defineSlotRecipe({ + slots: [ 'input' ], + base: { + input: { + ...inputRecipe.base, + textAlign: 'center', + width: 'var(--input-height)', + }, + }, + variants: { + size: { + md: { + input: { + boxSize: 10, + borderRadius: 'base', + }, + }, + }, + variant: mapEntries(variants!.variant, (key, value) => [ + key, + { input: value }, + ]), + }, + defaultVariants: { + size: 'md', + variant: 'outline', + }, +}); diff --git a/toolkit/theme/recipes/popover.recipe.ts b/toolkit/theme/recipes/popover.recipe.ts new file mode 100644 index 0000000000..5dbd279f05 --- /dev/null +++ b/toolkit/theme/recipes/popover.recipe.ts @@ -0,0 +1,71 @@ +import { defineSlotRecipe } from '@chakra-ui/react'; + +export const recipe = defineSlotRecipe({ + slots: [ 'content', 'header', 'body', 'footer', 'arrow', 'arrowTip' ], + base: { + content: { + position: 'relative', + display: 'flex', + flexDirection: 'column', + textStyle: 'sm', + '--popover-bg': 'colors.popover.bg', + bg: 'var(--popover-bg)', + boxShadow: 'popover', + boxShadowColor: 'colors.popover.shadow', + '--popover-mobile-size': 'calc(100dvw - 1rem)', + width: 'fit-content', + maxW: { + base: 'calc(100vw - 8px)', + lg: '800px', + }, + borderWidth: '0', + borderRadius: 'md', + '--popover-z-index': 'zIndex.popover', + zIndex: 'calc(var(--popover-z-index) + var(--layer-index, 0))', + outline: '0', + transformOrigin: 'var(--transform-origin)', + _open: { + animationStyle: 'scale-fade-in', + animationDuration: 'fast', + }, + _closed: { + animationStyle: 'scale-fade-out', + animationDuration: 'faster', + }, + }, + header: { + paddingInline: 'var(--popover-padding)', + paddingTop: 'var(--popover-padding)', + }, + body: { + padding: 'var(--popover-padding)', + flex: '1', + }, + footer: { + display: 'flex', + alignItems: 'center', + paddingInline: 'var(--popover-padding)', + paddingBottom: 'var(--popover-padding)', + }, + arrow: { + '--arrow-size': 'sizes.3', + '--arrow-background': 'var(--popover-bg)', + }, + arrowTip: { + borderTopWidth: '1px', + borderInlineStartWidth: '1px', + }, + }, + variants: { + size: { + sm: { + content: { + '--popover-padding': 'spacing.4', + }, + }, + }, + }, + defaultVariants: { + size: 'sm', + }, +}); diff --git a/toolkit/theme/recipes/progress-circle.recipe.ts b/toolkit/theme/recipes/progress-circle.recipe.ts new file mode 100644 index 0000000000..a71a3ab33e --- /dev/null +++ b/toolkit/theme/recipes/progress-circle.recipe.ts @@ -0,0 +1,74 @@ +import { defineSlotRecipe } from '@chakra-ui/react'; + +export const recipe = defineSlotRecipe({ + slots: [ 'root', 'circle', 'circleTrack', 'circleRange', 'label', 'valueText' ], + base: { + root: { + display: 'inline-flex', + textStyle: 'sm', + position: 'relative', + }, + circle: { + _indeterminate: { + animation: 'spin 2s linear infinite', + }, + }, + circleTrack: { + '--track-color': 'colors.progressCircle.trackColor', + stroke: 'var(--track-color)', + }, + circleRange: { + stroke: 'blue.500', + transitionProperty: 'stroke-dasharray', + transitionDuration: '0.6s', + _indeterminate: { + animation: 'circular-progress 1.5s linear infinite', + }, + }, + label: { + display: 'inline-flex', + }, + valueText: { + lineHeight: '1', + fontWeight: 'medium', + letterSpacing: 'tight', + fontVariantNumeric: 'tabular-nums', + }, + }, + + variants: { + size: { + sm: { + circle: { + '--size': '20px', + '--thickness': '2px', + }, + valueText: { + textStyle: 'xs', + }, + }, + md: { + circle: { + '--size': '32px', + '--thickness': '3px', + }, + valueText: { + textStyle: 'xs', + }, + }, + lg: { + circle: { + '--size': '80px', + '--thickness': '8px', + }, + valueText: { + textStyle: 'sm', + }, + }, + }, + }, + + defaultVariants: { + size: 'md', + }, +}); diff --git a/toolkit/theme/recipes/radio-group.recipe.ts b/toolkit/theme/recipes/radio-group.recipe.ts new file mode 100644 index 0000000000..4e925984a0 --- /dev/null +++ b/toolkit/theme/recipes/radio-group.recipe.ts @@ -0,0 +1,73 @@ +import { defineSlotRecipe } from '@chakra-ui/react'; + +import { recipe as radiomarkRecipe } from './radiomark.recipe'; + +export const recipe = defineSlotRecipe({ + slots: [ 'item', 'itemControl', 'itemText', 'root' ], + base: { + root: { + display: 'flex', + }, + + item: { + display: 'inline-flex', + alignItems: 'center', + position: 'relative', + fontWeight: 'normal', + cursor: 'pointer', + _disabled: { + cursor: 'disabled', + }, + _readOnly: { + cursor: 'default', + }, + }, + + itemControl: radiomarkRecipe.base, + + itemText: { + userSelect: 'none', + textStyle: 'md', + _disabled: { + opacity: 'control.disabled', + }, + }, + }, + variants: { + variant: { + solid: { + itemControl: radiomarkRecipe.variants?.variant?.solid, + }, + }, + + size: { + md: { + item: { textStyle: 'md', gap: '2' }, + itemControl: radiomarkRecipe.variants?.size?.md, + }, + }, + + orientation: { + vertical: { + root: { + flexDirection: 'column', + alignItems: 'flex-start', + rowGap: '12px', + }, + }, + horizontal: { + root: { + flexDirection: 'row', + alignItems: 'center', + columnGap: '32px', + }, + }, + }, + }, + + defaultVariants: { + size: 'md', + variant: 'solid', + orientation: 'vertical', + }, +}); diff --git a/toolkit/theme/recipes/radiomark.recipe.ts b/toolkit/theme/recipes/radiomark.recipe.ts new file mode 100644 index 0000000000..5812ff903e --- /dev/null +++ b/toolkit/theme/recipes/radiomark.recipe.ts @@ -0,0 +1,91 @@ +import { defineRecipe } from '@chakra-ui/react'; + +export const recipe = defineRecipe({ + base: { + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + flexShrink: 0, + verticalAlign: 'top', + color: 'white', + borderWidth: '2px', + borderColor: 'transparent', + borderRadius: 'full', + _focusVisible: { + outline: '2px solid', + outlineColor: 'colorPalette.focusRing', + outlineOffset: '2px', + }, + _disabled: { + cursor: 'disabled', + opacity: 'control.disabled', + }, + + '& .dot': { + height: '100%', + width: '100%', + borderRadius: 'full', + bg: 'currentColor', + scale: '0.4', + }, + }, + + variants: { + variant: { + solid: { + borderWidth: '2px', + borderColor: 'radio.control.border', + _hover: { + borderColor: 'radio.control.border.hover', + }, + _checked: { + bg: 'blue.500', + color: 'white', + borderColor: 'blue.500', + _hover: { + bg: 'blue.400', + borderColor: 'blue.400', + }, + }, + _invalid: { + bg: 'red.500', + borderColor: 'red.500', + }, + _readOnly: { + borderColor: 'radio.control.border.readOnly', + _hover: { + borderColor: 'radio.control.border.readOnly', + }, + _checked: { + bg: 'radio.control.border.readOnly', + _hover: { + bg: 'radio.control.border.readOnly', + }, + '& .dot': { + bg: 'gray.500', + }, + }, + }, + }, + }, + + size: { + xs: { + boxSize: '3', + }, + + sm: { + boxSize: '4', + }, + + md: { + boxSize: '5', + }, + }, + }, + + defaultVariants: { + variant: 'solid', + size: 'md', + }, +}); diff --git a/toolkit/theme/recipes/rating-group.recipe.ts b/toolkit/theme/recipes/rating-group.recipe.ts new file mode 100644 index 0000000000..1671b945ed --- /dev/null +++ b/toolkit/theme/recipes/rating-group.recipe.ts @@ -0,0 +1,93 @@ +import { defineSlotRecipe } from '@chakra-ui/react'; + +export const recipe = defineSlotRecipe({ + className: 'chakra-rating-group', + slots: [ 'root', 'control', 'item', 'itemIndicator' ], + base: { + root: { + display: 'inline-flex', + alignItems: 'center', + columnGap: 3, + }, + + control: { + display: 'inline-flex', + alignItems: 'center', + gap: 1, + }, + + item: { + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + userSelect: 'none', + cursor: 'pointer', + + _icon: { + width: '100%', + height: '100%', + display: 'inline-block', + flexShrink: 0, + position: 'absolute', + left: 0, + top: 0, + }, + }, + + itemIndicator: { + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + position: 'relative', + + _icon: { + stroke: 'none', + width: '100%', + height: '100%', + display: 'inline-block', + flexShrink: 0, + position: 'absolute', + left: 0, + top: 0, + }, + + '& [data-bg]': { + color: 'rating', + }, + + '& [data-fg]': { + color: 'transparent', + }, + + '&[data-highlighted]:not([data-half])': { + '& [data-fg]': { + color: 'rating.highlighted', + }, + }, + + '&[data-half]': { + '& [data-fg]': { + color: 'rating.highlighted', + clipPath: 'inset(0 50% 0 0)', + }, + }, + }, + }, + + variants: { + size: { + md: { + item: { + boxSize: 5, + }, + root: { + textStyle: 'md', + }, + }, + }, + }, + + defaultVariants: { + size: 'md', + }, +}); diff --git a/toolkit/theme/recipes/select.recipe.ts b/toolkit/theme/recipes/select.recipe.ts new file mode 100644 index 0000000000..e272ab49d0 --- /dev/null +++ b/toolkit/theme/recipes/select.recipe.ts @@ -0,0 +1,277 @@ +import { defineSlotRecipe } from '@chakra-ui/react'; + +export const recipe = defineSlotRecipe({ + slots: [ 'root', 'trigger', 'indicatorGroup', 'indicator', 'content', 'item', 'control', 'itemText', 'itemGroup', 'itemGroupLabel', 'label', 'valueText' ], + base: { + root: { + display: 'flex', + flexDirection: 'column', + gap: '1.5', + width: '100%', + }, + trigger: { + display: 'flex', + alignItems: 'center', + justifyContent: 'flex-start', + width: 'full', + minH: 'var(--select-trigger-height)', + pr: 'var(--select-trigger-padding-right)', + pl: 'var(--select-trigger-padding-left)', + borderRadius: 'base', + userSelect: 'none', + textAlign: 'start', + fontWeight: 'semibold', + cursor: 'pointer', + focusVisibleRing: 'none', + _disabled: { + opacity: 'control.disabled', + }, + _placeholderShown: { + '& [data-part=value-text]': { + display: '-webkit-box', + }, + }, + }, + indicatorGroup: { + display: 'flex', + alignItems: 'center', + gap: '1', + pos: 'absolute', + right: '0', + top: '0', + bottom: '0', + px: '0', + pointerEvents: 'none', + }, + indicator: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + boxSize: '5', + color: 'inherit', + _open: { + color: 'link.primary.hover', + }, + }, + content: { + background: 'popover.bg', + display: 'flex', + flexDirection: 'column', + zIndex: 'popover', + borderRadius: 'md', + borderWidth: '0', + outline: 0, + boxShadow: 'popover', + boxShadowColor: 'colors.popover.shadow', + maxH: '96', + overflowY: 'auto', + minWidth: '150px', + rowGap: '2', + _open: { + animationStyle: 'slide-fade-in', + animationDuration: 'fast', + }, + _closed: { + animationStyle: 'slide-fade-out', + animationDuration: 'fastest', + }, + }, + item: { + position: 'relative', + userSelect: 'none', + display: 'flex', + alignItems: 'center', + gap: '2', + cursor: 'pointer', + justifyContent: 'flex-start', + flex: '1', + textAlign: 'start', + borderRadius: 'none', + _disabled: { + pointerEvents: 'none', + opacity: 'control.disabled', + }, + _highlighted: { + bg: 'select.item.bg.highlighted', + }, + }, + control: { + pos: 'relative', + }, + itemText: { + flex: '1', + }, + itemGroup: { + _first: { mt: '0' }, + }, + itemGroupLabel: { + py: '1', + fontWeight: 'medium', + }, + label: { + fontWeight: 'medium', + userSelect: 'none', + textStyle: 'sm', + _disabled: { + opacity: 'control.disabled', + }, + }, + valueText: { + display: 'flex', + flexDirection: 'column', + lineClamp: '1', + maxW: '100%', + wordBreak: 'break-all', + }, + }, + + variants: { + variant: { + outline: { + trigger: { + borderWidth: '2px', + bg: 'input.bg', + color: 'select.trigger.outline.fg', + borderColor: 'input.border.filled', + _expanded: { + color: 'link.primary.hover', + borderColor: 'link.primary.hover', + _hover: { + color: 'link.primary.hover', + borderColor: 'link.primary.hover', + }, + }, + _hover: { + color: 'select.trigger.outline.fg', + borderColor: 'input.border.hover', + }, + _focusVisible: { + borderColor: 'input.border.focus', + }, + _readOnly: { + userSelect: 'all', + pointerEvents: 'none', + cursor: 'default', + bg: 'input.bg.readOnly', + borderColor: 'input.border.readOnly', + _focus: { + borderColor: 'input.border.readOnly', + }, + _hover: { + borderColor: 'input.border.readOnly', + }, + }, + _invalid: { + borderColor: 'input.border.error', + _hover: { + borderColor: 'input.border.error', + }, + _expanded: { + color: 'link.primary.hover', + borderColor: 'link.primary.hover', + _hover: { + color: 'link.primary.hover', + borderColor: 'link.primary.hover', + }, + }, + }, + _placeholderShown: { + color: 'select.placeholder.fg', + borderColor: 'input.border', + _hover: { + color: 'select.placeholder.fg', + }, + _invalid: { + color: 'select.placeholder.fg.error', + _hover: { + color: 'select.placeholder.fg.error', + }, + }, + }, + }, + indicatorGroup: { + color: 'select.indicator.fg', + _peerDisabled: { + opacity: 'control.disabled', + }, + }, + }, + plain: { + trigger: {}, + indicatorGroup: {}, + }, + }, + + size: { + sm: { + root: { + '--select-trigger-height': 'sizes.8', + '--select-trigger-padding-right': 'spacing.8', + '--select-trigger-padding-left': 'spacing.2', + }, + content: { + px: '0', + py: '4', + textStyle: 'md', + }, + trigger: { + textStyle: 'sm', + gap: '1', + }, + indicatorGroup: { + pr: '2', + pl: '1', + }, + item: { + py: '5px', + px: '4', + }, + }, + lg: { + root: { + '--select-trigger-height': '60px', + '--select-trigger-padding-right': '44px', + '--select-trigger-padding-left': 'spacing.4', + }, + content: { + px: '0', + py: '4', + textStyle: 'md', + }, + trigger: { + py: '2', + }, + item: { + py: '5px', + px: '4', + }, + indicatorGroup: { + pr: '4', + pl: '2', + }, + }, + }, + }, + + compoundVariants: [ + { + size: 'sm', + variant: 'outline', + css: { + trigger: { + _placeholderShown: { + color: 'select.trigger.outline.fg', + _hover: { + color: 'select.trigger.outline.fg', + }, + }, + }, + }, + }, + ], + + defaultVariants: { + size: 'sm', + variant: 'outline', + }, +}); diff --git a/toolkit/theme/recipes/skeleton.recipe.ts b/toolkit/theme/recipes/skeleton.recipe.ts new file mode 100644 index 0000000000..919578ac8f --- /dev/null +++ b/toolkit/theme/recipes/skeleton.recipe.ts @@ -0,0 +1,56 @@ +import { defineRecipe } from '@chakra-ui/react'; + +export const recipe = defineRecipe({ + base: {}, + + variants: { + loading: { + // special value to override the default behavior of the skeleton in Chakra + // it uses "background: unset" when the loading prop is set to false + // but it causes issues with background color of child element (e.g. button, badge, etc.) + // so, instead of the "loading" prop, we use the "state" prop to control the skeleton (see below) + reset: {}, + }, + state: { + loading: { + borderRadius: 'base', + boxShadow: 'none', + backgroundClip: 'padding-box', + cursor: 'default', + color: 'transparent', + borderWidth: '0px', + pointerEvents: 'none', + userSelect: 'none', + '&::before, &::after, *': { + visibility: 'hidden', + }, + }, + }, + variant: { + pulse: { + background: 'bg.emphasized', + animation: 'pulse', + animationDuration: 'var(--duration, 1.2s)', + }, + shine: { + '--animate-from': '100%', + '--animate-to': '-100%', + '--start-color': 'colors.skeleton.bg.start', + '--end-color': 'colors.skeleton.bg.end', + backgroundImage: + 'linear-gradient(90deg,var(--start-color) 8%,var(--end-color) 18%,var(--start-color) 33%)', + backgroundColor: 'transparent', + backgroundSize: '200% 100%', + animation: 'bg-position var(--duration, 2s) linear infinite', + }, + none: { + animation: 'none', + }, + }, + }, + + defaultVariants: { + variant: 'shine', + loading: 'reset', + }, +}); diff --git a/toolkit/theme/recipes/spinner.recipe.ts b/toolkit/theme/recipes/spinner.recipe.ts new file mode 100644 index 0000000000..cae5cb9835 --- /dev/null +++ b/toolkit/theme/recipes/spinner.recipe.ts @@ -0,0 +1,31 @@ +import { defineRecipe } from '@chakra-ui/react'; + +export const recipe = defineRecipe({ + base: { + display: 'inline-block', + borderColor: 'blue.500', + borderStyle: 'solid', + borderWidth: '2px', + borderRadius: 'full', + width: 'var(--spinner-size)', + height: 'var(--spinner-size)', + animation: 'spin', + animationDuration: 'slowest', + '--spinner-track-color': '{colors.spinner.track}', + borderBottomColor: 'var(--spinner-track-color)', + borderInlineStartColor: 'var(--spinner-track-color)', + }, + variants: { + size: { + inherit: { '--spinner-size': '1em' }, + xs: { '--spinner-size': 'sizes.3' }, + sm: { '--spinner-size': 'sizes.4' }, + md: { '--spinner-size': 'sizes.5' }, + lg: { '--spinner-size': 'sizes.8' }, + xl: { '--spinner-size': 'sizes.10' }, + }, + }, + defaultVariants: { + size: 'md', + }, +}); diff --git a/toolkit/theme/recipes/stat.recipe.ts b/toolkit/theme/recipes/stat.recipe.ts new file mode 100644 index 0000000000..3431ad23e7 --- /dev/null +++ b/toolkit/theme/recipes/stat.recipe.ts @@ -0,0 +1,101 @@ +import { defineSlotRecipe } from '@chakra-ui/react'; + +export const recipe = defineSlotRecipe({ + slots: [ 'root', 'label', 'helpText', 'valueUnit', 'valueText', 'indicator' ], + base: { + root: { + display: 'flex', + flexDirection: 'column', + gap: '1', + position: 'relative', + flex: '1', + }, + label: { + display: 'inline-flex', + gap: '1.5', + alignItems: 'center', + color: 'text', + textStyle: 'sm', + }, + helpText: { + color: 'text', + textStyle: 'xs', + }, + valueUnit: { + color: 'text', + textStyle: 'xs', + fontWeight: 'initial', + letterSpacing: 'initial', + }, + valueText: { + verticalAlign: 'baseline', + fontWeight: 'semibold', + letterSpacing: 'normal', + fontFeatureSettings: 'initial', + fontVariantNumeric: 'initial', + display: 'inline-flex', + gap: '1', + }, + indicator: { + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + marginEnd: 0, + '& :where(svg)': { + w: '1em', + h: '1em', + }, + '&[data-type=up]': { + color: 'stat.indicator.up', + }, + '&[data-type=down]': { + color: 'stat.indicator.down', + }, + }, + }, + + variants: { + orientation: { + horizontal: { + root: { + flexDirection: 'row', + alignItems: 'center', + }, + }, + }, + positive: { + 'true': { + valueText: { + color: 'stat.indicator.up', + }, + }, + 'false': { + valueText: { + color: 'stat.indicator.down', + }, + }, + }, + size: { + sm: { + valueText: { + textStyle: 'sm', + }, + }, + md: { + valueText: { + textStyle: 'md', + }, + }, + lg: { + valueText: { + textStyle: 'lg', + }, + }, + }, + }, + + defaultVariants: { + size: 'md', + orientation: 'horizontal', + }, +}); diff --git a/toolkit/theme/recipes/switch.recipe.ts b/toolkit/theme/recipes/switch.recipe.ts new file mode 100644 index 0000000000..da551a7ab9 --- /dev/null +++ b/toolkit/theme/recipes/switch.recipe.ts @@ -0,0 +1,130 @@ +import { defineSlotRecipe } from '@chakra-ui/react'; + +export const recipe = defineSlotRecipe({ + slots: [ 'root', 'label', 'indicator', 'control', 'thumb' ], + className: 'chakra-switch', + base: { + root: { + display: 'inline-flex', + gap: '2.5', + alignItems: 'center', + position: 'relative', + verticalAlign: 'middle', + '--switch-diff': 'calc(var(--switch-width) - var(--switch-height))', + '--switch-x': { + base: 'var(--switch-diff)', + _rtl: 'calc(var(--switch-diff) * -1)', + }, + }, + + label: { + lineHeight: '1', + userSelect: 'none', + fontSize: 'sm', + fontWeight: '400', + _disabled: { + opacity: '0.5', + }, + }, + + indicator: { + position: 'absolute', + height: 'var(--switch-height)', + width: 'var(--switch-height)', + fontSize: 'var(--switch-indicator-font-size)', + flexShrink: 0, + userSelect: 'none', + display: 'grid', + placeContent: 'center', + transition: 'inset-inline-start 0.12s ease', + insetInlineStart: 'calc(var(--switch-x) - 2px)', + _checked: { + insetInlineStart: '2px', + }, + }, + + control: { + display: 'inline-flex', + gap: '0.5rem', + flexShrink: 0, + justifyContent: 'flex-start', + cursor: 'switch', + borderRadius: 'full', + position: 'relative', + width: 'var(--switch-width)', + height: 'var(--switch-height)', + _disabled: { + opacity: '0.5', + cursor: 'not-allowed', + }, + _invalid: { + outline: '2px solid', + outlineColor: 'border.error', + outlineOffset: '2px', + }, + }, + + thumb: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + flexShrink: 0, + transitionProperty: 'translate', + transitionDuration: 'fast', + borderRadius: 'inherit', + _checked: { + translate: 'var(--switch-x) 0', + }, + }, + }, + + variants: { + variant: { + primary: { + control: { + borderRadius: 'full', + bg: 'switch.primary.bg', + focusVisibleRing: 'outside', + _checked: { + bg: 'switch.primary.bg.checked', + _hover: { + bg: 'switch.primary.bg.hover', + }, + }, + }, + thumb: { + bg: 'white', + width: 'var(--switch-height)', + height: 'var(--switch-height)', + scale: '0.8', + boxShadow: 'sm', + _checked: { + bg: 'white', + }, + }, + }, + }, + + size: { + sm: { + root: { + '--switch-width': '26px', + '--switch-height': 'sizes.4', + '--switch-indicator-font-size': 'fontSizes.sm', + }, + }, + md: { + root: { + '--switch-width': '34px', + '--switch-height': 'sizes.5', + '--switch-indicator-font-size': 'fontSizes.md', + }, + }, + }, + }, + + defaultVariants: { + variant: 'primary', + size: 'md', + }, +}); diff --git a/toolkit/theme/recipes/table.recipe.ts b/toolkit/theme/recipes/table.recipe.ts new file mode 100644 index 0000000000..89511ce483 --- /dev/null +++ b/toolkit/theme/recipes/table.recipe.ts @@ -0,0 +1,84 @@ +import { defineSlotRecipe } from '@chakra-ui/react'; + +export const recipe = defineSlotRecipe({ + slots: [ 'root', 'row', 'cell', 'columnHeader', 'caption', 'footer', 'body', 'header' ], + base: { + root: { + tableLayout: 'fixed', + fontVariant: 'normal', + fontVariantLigatures: 'no-contextual', + borderCollapse: 'collapse', + width: 'full', + textAlign: 'start', + verticalAlign: 'top', + overflow: 'unset', + }, + cell: { + textAlign: 'start', + alignItems: 'center', + verticalAlign: 'top', + fontWeight: 'medium', + }, + columnHeader: { + fontWeight: 'medium', + textAlign: 'start', + }, + }, + + variants: { + variant: { + line: { + columnHeader: { + color: 'table.header.fg', + backgroundColor: 'table.header.bg', + _first: { + borderTopLeftRadius: '8px', + }, + _last: { + borderTopRightRadius: '8px', + }, + }, + cell: { + borderBottomWidth: '1px', + borderColor: 'border.divider', + }, + row: { + bg: 'bg', + }, + }, + }, + + size: { + md: { + root: { + fontSize: 'sm', + }, + columnHeader: { + px: '6px', + py: '10px', + _first: { + pl: 3, + }, + _last: { + pr: 3, + }, + }, + cell: { + px: '6px', + py: 4, + _first: { + pl: 3, + }, + _last: { + pr: 3, + }, + }, + }, + }, + }, + + defaultVariants: { + variant: 'line', + size: 'md', + }, +}); diff --git a/toolkit/theme/recipes/tabs.recipe.ts b/toolkit/theme/recipes/tabs.recipe.ts new file mode 100644 index 0000000000..9c68fe1077 --- /dev/null +++ b/toolkit/theme/recipes/tabs.recipe.ts @@ -0,0 +1,219 @@ +import { defineSlotRecipe } from '@chakra-ui/react'; + +export const recipe = defineSlotRecipe({ + slots: [ 'root', 'list', 'trigger', 'content', 'indicator' ], + base: { + root: { + '--tabs-trigger-radius': 'radii.l2', + position: 'relative', + _horizontal: { + display: 'block', + }, + _vertical: { + display: 'flex', + }, + }, + list: { + display: 'inline-flex', + width: '100%', + position: 'relative', + isolation: 'isolate', + '--tabs-indicator-shadow': 'shadows.xs', + '--tabs-indicator-bg': 'colors.bg', + minH: 'var(--tabs-height)', + _horizontal: { + flexDirection: 'row', + }, + _vertical: { + flexDirection: 'column', + }, + }, + trigger: { + outline: '0', + minW: 'var(--tabs-height)', + height: 'var(--tabs-height)', + display: 'flex', + alignItems: 'center', + position: 'relative', + cursor: 'button', + gap: '2', + _focusVisible: { + zIndex: 1, + outline: '2px solid', + outlineColor: 'colorPalette.focusRing', + }, + _disabled: { + cursor: 'not-allowed', + opacity: 0.5, + }, + }, + content: { + focusVisibleRing: 'inside', + _horizontal: { + width: '100%', + pt: 'var(--tabs-content-padding)', + }, + _vertical: { + height: '100%', + ps: 'var(--tabs-content-padding)', + }, + }, + indicator: { + width: 'var(--width)', + height: 'var(--height)', + borderRadius: 'var(--tabs-indicator-radius)', + bg: 'var(--tabs-indicator-bg)', + shadow: 'var(--tabs-indicator-shadow)', + zIndex: -1, + }, + }, + + variants: { + fitted: { + 'true': { + list: { + display: 'flex', + }, + trigger: { + flex: 1, + textAlign: 'center', + justifyContent: 'center', + }, + }, + }, + + justify: { + start: { + list: { + justifyContent: 'flex-start', + }, + }, + center: { + list: { + justifyContent: 'center', + }, + }, + end: { + list: { + justifyContent: 'flex-end', + }, + }, + }, + + size: { + sm: { + root: { + '--tabs-height': 'sizes.8', + '--tabs-content-padding': 'spacing.6', + }, + trigger: { + py: '1', + px: '3', + textStyle: 'sm', + }, + }, + md: { + root: { + '--tabs-height': 'sizes.10', + '--tabs-content-padding': 'spacing.6', + }, + trigger: { + py: '2', + px: '4', + textStyle: 'md', + }, + }, + free: {}, + }, + + variant: { + solid: { + trigger: { + fontWeight: '600', + gap: '1', + borderRadius: 'base', + color: 'tabs.solid.fg', + bg: 'transparent', + _selected: { + bg: 'tabs.solid.bg.selected', + color: 'tabs.solid.fg.selected', + _hover: { + color: 'tabs.solid.fg.selected', + }, + }, + _hover: { + color: 'link.primary.hover', + }, + }, + }, + secondary: { + list: { + border: 'none', + columnGap: '2', + _horizontal: { + _before: { + display: 'none', + }, + }, + }, + trigger: { + fontWeight: '500', + color: 'tabs.secondary.fg', + bg: 'transparent', + borderWidth: '2px', + borderStyle: 'solid', + borderColor: 'tabs.secondary.border', + borderRadius: 'base', + _selected: { + bg: 'tabs.secondary.bg.selected', + borderColor: 'transparent', + _hover: { + borderColor: 'transparent', + }, + }, + _hover: { + color: 'link.primary.hover', + borderColor: 'link.primary.hover', + }, + }, + }, + segmented: { + trigger: { + color: 'tabs.segmented.fg', + bg: 'transparent', + borderWidth: '2px', + borderStyle: 'solid', + borderColor: 'tabs.segmented.border', + _hover: { + color: 'link.primary.hover', + }, + _selected: { + color: 'tabs.segmented.fg.selected', + bg: 'tabs.segmented.border', + borderColor: 'tabs.segmented.border', + _hover: { + color: 'tabs.segmented.fg.selected', + }, + }, + _notFirst: { + borderLeftWidth: '0', + }, + _first: { + borderTopLeftRadius: 'base', + borderBottomLeftRadius: 'base', + }, + _last: { + borderTopRightRadius: 'base', + borderBottomRightRadius: 'base', + }, + }, + }, + unstyled: {}, + }, + }, + + defaultVariants: { + size: 'md', + variant: 'solid', + }, +}); diff --git a/toolkit/theme/recipes/tag.recipe.ts b/toolkit/theme/recipes/tag.recipe.ts new file mode 100644 index 0000000000..1136c6398c --- /dev/null +++ b/toolkit/theme/recipes/tag.recipe.ts @@ -0,0 +1,145 @@ +import { defineSlotRecipe } from '@chakra-ui/react'; + +export const recipe = defineSlotRecipe({ + slots: [ 'root', 'label', 'startElement', 'endElement', 'closeTrigger' ], + base: { + root: { + display: 'inline-flex', + alignItems: 'center', + verticalAlign: 'top', + maxWidth: '100%', + userSelect: 'none', + borderRadius: 'sm', + focusVisibleRing: 'outside', + _loading: { + borderRadius: 'sm', + }, + }, + label: { + lineClamp: '1', + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + fontWeight: 'medium', + display: 'inline', + }, + closeTrigger: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + outline: '0', + borderRadius: 'none', + color: 'closeButton.fg', + focusVisibleRing: 'inside', + focusRingWidth: '2px', + _hover: { + color: 'link.primary.hover', + }, + }, + startElement: { + flexShrink: 0, + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + boxSize: 'var(--tag-element-size)', + ms: 'var(--tag-element-offset)', + '&:has([data-scope=avatar])': { + boxSize: 'var(--tag-avatar-size)', + ms: 'calc(var(--tag-element-offset) * 1.5)', + }, + _icon: { boxSize: '100%' }, + }, + endElement: { + flexShrink: 0, + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + boxSize: 'var(--tag-element-size)', + me: 'var(--tag-element-offset)', + _icon: { boxSize: '100%' }, + '&:has(button)': { + ms: 'calc(var(--tag-element-offset) * -1)', + }, + }, + }, + + variants: { + size: { + md: { + root: { + px: '1', + py: '0.5', + minH: '6', + gap: '1', + '--tag-avatar-size': 'spacing.4', + '--tag-element-size': 'spacing.3', + '--tag-element-offset': '0px', + }, + label: { + textStyle: 'sm', + }, + }, + lg: { + root: { + px: '6px', + py: '6px', + minH: '8', + gap: '1', + '--tag-avatar-size': 'spacing.4', + '--tag-element-size': 'spacing.3', + '--tag-element-offset': '0px', + }, + label: { + textStyle: 'sm', + }, + }, + }, + + variant: { + subtle: { + root: { + bgColor: 'tag.root.subtle.bg', + color: 'tag.root.subtle.fg', + }, + }, + clickable: { + root: { + cursor: 'pointer', + bgColor: 'tag.root.clickable.bg', + color: 'tag.root.clickable.fg', + _hover: { + opacity: 0.76, + }, + }, + }, + filter: { + root: { + bgColor: 'tag.root.filter.bg', + }, + }, + select: { + root: { + cursor: 'pointer', + bgColor: 'tag.root.select.bg', + color: 'tag.root.select.fg', + _hover: { + color: 'blue.400', + opacity: 0.76, + }, + _selected: { + bgColor: 'tag.root.select.bg.selected', + color: 'whiteAlpha.800', + _hover: { + color: 'whiteAlpha.800', + opacity: 0.76, + }, + }, + }, + }, + }, + }, + + defaultVariants: { + size: 'md', + variant: 'subtle', + }, +}); diff --git a/toolkit/theme/recipes/textarea.recipe.ts b/toolkit/theme/recipes/textarea.recipe.ts new file mode 100644 index 0000000000..3c569ac6e6 --- /dev/null +++ b/toolkit/theme/recipes/textarea.recipe.ts @@ -0,0 +1,86 @@ +import { defineRecipe } from '@chakra-ui/react'; + +export const recipe = defineRecipe({ + base: { + width: '100%', + minWidth: '0', + minHeight: '160px', + outline: '0', + position: 'relative', + appearance: 'none', + textAlign: 'start', + borderRadius: 'base', + color: 'input.fg', + '--focus-color': 'colors.border.error', + '--error-color': 'colors.border.error', + _invalid: { + focusRingColor: 'var(--error-color)', + borderColor: 'var(--error-color)', + }, + }, + variants: { + size: { + '2xl': { + textStyle: 'md', + py: '4', + pl: '4', + pr: '5', // === scrollbar width + scrollPaddingBottom: '4', + }, + }, + + variant: { + outline: { + bg: 'input.bg', + borderWidth: '2px', + borderColor: 'input.border', + focusVisibleRing: 'none', + _hover: { + borderColor: 'input.border.hover', + }, + _focus: { + borderColor: 'input.border.focus', + boxShadow: 'size.md', + _hover: { + borderColor: 'input.border.focus', + }, + }, + _readOnly: { + userSelect: 'all', + bg: 'input.bg.readOnly', + borderColor: 'input.border.readOnly', + _focus: { + borderColor: 'input.border.readOnly', + }, + _hover: { + borderColor: 'input.border.readOnly', + }, + }, + _disabled: { + pointerEvents: 'none', + opacity: 'control.disabled', + }, + _invalid: { + borderColor: 'input.border.error', + _hover: { + borderColor: 'input.border.error', + }, + }, + }, + }, + + floating: { + 'true': { + paddingTop: '8', + _placeholderShown: { + paddingTop: '10', + }, + }, + }, + }, + + defaultVariants: { + size: '2xl', + variant: 'outline', + }, +}); diff --git a/toolkit/theme/recipes/toast.recipe.ts b/toolkit/theme/recipes/toast.recipe.ts new file mode 100644 index 0000000000..edbb674787 --- /dev/null +++ b/toolkit/theme/recipes/toast.recipe.ts @@ -0,0 +1,97 @@ +import { defineSlotRecipe } from '@chakra-ui/react'; + +export const recipe = defineSlotRecipe({ + slots: [ 'root', 'title', 'description', 'indicator', 'actionTrigger', 'closeTrigger' ], + base: { + root: { + width: 'full', + display: 'flex', + alignItems: 'flex-start', + position: 'relative', + gap: '3', + py: '3', + ps: '6', + pe: '3', + borderRadius: 'md', + translate: 'var(--x) var(--y)', + scale: 'var(--scale)', + zIndex: 'var(--z-index)', + height: 'var(--height)', + opacity: 'var(--opacity)', + willChange: 'translate, opacity, scale', + transition: + 'translate 400ms, scale 400ms, opacity 400ms, height 400ms, box-shadow 200ms', + transitionTimingFunction: 'cubic-bezier(0.21, 1.02, 0.73, 1)', + _closed: { + transition: 'translate 400ms, scale 400ms, opacity 200ms', + transitionTimingFunction: 'cubic-bezier(0.06, 0.71, 0.55, 1)', + }, + bg: 'toast.bg.info', + color: 'toast.fg', + boxShadow: 'xl', + '--toast-trigger-bg': 'colors.bg.muted', + '&[data-type=warning]': { + color: 'toast.fg', + bg: 'toast.bg.warning', + '--toast-trigger-bg': '{white/10}', + '--toast-border-color': '{white/40}', + }, + '&[data-type=success]': { + color: 'toast.fg', + bg: 'toast.bg.success', + '--toast-trigger-bg': '{white/10}', + '--toast-border-color': '{white/40}', + }, + '&[data-type=error]': { + color: 'toast.fg', + bg: 'toast.bg.error', + '--toast-trigger-bg': '{white/10}', + '--toast-border-color': '{white/40}', + }, + '&[data-type=info]': { + color: 'toast.fg', + bg: 'toast.bg.info', + '--toast-trigger-bg': '{white/10}', + '--toast-border-color': '{white/40}', + }, + '&[data-type=loading]': { + color: 'toast.fg', + bg: 'toast.bg.info', + '--toast-trigger-bg': '{white/10}', + '--toast-border-color': '{white/40}', + }, + }, + title: { + fontWeight: '700', + textStyle: 'md', + marginEnd: '0', + }, + description: { + display: 'inline', + textStyle: 'md', + }, + indicator: { + flexShrink: '0', + boxSize: '5', + }, + actionTrigger: { + textStyle: 'sm', + fontWeight: 'medium', + height: '8', + px: '3', + borderRadius: 'base', + alignSelf: 'center', + borderWidth: '1px', + borderColor: 'var(--toast-border-color, inherit)', + transition: 'background 200ms', + _hover: { + bg: 'var(--toast-trigger-bg)', + }, + }, + closeTrigger: { + position: 'static', + alignSelf: 'center', + color: 'closeButton.fg', + }, + }, +}); diff --git a/toolkit/theme/recipes/tooltip.recipe.ts b/toolkit/theme/recipes/tooltip.recipe.ts new file mode 100644 index 0000000000..4acea64d12 --- /dev/null +++ b/toolkit/theme/recipes/tooltip.recipe.ts @@ -0,0 +1,85 @@ +import { defineSlotRecipe } from '@chakra-ui/react'; + +export const recipe = defineSlotRecipe({ + slots: [ 'content', 'arrow', 'arrowTip' ], + base: { + content: { + px: '2', + py: '1', + borderRadius: 'sm', + fontWeight: '500', + textStyle: 'sm', + textAlign: 'center', + boxShadow: 'size.md', + zIndex: 'tooltip', + maxW: '320px', + transformOrigin: 'var(--transform-origin)', + _open: { + animationStyle: 'scale-fade-in', + animationDuration: 'fast', + }, + _closed: { + animationStyle: 'scale-fade-out', + animationDuration: 'fast', + }, + }, + arrow: { + '--arrow-size': 'sizes.2', + '--arrow-background': 'var(--tooltip-bg)', + }, + arrowTip: { + borderTopWidth: '1px', + borderInlineStartWidth: '1px', + borderColor: 'var(--tooltip-bg)', + }, + }, + variants: { + variant: { + regular: { + content: { + '--tooltip-bg': 'colors.tooltip.bg', + bg: 'var(--tooltip-bg)', + color: 'tooltip.fg', + }, + }, + navigation: { + content: { + '--tooltip-bg': 'colors.tooltip.navigation.bg', + bg: 'var(--tooltip-bg)', + color: 'tooltip.navigation.fg', + borderWidth: '0', + borderRadius: 'base', + minW: '120px', + boxShadow: 'none', + textAlign: 'center', + padding: '15px 12px', + _selected: { + color: 'tooltip.navigation.fg.selected', + }, + }, + arrow: { + display: 'none', + }, + arrowTip: { + display: 'none', + }, + }, + popover: { + content: { + maxW: 'none', + bg: 'popover.bg', + color: 'text.primary', + p: '4', + boxShadow: 'popover', + boxShadowColor: 'popover.shadow', + borderRadius: 'md', + textAlign: 'left', + fontWeight: 'normal', + }, + }, + }, + }, + defaultVariants: { + variant: 'regular', + }, +}); diff --git a/toolkit/theme/theme.ts b/toolkit/theme/theme.ts new file mode 100644 index 0000000000..f6f1f15cb2 --- /dev/null +++ b/toolkit/theme/theme.ts @@ -0,0 +1,41 @@ +import { createSystem, defaultConfig, defineConfig } from '@chakra-ui/react'; + +import { keyframes } from './foundations/animations'; +import * as borders from './foundations/borders'; +import breakpoints from './foundations/breakpoints'; +import colors from './foundations/colors'; +import durations from './foundations/durations'; +import semanticTokens from './foundations/semanticTokens'; +import shadows from './foundations/shadows'; +import { fonts, textStyles } from './foundations/typography'; +import zIndex from './foundations/zIndex'; +import globalCss from './globalCss'; +import { recipes, slotRecipes } from './recipes'; + +const customConfig = defineConfig({ + globalCss, + theme: { + breakpoints, + keyframes, + recipes, + slotRecipes, + semanticTokens, + textStyles, + tokens: { + ...borders, + colors, + durations, + fonts, + shadows, + zIndex, + fontWeights: { + normal: { value: '400' }, + medium: { value: '500' }, + semibold: { value: '600' }, + bold: { value: '700' }, + }, + }, + }, +}); + +export default createSystem(defaultConfig, customConfig); diff --git a/toolkit/theme/utils/entries.ts b/toolkit/theme/utils/entries.ts new file mode 100644 index 0000000000..1205e3dbde --- /dev/null +++ b/toolkit/theme/utils/entries.ts @@ -0,0 +1,12 @@ +// https://github.com/chakra-ui/chakra-ui/blob/main/packages/react/src/utils/entries.ts#L1 +export function mapEntries( + obj: { [key in K]: A }, + f: (key: K, val: A) => [K, B], +): { [key in K]: B } { + const result: { [key in K]: B } = {} as unknown as { [key in K]: B }; + for (const key in obj) { + const kv = f(key, obj[key]); + result[kv[0]] = kv[1]; + } + return result; +} diff --git a/toolkit/utils/getComponentDisplayName.ts b/toolkit/utils/getComponentDisplayName.ts new file mode 100644 index 0000000000..12893671a7 --- /dev/null +++ b/toolkit/utils/getComponentDisplayName.ts @@ -0,0 +1,11 @@ +import type React from 'react'; + +export default function getComponentDisplayName(type: string | React.JSXElementConstructor) { + if (typeof type === 'string') { + return; + } + + if ('displayName' in type) { + return type.displayName as string; + } +} diff --git a/tools/preset-sync/index.ts b/tools/preset-sync/index.ts index 6feccfe352..e1982823e3 100755 --- a/tools/preset-sync/index.ts +++ b/tools/preset-sync/index.ts @@ -11,13 +11,15 @@ const PRESETS = { eth: 'https://eth.blockscout.com', eth_goerli: 'https://eth-goerli.blockscout.com', eth_sepolia: 'https://eth-sepolia.blockscout.com', - garnet: 'https://explorer.garnetchain.com', filecoin: 'https://filecoin.blockscout.com', + garnet: 'https://explorer.garnetchain.com', gnosis: 'https://gnosis.blockscout.com', + immutable: 'https://explorer.immutable.com', mekong: 'https://mekong.blockscout.com', neon_devnet: 'https://neon-devnet.blockscout.com', optimism: 'https://optimism.blockscout.com', optimism_celestia: 'https://opcelestia-raspberry.gelatoscout.com', + optimism_interop_0: 'https://optimism-interop-alpha-0.blockscout.com', optimism_sepolia: 'https://optimism-sepolia.blockscout.com', polygon: 'https://polygon.blockscout.com', rari_testnet: 'https://rari-testnet.cloud.blockscout.com', diff --git a/tsconfig.json b/tsconfig.json index 29c8095af6..b93f43ec24 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,13 +8,14 @@ "forceConsistentCasingInFileNames": true, "noEmit": true, "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "node", + "module": "ESNext", + "moduleResolution": "Bundler", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", "incremental": true, "baseUrl": ".", + "types": ["node", "jest"], }, "include": ["next-env.d.ts", "**/*.ts", "**/*.node.ts", "**/*.tsx", "**/*.pw.tsx", "decs.d.ts", "global.d.ts"], "exclude": ["node_modules", "node_modules_linux", "./deploy/tools/envs-validator", "./deploy/tools/favicon-generator"], diff --git a/types/api/addressMetadata.ts b/types/api/addressMetadata.ts index 6840a8235f..71f419a29e 100644 --- a/types/api/addressMetadata.ts +++ b/types/api/addressMetadata.ts @@ -1,4 +1,4 @@ -import type { AlertStatus } from '@chakra-ui/react'; +import type { AlertProps } from 'toolkit/chakra/alert'; export interface AddressMetadataInfo { addresses: Record data?: string; alertBgColor?: string; alertTextColor?: string; - alertStatus?: AlertStatus; + alertStatus?: AlertProps['status']; } | null; } diff --git a/types/api/interop.ts b/types/api/interop.ts new file mode 100644 index 0000000000..4b30637fcd --- /dev/null +++ b/types/api/interop.ts @@ -0,0 +1,30 @@ +export interface ChainInfo { + chain_id: number; + chain_name: string | null; + chain_logo: string | null; + instance_url: string; +} + +export type MessageStatus = 'Sent' | 'Relayed' | 'Failed'; + +export interface InteropMessage { + init_transaction_hash: string; + init_chain?: ChainInfo | null; + nonce: number; + payload: string; + relay_chain?: ChainInfo | null; + relay_transaction_hash: string | null; + sender: string; + status: MessageStatus; + target: string; + timestamp: string; +} + +export interface InteropMessageListResponse { + items: Array; + next_page_params?: { + init_transaction_hash: string; + items_count: number; + timestamp: number; + }; +} diff --git a/types/api/rewards.ts b/types/api/rewards.ts index 70df2d98b8..f09412d1d4 100644 --- a/types/api/rewards.ts +++ b/types/api/rewards.ts @@ -12,6 +12,8 @@ export type RewardsConfigResponse = { export type RewardsCheckRefCodeResponse = { valid: boolean; + is_custom: boolean; + reward: string | null; }; export type RewardsNonceResponse = { diff --git a/types/api/token.ts b/types/api/token.ts index c223d70a03..3a850e89e3 100644 --- a/types/api/token.ts +++ b/types/api/token.ts @@ -59,10 +59,12 @@ export interface TokenInstance { holder_address_hash: string | null; image_url: string | null; animation_url: string | null; + media_url?: string | null; + media_type?: string | null; external_app_url: string | null; metadata: Record | null; owner: AddressParam | null; - thumbnails: Partial> | null; + thumbnails: ({ original: string } & Partial, string>>) | null; } export interface TokenInstanceMetadataSocketMessage { diff --git a/types/api/tokenTransfer.ts b/types/api/tokenTransfer.ts index b34b78269a..e2d9a81621 100644 --- a/types/api/tokenTransfer.ts +++ b/types/api/tokenTransfer.ts @@ -1,5 +1,5 @@ import type { AddressParam } from './addressParams'; -import type { TokenInfo, TokenType } from './token'; +import type { TokenInfo, TokenInstance, TokenType } from './token'; export type Erc20TotalPayload = { decimals: string | null; @@ -8,20 +8,24 @@ export type Erc20TotalPayload = { export type Erc721TotalPayload = { token_id: string | null; + token_instance: TokenInstance | null; }; export type Erc1155TotalPayload = { decimals: string | null; value: string; token_id: string | null; + token_instance: TokenInstance | null; }; export type Erc404TotalPayload = { decimals: string; value: string; token_id: null; + token_instance: TokenInstance | null; } | { token_id: string; + token_instance: TokenInstance | null; }; export type TokenTransfer = ( diff --git a/types/api/tokens.ts b/types/api/tokens.ts index b7e2410fa2..4248036036 100644 --- a/types/api/tokens.ts +++ b/types/api/tokens.ts @@ -34,4 +34,4 @@ export interface TokensSorting { export type TokensSortingField = TokensSorting['sort']; -export type TokensSortingValue = `${ TokensSortingField }-${ TokensSorting['order'] }`; +export type TokensSortingValue = `${ TokensSortingField }-${ TokensSorting['order'] }` | 'default'; diff --git a/types/api/transaction.ts b/types/api/transaction.ts index e4e30ecf50..539cd62e56 100644 --- a/types/api/transaction.ts +++ b/types/api/transaction.ts @@ -3,6 +3,7 @@ import type { ArbitrumBatchStatus, ArbitrumL2TxData } from './arbitrumL2'; import type { BlockTransactionsResponse } from './block'; import type { DecodedInput } from './decodedInput'; import type { Fee } from './fee'; +import type { ChainInfo, MessageStatus } from './interop'; import type { NovesTxTranslation } from './noves'; import type { OptimisticL2WithdrawalStatus } from './optimisticL2'; import type { ScrollL2BlockStatus } from './scrollL2'; @@ -15,7 +16,7 @@ export type TransactionRevertReason = { raw: string; } | DecodedInput; -type WrappedTransactionFields = 'decoded_input' | 'fee' | 'gas_limit' | 'gas_price' | 'hash' | 'max_fee_per_gas' | +export type WrappedTransactionFields = 'decoded_input' | 'fee' | 'gas_limit' | 'gas_price' | 'hash' | 'max_fee_per_gas' | 'max_priority_fee_per_gas' | 'method' | 'nonce' | 'raw_input' | 'to' | 'type' | 'value'; export interface OpWithdrawal { @@ -107,6 +108,8 @@ export type Transaction = { scroll?: ScrollTransactionData; // EIP-7702 authorization_list?: Array; + // Interop + op_interop?: InteropTransactionInfo; }; type ArbitrumTransactionData = { @@ -193,7 +196,7 @@ export interface TransactionsSorting { export type TransactionsSortingField = TransactionsSorting['sort']; -export type TransactionsSortingValue = `${ TransactionsSortingField }-${ TransactionsSorting['order'] }`; +export type TransactionsSortingValue = `${ TransactionsSortingField }-${ TransactionsSorting['order'] }` | 'default'; export type ScrollTransactionData = { l1_fee: string; @@ -215,3 +218,15 @@ export interface TxAuthorization { chain_id: number; nonce: number; } + +export interface InteropTransactionInfo { + nonce: number; + payload: string; + init_chain?: ChainInfo | null; + relay_chain?: ChainInfo | null; + init_transaction_hash?: string; + relay_transaction_hash?: string; + sender: string; + status: MessageStatus; + target: string; +} diff --git a/types/api/validators.ts b/types/api/validators.ts index fcc211732f..620759f0c9 100644 --- a/types/api/validators.ts +++ b/types/api/validators.ts @@ -37,7 +37,7 @@ export interface ValidatorsStabilitySorting { export type ValidatorsStabilitySortingField = ValidatorsStabilitySorting['sort']; -export type ValidatorsStabilitySortingValue = `${ ValidatorsStabilitySortingField }-${ ValidatorsStabilitySorting['order'] }`; +export type ValidatorsStabilitySortingValue = `${ ValidatorsStabilitySortingField }-${ ValidatorsStabilitySorting['order'] }` | 'default'; // Blackfort @@ -68,7 +68,7 @@ export interface ValidatorsBlackfortSorting { export type ValidatorsBlackfortSortingField = ValidatorsBlackfortSorting['sort']; -export type ValidatorsBlackfortSortingValue = `${ ValidatorsBlackfortSortingField }-${ ValidatorsBlackfortSorting['order'] }`; +export type ValidatorsBlackfortSortingValue = `${ ValidatorsBlackfortSortingField }-${ ValidatorsBlackfortSorting['order'] }` | 'default'; // Zilliqa export interface ValidatorsZilliqaItem { diff --git a/types/api/verifiedContracts.ts b/types/api/verifiedContracts.ts index 975c46ed8b..da66d4c8c6 100644 --- a/types/api/verifiedContracts.ts +++ b/types/api/verifiedContracts.ts @@ -5,4 +5,4 @@ export interface VerifiedContractsSorting { export type VerifiedContractsSortingField = VerifiedContractsSorting['sort']; -export type VerifiedContractsSortingValue = `${ VerifiedContractsSortingField }-${ VerifiedContractsSorting['order'] }`; +export type VerifiedContractsSortingValue = `${ VerifiedContractsSortingField }-${ VerifiedContractsSorting['order'] }` | 'default'; diff --git a/ui/address/AddressAccountHistory.tsx b/ui/address/AddressAccountHistory.tsx index cd9d495b6a..5e83554a9f 100644 --- a/ui/address/AddressAccountHistory.tsx +++ b/ui/address/AddressAccountHistory.tsx @@ -1,5 +1,4 @@ -import { Box, Hide, Show, Table, - Tbody, Th, Tr } from '@chakra-ui/react'; +import { Box } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import React from 'react'; @@ -11,13 +10,13 @@ import useIsMounted from 'lib/hooks/useIsMounted'; import getQueryParamString from 'lib/router/getQueryParamString'; import { NOVES_TRANSLATE } from 'stubs/noves/NovesTranslate'; import { generateListStub } from 'stubs/utils'; +import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table'; import AddressAccountHistoryTableItem from 'ui/address/accountHistory/AddressAccountHistoryTableItem'; import ActionBar from 'ui/shared/ActionBar'; import DataListDisplay from 'ui/shared/DataListDisplay'; import { getFromToValue } from 'ui/shared/Noves/utils'; import Pagination from 'ui/shared/pagination/Pagination'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; -import TheadSticky from 'ui/shared/TheadSticky'; import AddressAccountHistoryListItem from './accountHistory/AddressAccountHistoryListItem'; import AccountHistoryFilter from './AddressAccountHistoryFilter'; @@ -25,12 +24,11 @@ import AccountHistoryFilter from './AddressAccountHistoryFilter'; const getFilterValue = (getFilterValueFromQuery).bind(null, NovesHistoryFilterValues); type Props = { - scrollRef?: React.RefObject; shouldRender?: boolean; isQueryEnabled?: boolean; }; -const AddressAccountHistory = ({ scrollRef, shouldRender = true, isQueryEnabled = true }: Props) => { +const AddressAccountHistory = ({ shouldRender = true, isQueryEnabled = true }: Props) => { const router = useRouter(); const isMounted = useIsMounted(); @@ -41,7 +39,6 @@ const AddressAccountHistory = ({ scrollRef, shouldRender = true, isQueryEnabled const { data, isError, pagination, isPlaceholderData } = useQueryWithPages({ resourceName: 'noves_address_history', pathParams: { address: currentAddress }, - scrollRef, options: { enabled: isQueryEnabled, placeholderData: generateListStub<'noves_address_history'>(NOVES_TRANSLATE, 10, { hasNextPage: false, pageNumber: 1, pageSize: 10 }), @@ -75,7 +72,7 @@ const AddressAccountHistory = ({ scrollRef, shouldRender = true, isQueryEnabled const content = ( - + { filteredData?.map((item, i) => ( )) } - + - - - - - - - - - - + + + + { filteredData?.map((item, i) => ( )) } - -
+ + + + + Age - + + Action - + + From/To -
-
+ + +
); return ( + > + { content } + ); }; diff --git a/ui/address/AddressAccountHistoryFilter.tsx b/ui/address/AddressAccountHistoryFilter.tsx index b321051b27..7c202f0e99 100644 --- a/ui/address/AddressAccountHistoryFilter.tsx +++ b/ui/address/AddressAccountHistoryFilter.tsx @@ -1,3 +1,4 @@ +import { createListCollection } from '@chakra-ui/react'; import React from 'react'; import type { NovesHistoryFilterValue } from 'types/api/noves'; @@ -11,6 +12,8 @@ const OPTIONS = [ { value: 'sent', label: 'Sent to' }, ]; +const collection = createListCollection({ items: OPTIONS }); + interface Props { hasActiveFilter: boolean; defaultFilter: NovesHistoryFilterValue; @@ -24,11 +27,11 @@ const AccountHistoryFilter = ({ onFilterChange, defaultFilter, hasActiveFilter, return ( ); }; diff --git a/ui/address/AddressBlocksValidated.tsx b/ui/address/AddressBlocksValidated.tsx index 114d47c57b..f0b729fd1a 100644 --- a/ui/address/AddressBlocksValidated.tsx +++ b/ui/address/AddressBlocksValidated.tsx @@ -1,4 +1,4 @@ -import { Hide, Show, Table, Tbody, Th, Tr } from '@chakra-ui/react'; +import { Box } from '@chakra-ui/react'; import { useQueryClient } from '@tanstack/react-query'; import { useRouter } from 'next/router'; import React from 'react'; @@ -14,12 +14,12 @@ import useSocketMessage from 'lib/socket/useSocketMessage'; import { currencyUnits } from 'lib/units'; import { BLOCK } from 'stubs/block'; import { generateListStub } from 'stubs/utils'; +import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table'; import ActionBar, { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; import DataListDisplay from 'ui/shared/DataListDisplay'; import Pagination from 'ui/shared/pagination/Pagination'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice'; -import { default as Thead } from 'ui/shared/TheadSticky'; import AddressBlocksValidatedListItem from './blocksValidated/AddressBlocksValidatedListItem'; import AddressBlocksValidatedTableItem from './blocksValidated/AddressBlocksValidatedTableItem'; @@ -27,12 +27,11 @@ import AddressBlocksValidatedTableItem from './blocksValidated/AddressBlocksVali const OVERLOAD_COUNT = 75; interface Props { - scrollRef?: React.RefObject; shouldRender?: boolean; isQueryEnabled?: boolean; } -const AddressBlocksValidated = ({ scrollRef, shouldRender = true, isQueryEnabled = true }: Props) => { +const AddressBlocksValidated = ({ shouldRender = true, isQueryEnabled = true }: Props) => { const [ socketAlert, setSocketAlert ] = React.useState(''); const [ newItemsCount, setNewItemsCount ] = React.useState(0); @@ -44,7 +43,6 @@ const AddressBlocksValidated = ({ scrollRef, shouldRender = true, isQueryEnabled const query = useQueryWithPages({ resourceName: 'address_blocks_validated', pathParams: { hash: addressHash }, - scrollRef, options: { enabled: isQueryEnabled, placeholderData: generateListStub<'address_blocks_validated'>( @@ -104,19 +102,19 @@ const AddressBlocksValidated = ({ scrollRef, shouldRender = true, isQueryEnabled const content = query.data?.items ? ( <> - - - - - - - - + + + + + Block + Age + Txn + Gas used { !config.UI.views.block.hiddenFields?.total_reward && !config.features.rollup.isEnabled && - } - - - + Reward { currencyUnits.ether } } + + + )) } - -
BlockAgeTxnGas usedReward { currencyUnits.ether }
-
- + + + + { query.pagination.page === 1 && ( )) } - + ) : null; @@ -166,11 +164,12 @@ const AddressBlocksValidated = ({ scrollRef, shouldRender = true, isQueryEnabled return ( + > + { content } + ); }; diff --git a/ui/address/AddressContract.pw.tsx b/ui/address/AddressContract.pw.tsx index 0229581a08..72f537af77 100644 --- a/ui/address/AddressContract.pw.tsx +++ b/ui/address/AddressContract.pw.tsx @@ -33,7 +33,7 @@ test.describe('ABI functionality', () => { await expect(component.getByRole('button', { name: 'Connect wallet' })).toBeVisible(); await component.getByText('FLASHLOAN_PREMIUM_TOTAL').click(); - await expect(component.getByRole('button', { name: 'Read' })).toBeVisible(); + await expect(component.getByLabel('FLASHLOAN_PREMIUM_TOTAL').getByRole('button', { name: 'Read' })).toBeVisible(); }); test('read, no wallet client', async({ render, createSocket, mockEnvs }) => { @@ -49,7 +49,7 @@ test.describe('ABI functionality', () => { await expect(component.getByRole('button', { name: 'Connect wallet' })).toBeHidden(); await component.getByText('FLASHLOAN_PREMIUM_TOTAL').click(); - await expect(component.getByRole('button', { name: 'Read' })).toBeVisible(); + await expect(component.getByLabel('FLASHLOAN_PREMIUM_TOTAL').getByRole('button', { name: 'Read' })).toBeVisible(); }); test('write', async({ render, createSocket }) => { diff --git a/ui/address/AddressContract.tsx b/ui/address/AddressContract.tsx index a349532a7a..84c3337e8b 100644 --- a/ui/address/AddressContract.tsx +++ b/ui/address/AddressContract.tsx @@ -1,26 +1,22 @@ import React from 'react'; -import type { RoutedSubTab } from 'ui/shared/Tabs/types'; +import type { TabItemRegular } from 'toolkit/components/AdaptiveTabs/types'; -import RoutedTabs from 'ui/shared/Tabs/RoutedTabs'; +import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs'; interface Props { - tabs: Array; + tabs: Array; isLoading: boolean; shouldRender?: boolean; } -const TAB_LIST_PROPS = { - columnGap: 3, -}; - const AddressContract = ({ tabs, isLoading, shouldRender }: Props) => { if (!shouldRender) { return null; } return ( - + ); }; diff --git a/ui/address/AddressCsvExportLink.tsx b/ui/address/AddressCsvExportLink.tsx index 13e470497f..a60e4cf961 100644 --- a/ui/address/AddressCsvExportLink.tsx +++ b/ui/address/AddressCsvExportLink.tsx @@ -1,4 +1,4 @@ -import { chakra, Tooltip, Hide, Flex } from '@chakra-ui/react'; +import { chakra, Flex } from '@chakra-ui/react'; import React from 'react'; import type { CsvExportParams } from 'types/client/address'; @@ -8,9 +8,10 @@ import { route } from 'nextjs-routes'; import config from 'configs/app'; import useIsInitialLoading from 'lib/hooks/useIsInitialLoading'; import useIsMobile from 'lib/hooks/useIsMobile'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Link } from 'toolkit/chakra/link'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import { Tooltip } from 'toolkit/chakra/tooltip'; import IconSvg from 'ui/shared/IconSvg'; -import LinkInternal from 'ui/shared/links/LinkInternal'; interface Props { address: string; @@ -30,27 +31,23 @@ const AddressCsvExportLink = ({ className, address, params, isLoading }: Props) if (isInitialLoading) { return ( - - - - + + ); } return ( - - + - Download CSV - + Download CSV + ); }; diff --git a/ui/address/AddressDetails.tsx b/ui/address/AddressDetails.tsx index 2adb3f5e9a..29ef454365 100644 --- a/ui/address/AddressDetails.tsx +++ b/ui/address/AddressDetails.tsx @@ -1,4 +1,4 @@ -import { Box, Text, Grid } from '@chakra-ui/react'; +import { Box, Text } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import React from 'react'; @@ -12,8 +12,8 @@ import ServiceDegradationWarning from 'ui/shared/alerts/ServiceDegradationWarnin import isCustomAppError from 'ui/shared/AppError/isCustomAppError'; import CopyToClipboard from 'ui/shared/CopyToClipboard'; import DataFetchAlert from 'ui/shared/DataFetchAlert'; -import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem'; -import DetailsSponsoredItem from 'ui/shared/DetailsSponsoredItem'; +import * as DetailedInfo from 'ui/shared/DetailedInfo/DetailedInfo'; +import DetailedInfoSponsoredItem from 'ui/shared/DetailedInfo/DetailedInfoSponsoredItem'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; @@ -31,10 +31,9 @@ import type { AddressQuery } from './utils/useAddressQuery'; interface Props { addressQuery: AddressQuery; - scrollRef?: React.RefObject; } -const AddressDetails = ({ addressQuery, scrollRef }: Props) => { +const AddressDetails = ({ addressQuery }: Props) => { const router = useRouter(); const addressHash = getQueryParamString(router.query.hash); @@ -44,13 +43,6 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => { addressQuery, }); - const handleCounterItemClick = React.useCallback(() => { - window.setTimeout(() => { - // cannot do scroll instantly, have to wait a little - scrollRef?.current?.scrollIntoView({ behavior: 'smooth' }); - }, 500); - }, [ scrollRef ]); - const error404Data = React.useMemo(() => ({ hash: addressHash || '', is_contract: false, @@ -94,54 +86,50 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => { return ( <> { addressQuery.isDegradedData && } - + { data.filecoin?.id && ( <> - ID - - + + { data.filecoin.id } - + ) } { data.filecoin?.actor_type && ( <> - Actor - - + + - + ) } { (data.filecoin?.actor_type === 'evm' || data.filecoin?.actor_type === 'ethaccount') && data?.filecoin?.robust && ( <> - Ethereum Address - - + + - + ) } @@ -149,13 +137,13 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => { { data.is_contract && data.creation_transaction_hash && (creatorAddressHash) && ( <> - Creator - - + + { /> at txn - + ) } - { data.is_contract && data.implementations && data.implementations?.length > 0 && ( + { !addressQuery.isPlaceholderData && data.is_contract && data.implementations && data.implementations?.length > 0 && ( { { data.has_tokens && ( <> - Tokens - - - { addressQuery.data ? : 0 } - + + + { addressQuery.data ? : 0 } + ) } { (config.features.multichainButton.isEnabled || (data.exchange_rate && data.has_tokens)) && ( <> - Net worth - - + + - + ) } - Transactions - - + + { addressQuery.data ? ( ) : 0 } - + { data.has_token_transfers && ( <> - Transfers - - + + { addressQuery.data ? ( ) : 0 } - + ) } { countersQuery.data?.gas_usage_count && ( <> - Gas used - - + + { addressQuery.data ? ( @@ -273,53 +258,52 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => { address={ data.hash } /> ) } - + ) } { data.has_validated_blocks && ( <> - { `Blocks ${ getNetworkValidationActionText() }` } - - + + { addressQuery.data ? ( ) : 0 } - + ) } { data.block_number_balance_updated_at && ( <> - Last balance update - - + + - + ) } - - + + ); }; diff --git a/ui/address/AddressEpochRewards.tsx b/ui/address/AddressEpochRewards.tsx index d97f586a0a..a29919cef4 100644 --- a/ui/address/AddressEpochRewards.tsx +++ b/ui/address/AddressEpochRewards.tsx @@ -1,4 +1,4 @@ -import { Hide, Show } from '@chakra-ui/react'; +import { Box } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import React from 'react'; @@ -16,12 +16,11 @@ import AddressCsvExportLink from './AddressCsvExportLink'; import AddressEpochRewardsListItem from './epochRewards/AddressEpochRewardsListItem'; type Props = { - scrollRef?: React.RefObject; shouldRender?: boolean; isQueryEnabled?: boolean; }; -const AddressEpochRewards = ({ scrollRef, shouldRender = true, isQueryEnabled = true }: Props) => { +const AddressEpochRewards = ({ shouldRender = true, isQueryEnabled = true }: Props) => { const router = useRouter(); const isMounted = useIsMounted(); @@ -32,7 +31,6 @@ const AddressEpochRewards = ({ scrollRef, shouldRender = true, isQueryEnabled = pathParams: { hash, }, - scrollRef, options: { enabled: isQueryEnabled && Boolean(hash), placeholderData: generateListStub<'address_epoch_rewards'>(EPOCH_REWARD_ITEM, 50, { next_page_params: { @@ -51,14 +49,14 @@ const AddressEpochRewards = ({ scrollRef, shouldRender = true, isQueryEnabled = const content = rewardsQuery.data?.items ? ( <> - + - - + + { rewardsQuery.data.items.map((item, index) => ( )) } - + ) : null; @@ -85,11 +83,12 @@ const AddressEpochRewards = ({ scrollRef, shouldRender = true, isQueryEnabled = return ( + > + { content } + ); }; diff --git a/ui/address/AddressInternalTxs.pw.tsx b/ui/address/AddressInternalTxs.pw.tsx index cce88f4c99..c4401f1fec 100644 --- a/ui/address/AddressInternalTxs.pw.tsx +++ b/ui/address/AddressInternalTxs.pw.tsx @@ -14,6 +14,7 @@ const hooksConfig = { }; test('base view +@mobile', async({ render, mockApiResponse }) => { + test.slow(); await mockApiResponse('address_internal_txs', internalTxsMock.baseResponse, { pathParams: { hash: ADDRESS_HASH } }); const component = await render( @@ -21,5 +22,5 @@ test('base view +@mobile', async({ render, mockApiResponse }) => { , { hooksConfig }, ); - await expect(component).toHaveScreenshot(); + await expect(component).toHaveScreenshot({ timeout: 10_000 }); }); diff --git a/ui/address/AddressInternalTxs.tsx b/ui/address/AddressInternalTxs.tsx index 259d623c42..f50950cff7 100644 --- a/ui/address/AddressInternalTxs.tsx +++ b/ui/address/AddressInternalTxs.tsx @@ -1,4 +1,4 @@ -import { Show, Hide } from '@chakra-ui/react'; +import { Box } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import React from 'react'; @@ -24,11 +24,10 @@ import AddressTxsFilter from './AddressTxsFilter'; const getFilterValue = (getFilterValueFromQuery).bind(null, AddressFromToFilterValues); type Props = { - scrollRef?: React.RefObject; shouldRender?: boolean; isQueryEnabled?: boolean; }; -const AddressInternalTxs = ({ scrollRef, shouldRender = true, isQueryEnabled = true }: Props) => { +const AddressInternalTxs = ({ shouldRender = true, isQueryEnabled = true }: Props) => { const router = useRouter(); const isMounted = useIsMounted(); @@ -40,7 +39,6 @@ const AddressInternalTxs = ({ scrollRef, shouldRender = true, isQueryEnabled = t resourceName: 'address_internal_txs', pathParams: { hash }, filters: { filter: filterValue }, - scrollRef, options: { enabled: isQueryEnabled, placeholderData: generateListStub<'address_internal_txs'>( @@ -70,19 +68,19 @@ const AddressInternalTxs = ({ scrollRef, shouldRender = true, isQueryEnabled = t const content = data?.items ? ( <> - + - - + + - + ) : null ; const actionBar = ( + > + { content } + ); }; diff --git a/ui/address/AddressLogs.tsx b/ui/address/AddressLogs.tsx index 9df130cd8d..9cb9e217f1 100644 --- a/ui/address/AddressLogs.tsx +++ b/ui/address/AddressLogs.tsx @@ -15,12 +15,11 @@ import AddressCsvExportLink from './AddressCsvExportLink'; import useAddressQuery from './utils/useAddressQuery'; type Props = { - scrollRef?: React.RefObject; shouldRender?: boolean; isQueryEnabled?: boolean; }; -const AddressLogs = ({ scrollRef, shouldRender = true, isQueryEnabled = true }: Props) => { +const AddressLogs = ({ shouldRender = true, isQueryEnabled = true }: Props) => { const router = useRouter(); const isMounted = useIsMounted(); @@ -28,7 +27,6 @@ const AddressLogs = ({ scrollRef, shouldRender = true, isQueryEnabled = true }: const { data, isPlaceholderData, isError, pagination } = useQueryWithPages({ resourceName: 'address_logs', pathParams: { hash }, - scrollRef, options: { enabled: isQueryEnabled, placeholderData: generateListStub<'address_logs'>(LOG, 3, { next_page_params: { @@ -70,11 +68,12 @@ const AddressLogs = ({ scrollRef, shouldRender = true, isQueryEnabled = true }: return ( + > + { content } + ); }; diff --git a/ui/address/AddressMud.tsx b/ui/address/AddressMud.tsx index 93489f97fe..9c4df0ef7f 100644 --- a/ui/address/AddressMud.tsx +++ b/ui/address/AddressMud.tsx @@ -8,12 +8,11 @@ import AddressMudTable from './mud/AddressMudTable'; import AddressMudTables from './mud/AddressMudTables'; type Props = { - scrollRef?: React.RefObject; shouldRender?: boolean; isQueryEnabled?: boolean; }; -const AddressMud = ({ scrollRef, shouldRender = true, isQueryEnabled = true }: Props) => { +const AddressMud = ({ shouldRender = true, isQueryEnabled = true }: Props) => { const isMounted = useIsMounted(); const router = useRouter(); const tableId = router.query.table_id?.toString(); @@ -24,14 +23,14 @@ const AddressMud = ({ scrollRef, shouldRender = true, isQueryEnabled = true }: P } if (tableId && recordId) { - return ; + return ; } if (tableId) { - return ; + return ; } - return ; + return ; }; export default AddressMud; diff --git a/ui/address/AddressTokenTransfers.tsx b/ui/address/AddressTokenTransfers.tsx index 077dc25b47..bdccc6d2b9 100644 --- a/ui/address/AddressTokenTransfers.tsx +++ b/ui/address/AddressTokenTransfers.tsx @@ -1,4 +1,4 @@ -import { Flex, Hide, Show, Text } from '@chakra-ui/react'; +import { Box, Flex, Text } from '@chakra-ui/react'; import { useQueryClient } from '@tanstack/react-query'; import { useRouter } from 'next/router'; import React from 'react'; @@ -63,14 +63,13 @@ const matchFilters = (filters: Filters, tokenTransfer: TokenTransfer, address?: }; type Props = { - scrollRef?: React.RefObject; shouldRender?: boolean; isQueryEnabled?: boolean; // for tests only overloadCount?: number; }; -const AddressTokenTransfers = ({ scrollRef, overloadCount = OVERLOAD_COUNT, shouldRender = true, isQueryEnabled = true }: Props) => { +const AddressTokenTransfers = ({ overloadCount = OVERLOAD_COUNT, shouldRender = true, isQueryEnabled = true }: Props) => { const router = useRouter(); const queryClient = useQueryClient(); const isMobile = useIsMobile(); @@ -94,7 +93,6 @@ const AddressTokenTransfers = ({ scrollRef, overloadCount = OVERLOAD_COUNT, shou resourceName: 'address_token_transfers', pathParams: { hash: currentAddress }, filters: tokenFilter ? { token: tokenFilter } : filters, - scrollRef, options: { enabled: isQueryEnabled, placeholderData: getTokenTransfersStub(undefined, { @@ -201,7 +199,7 @@ const AddressTokenTransfers = ({ scrollRef, overloadCount = OVERLOAD_COUNT, shou const content = data?.items ? ( <> - + - - + + { pagination.page === 1 && !tokenFilter && ( - + ) : null; @@ -280,15 +278,16 @@ const AddressTokenTransfers = ({ scrollRef, overloadCount = OVERLOAD_COUNT, shou return ( + > + { content } + ); }; diff --git a/ui/address/AddressTokens.pw.tsx b/ui/address/AddressTokens.pw.tsx index 3b2a34af8e..1d3802a1fd 100644 --- a/ui/address/AddressTokens.pw.tsx +++ b/ui/address/AddressTokens.pw.tsx @@ -144,7 +144,7 @@ test.describe('mobile', () => { { hooksConfig }, ); - await component.getByLabel('list').click(); + await component.locator('button').filter({ hasText: 'List' }).click(); await expect(component).toHaveScreenshot(); }); diff --git a/ui/address/AddressTokens.tsx b/ui/address/AddressTokens.tsx index 511c6c0079..a264995d58 100644 --- a/ui/address/AddressTokens.tsx +++ b/ui/address/AddressTokens.tsx @@ -1,4 +1,4 @@ -import { Box, HStack } from '@chakra-ui/react'; +import { Box, chakra, HStack } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import React from 'react'; @@ -15,12 +15,13 @@ import getQueryParamString from 'lib/router/getQueryParamString'; import { NFT_TOKEN_TYPE_IDS } from 'lib/token/tokenTypes'; import { ADDRESS_TOKEN_BALANCE_ERC_20, ADDRESS_NFT_1155, ADDRESS_COLLECTION } from 'stubs/address'; import { generateListStub } from 'stubs/utils'; +import { Button, ButtonGroupRadio } from 'toolkit/chakra/button'; +import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs'; import PopoverFilter from 'ui/shared/filters/PopoverFilter'; import TokenTypeFilter from 'ui/shared/filters/TokenTypeFilter'; +import IconSvg from 'ui/shared/IconSvg'; import Pagination from 'ui/shared/pagination/Pagination'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; -import RadioButtonGroup from 'ui/shared/radioButtonGroup/RadioButtonGroup'; -import RoutedTabs from 'ui/shared/Tabs/RoutedTabs'; import AddressCollections from './tokens/AddressCollections'; import AddressNFTs from './tokens/AddressNFTs'; @@ -33,12 +34,10 @@ const TAB_LIST_PROPS = { mt: 1, mb: { base: 6, lg: 1 }, py: 5, - columnGap: 3, }; const TAB_LIST_PROPS_MOBILE = { my: 8, - columnGap: 3, }; const getTokenFilterValue = (getFilterValuesFromQuery).bind(null, NFT_TOKEN_TYPE_IDS); @@ -96,9 +95,9 @@ const AddressTokens = ({ shouldRender = true, isQueryEnabled = true }: Props) => filters: { type: tokenTypes }, }); - const handleNFTsDisplayTypeChange = React.useCallback((val: TNftDisplayType) => { + const handleNFTsDisplayTypeChange = React.useCallback((val: string) => { cookies.set(cookies.NAMES.ADDRESS_NFT_DISPLAY_TYPE, val); - setNftDisplayType(val); + setNftDisplayType(val as TNftDisplayType); }, []); const handleTokenTypesChange = React.useCallback((value: Array) => { @@ -131,15 +130,20 @@ const AddressTokens = ({ shouldRender = true, isQueryEnabled = true }: Props) => ]; const nftDisplayTypeRadio = ( - - onChange={ handleNFTsDisplayTypeChange } + + onChange={ handleNFTsDisplayTypeChange } + equalWidth + > + + + ); let pagination: PaginationParams | undefined; @@ -158,7 +162,7 @@ const AddressTokens = ({ shouldRender = true, isQueryEnabled = true }: Props) => const rightSlot = ( <> - + { isNftTab && (hasNftData || hasActiveFilters) && nftDisplayTypeRadio } { isNftTab && (hasNftData || hasActiveFilters) && nftTypeFilter } @@ -173,12 +177,11 @@ const AddressTokens = ({ shouldRender = true, isQueryEnabled = true }: Props) => diff --git a/ui/address/AddressTxs.pw.tsx b/ui/address/AddressTxs.pw.tsx index 2f41a65ff7..6fa3830d71 100644 --- a/ui/address/AddressTxs.pw.tsx +++ b/ui/address/AddressTxs.pw.tsx @@ -41,7 +41,7 @@ test.describe('base view', () => { ); }); - test('+@mobile', async() => { + test('desktop', async() => { await expect(component).toHaveScreenshot(); }); @@ -55,6 +55,31 @@ test.describe('base view', () => { }); }); +test.describe('base view', () => { + test.use({ viewport: pwConfig.viewport.mobile }); + + test('mobile', async({ render, mockApiResponse }) => { + await mockApiResponse( + 'address_txs', + { + items: [ + txMock.base, + { ...txMock.base, hash: '0x62d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3194' }, + ], + next_page_params: DEFAULT_PAGINATION, + }, + { pathParams: { hash: CURRENT_ADDRESS } }, + ); + const component = await render( + + + , + { hooksConfig }, + ); + await expect(component).toHaveScreenshot(); + }); +}); + test.describe('socket', () => { // FIXME // test cases which use socket cannot run in parallel since the socket server always run on the same port diff --git a/ui/address/AddressTxs.tsx b/ui/address/AddressTxs.tsx index 4df86bd5b7..dcd1b46ae8 100644 --- a/ui/address/AddressTxs.tsx +++ b/ui/address/AddressTxs.tsx @@ -47,33 +47,32 @@ const matchFilter = (filterValue: AddressFromToFilter, transaction: Transaction, }; type Props = { - scrollRef?: React.RefObject; shouldRender?: boolean; isQueryEnabled?: boolean; // for tests only overloadCount?: number; }; -const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT, shouldRender = true, isQueryEnabled = true }: Props) => { +const AddressTxs = ({ overloadCount = OVERLOAD_COUNT, shouldRender = true, isQueryEnabled = true }: Props) => { const router = useRouter(); const queryClient = useQueryClient(); const isMounted = useIsMounted(); const [ socketAlert, setSocketAlert ] = React.useState(''); const [ newItemsCount, setNewItemsCount ] = React.useState(0); - const [ sort, setSort ] = React.useState(getSortValueFromQuery(router.query, SORT_OPTIONS)); + const [ sort, setSort ] = React.useState(getSortValueFromQuery(router.query, SORT_OPTIONS) || 'default'); const isMobile = useIsMobile(); const currentAddress = getQueryParamString(router.query.hash); - const [ filterValue, setFilterValue ] = React.useState(getFilterValue(router.query.filter)); + const initialFilterValue = getFilterValue(router.query.filter); + const [ filterValue, setFilterValue ] = React.useState(initialFilterValue); const addressTxsQuery = useQueryWithPages({ resourceName: 'address_txs', pathParams: { hash: currentAddress }, filters: { filter: filterValue }, sorting: getSortParamsFromValue(sort), - scrollRef, options: { enabled: isQueryEnabled, placeholderData: generateListStub<'address_txs'>(TX, 50, { next_page_params: { @@ -167,7 +166,7 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT, shouldRender = const filter = ( { !isMobile && ( - + { filter } { currentAddress && csvExportLink } diff --git a/ui/address/AddressTxsFilter.tsx b/ui/address/AddressTxsFilter.tsx index d3bdbd61fe..0d452770cf 100644 --- a/ui/address/AddressTxsFilter.tsx +++ b/ui/address/AddressTxsFilter.tsx @@ -1,3 +1,4 @@ +import { createListCollection } from '@chakra-ui/react'; import React from 'react'; import type { AddressFromToFilter } from 'types/api/address'; @@ -10,25 +11,26 @@ const OPTIONS = [ { value: 'from', label: 'Outgoing transactions' }, { value: 'to', label: 'Incoming transactions' }, ]; +const collection = createListCollection({ items: OPTIONS }); interface Props { hasActiveFilter: boolean; - defaultFilter: AddressFromToFilter; + initialValue: AddressFromToFilter; onFilterChange: (nextValue: string | Array) => void; isLoading?: boolean; } -const AddressTxsFilter = ({ onFilterChange, defaultFilter, hasActiveFilter, isLoading }: Props) => { +const AddressTxsFilter = ({ onFilterChange, initialValue, hasActiveFilter, isLoading }: Props) => { const isInitialLoading = useIsInitialLoading(isLoading); return ( ); }; diff --git a/ui/address/AddressWithdrawals.tsx b/ui/address/AddressWithdrawals.tsx index 414a016a25..30a88a1693 100644 --- a/ui/address/AddressWithdrawals.tsx +++ b/ui/address/AddressWithdrawals.tsx @@ -1,4 +1,4 @@ -import { Show, Hide } from '@chakra-ui/react'; +import { Box } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import React from 'react'; @@ -14,11 +14,10 @@ import BeaconChainWithdrawalsListItem from 'ui/withdrawals/beaconChain/BeaconCha import BeaconChainWithdrawalsTable from 'ui/withdrawals/beaconChain/BeaconChainWithdrawalsTable'; type Props = { - scrollRef?: React.RefObject; shouldRender?: boolean; isQueryEnabled?: boolean; }; -const AddressWithdrawals = ({ scrollRef, shouldRender = true, isQueryEnabled = true }: Props) => { +const AddressWithdrawals = ({ shouldRender = true, isQueryEnabled = true }: Props) => { const router = useRouter(); const isMounted = useIsMounted(); @@ -27,7 +26,6 @@ const AddressWithdrawals = ({ scrollRef, shouldRender = true, isQueryEnabled = t const { data, isPlaceholderData, isError, pagination } = useQueryWithPages({ resourceName: 'address_withdrawals', pathParams: { hash }, - scrollRef, options: { enabled: isQueryEnabled, placeholderData: generateListStub<'address_withdrawals'>(WITHDRAWAL, 50, { next_page_params: { @@ -43,7 +41,7 @@ const AddressWithdrawals = ({ scrollRef, shouldRender = true, isQueryEnabled = t const content = data?.items ? ( <> - + { data.items.map((item, index) => ( )) } - - + + - + ) : null ; @@ -73,11 +71,12 @@ const AddressWithdrawals = ({ scrollRef, shouldRender = true, isQueryEnabled = t return ( + > + { content } + ); }; diff --git a/ui/address/SolidityscanReport.tsx b/ui/address/SolidityscanReport.tsx index a704c53a1a..5e8d29908d 100644 --- a/ui/address/SolidityscanReport.tsx +++ b/ui/address/SolidityscanReport.tsx @@ -1,4 +1,4 @@ -import { Box, Text, Icon, PopoverTrigger, PopoverContent, PopoverBody, useDisclosure } from '@chakra-ui/react'; +import { Box, Text, Icon } from '@chakra-ui/react'; import React from 'react'; // This icon doesn't work properly when it is in the sprite @@ -6,8 +6,8 @@ import React from 'react'; // eslint-disable-next-line no-restricted-imports import solidityScanIcon from 'icons/brands/solidity_scan.svg'; import useFetchReport from 'lib/solidityScan/useFetchReport'; -import Popover from 'ui/shared/chakra/Popover'; -import LinkExternal from 'ui/shared/links/LinkExternal'; +import { Link } from 'toolkit/chakra/link'; +import { PopoverBody, PopoverContent, PopoverRoot } from 'toolkit/chakra/popover'; import SolidityscanReportButton from 'ui/shared/solidityscanReport/SolidityscanReportButton'; import SolidityscanReportDetails from 'ui/shared/solidityscanReport/SolidityscanReportDetails'; import SolidityscanReportScore from 'ui/shared/solidityscanReport/SolidityscanReportScore'; @@ -17,7 +17,6 @@ interface Props { } const SolidityscanReport = ({ hash }: Props) => { - const { isOpen, onToggle, onClose } = useDisclosure(); const { data, isPlaceholderData, isError } = useFetchReport({ hash }); @@ -36,17 +35,13 @@ const SolidityscanReport = ({ hash }: Props) => { const vulnerabilitiesCount = vulnerabilitiesCounts.reduce((acc, val) => acc + val, 0); return ( - - - - + + - + Contract analyzed for 240+ vulnerability patterns by @@ -55,14 +50,14 @@ const SolidityscanReport = ({ hash }: Props) => { { vulnerabilities && vulnerabilitiesCount > 0 && ( - Vulnerabilities distribution + Vulnerabilities distribution ) } - View full report + View full report - + ); }; diff --git a/ui/address/__screenshots__/AddressCoinBalance.pw.tsx_dark-color-mode_base-view-dark-mode-1.png b/ui/address/__screenshots__/AddressCoinBalance.pw.tsx_dark-color-mode_base-view-dark-mode-1.png index 58ccb93f59..812327ec8c 100644 Binary files a/ui/address/__screenshots__/AddressCoinBalance.pw.tsx_dark-color-mode_base-view-dark-mode-1.png and b/ui/address/__screenshots__/AddressCoinBalance.pw.tsx_dark-color-mode_base-view-dark-mode-1.png differ diff --git a/ui/address/__screenshots__/AddressCoinBalance.pw.tsx_default_base-view-dark-mode-1.png b/ui/address/__screenshots__/AddressCoinBalance.pw.tsx_default_base-view-dark-mode-1.png index ef39e74b09..e60254423e 100644 Binary files a/ui/address/__screenshots__/AddressCoinBalance.pw.tsx_default_base-view-dark-mode-1.png and b/ui/address/__screenshots__/AddressCoinBalance.pw.tsx_default_base-view-dark-mode-1.png differ diff --git a/ui/address/__screenshots__/AddressCoinBalance.pw.tsx_default_mobile-base-view-1.png b/ui/address/__screenshots__/AddressCoinBalance.pw.tsx_default_mobile-base-view-1.png index 6115f79ad2..63f8aa7e76 100644 Binary files a/ui/address/__screenshots__/AddressCoinBalance.pw.tsx_default_mobile-base-view-1.png and b/ui/address/__screenshots__/AddressCoinBalance.pw.tsx_default_mobile-base-view-1.png differ diff --git a/ui/address/__screenshots__/AddressDetails.pw.tsx_default_mobile-contract-1.png b/ui/address/__screenshots__/AddressDetails.pw.tsx_default_mobile-contract-1.png index 7c160310cf..5f3e2d0098 100644 Binary files a/ui/address/__screenshots__/AddressDetails.pw.tsx_default_mobile-contract-1.png and b/ui/address/__screenshots__/AddressDetails.pw.tsx_default_mobile-contract-1.png differ diff --git a/ui/address/__screenshots__/AddressDetails.pw.tsx_default_mobile-filecoin-1.png b/ui/address/__screenshots__/AddressDetails.pw.tsx_default_mobile-filecoin-1.png index 6f79f2dd68..9e9004e879 100644 Binary files a/ui/address/__screenshots__/AddressDetails.pw.tsx_default_mobile-filecoin-1.png and b/ui/address/__screenshots__/AddressDetails.pw.tsx_default_mobile-filecoin-1.png differ diff --git a/ui/address/__screenshots__/AddressDetails.pw.tsx_default_mobile-validator-1.png b/ui/address/__screenshots__/AddressDetails.pw.tsx_default_mobile-validator-1.png index fdc0888b51..12132c9c88 100644 Binary files a/ui/address/__screenshots__/AddressDetails.pw.tsx_default_mobile-validator-1.png and b/ui/address/__screenshots__/AddressDetails.pw.tsx_default_mobile-validator-1.png differ diff --git a/ui/address/__screenshots__/AddressEpochRewards.pw.tsx_default_base-view-mobile-1.png b/ui/address/__screenshots__/AddressEpochRewards.pw.tsx_default_base-view-mobile-1.png index a29982c419..79ab546eba 100644 Binary files a/ui/address/__screenshots__/AddressEpochRewards.pw.tsx_default_base-view-mobile-1.png and b/ui/address/__screenshots__/AddressEpochRewards.pw.tsx_default_base-view-mobile-1.png differ diff --git a/ui/address/__screenshots__/AddressEpochRewards.pw.tsx_mobile_base-view-mobile-1.png b/ui/address/__screenshots__/AddressEpochRewards.pw.tsx_mobile_base-view-mobile-1.png index 99d0b36bc9..360ded8be4 100644 Binary files a/ui/address/__screenshots__/AddressEpochRewards.pw.tsx_mobile_base-view-mobile-1.png and b/ui/address/__screenshots__/AddressEpochRewards.pw.tsx_mobile_base-view-mobile-1.png differ diff --git a/ui/address/__screenshots__/AddressInternalTxs.pw.tsx_default_base-view-mobile-1.png b/ui/address/__screenshots__/AddressInternalTxs.pw.tsx_default_base-view-mobile-1.png index 0d64c97a27..dc4fa82980 100644 Binary files a/ui/address/__screenshots__/AddressInternalTxs.pw.tsx_default_base-view-mobile-1.png and b/ui/address/__screenshots__/AddressInternalTxs.pw.tsx_default_base-view-mobile-1.png differ diff --git a/ui/address/__screenshots__/AddressInternalTxs.pw.tsx_mobile_base-view-mobile-1.png b/ui/address/__screenshots__/AddressInternalTxs.pw.tsx_mobile_base-view-mobile-1.png index ab937a3be6..a30e284f3f 100644 Binary files a/ui/address/__screenshots__/AddressInternalTxs.pw.tsx_mobile_base-view-mobile-1.png and b/ui/address/__screenshots__/AddressInternalTxs.pw.tsx_mobile_base-view-mobile-1.png differ diff --git a/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_mobile-with-token-filter-and-no-pagination-1.png b/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_mobile-with-token-filter-and-no-pagination-1.png index 9c47d83b6a..690d82cbc2 100644 Binary files a/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_mobile-with-token-filter-and-no-pagination-1.png and b/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_mobile-with-token-filter-and-no-pagination-1.png differ diff --git a/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_mobile-with-token-filter-and-pagination-1.png b/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_mobile-with-token-filter-and-pagination-1.png index ce20551d45..39b7b891ff 100644 Binary files a/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_mobile-with-token-filter-and-pagination-1.png and b/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_mobile-with-token-filter-and-pagination-1.png differ diff --git a/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_with-token-filter-and-no-pagination-1.png b/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_with-token-filter-and-no-pagination-1.png index 5387be0020..2a062b77a3 100644 Binary files a/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_with-token-filter-and-no-pagination-1.png and b/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_with-token-filter-and-no-pagination-1.png differ diff --git a/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_with-token-filter-and-pagination-1.png b/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_with-token-filter-and-pagination-1.png index 229ab9a91b..5902823332 100644 Binary files a/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_with-token-filter-and-pagination-1.png and b/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_with-token-filter-and-pagination-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_collections-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_collections-dark-mode-1.png index 63a64f592c..f8c313663e 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_collections-dark-mode-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_collections-dark-mode-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_erc20-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_erc20-dark-mode-1.png index 3d27aa37c1..43731b96ce 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_erc20-dark-mode-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_erc20-dark-mode-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_nfts-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_nfts-dark-mode-1.png index 9aaaf6dfac..54fe2ae04d 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_nfts-dark-mode-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_nfts-dark-mode-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_collections-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_collections-dark-mode-1.png index ea5d4ec1cc..b41c5af823 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_collections-dark-mode-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_collections-dark-mode-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_erc20-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_erc20-dark-mode-1.png index 55792ab34d..8caf660dff 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_erc20-dark-mode-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_erc20-dark-mode-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-collections-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-collections-1.png index af1b440782..3786f5643f 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-collections-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-collections-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-erc20-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-erc20-1.png index 716c9835f5..3703579f96 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-erc20-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-erc20-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-nfts-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-nfts-1.png index 17132ec07c..ec833181f4 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-nfts-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-nfts-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_nfts-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_nfts-dark-mode-1.png index 76e318c99b..6dbc68727b 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_nfts-dark-mode-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_nfts-dark-mode-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_update-balances-via-socket-base-flow-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_update-balances-via-socket-base-flow-1.png index 5c05da9ea0..8cf44452a3 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_update-balances-via-socket-base-flow-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_update-balances-via-socket-base-flow-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_update-balances-via-socket-base-flow-2.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_update-balances-via-socket-base-flow-2.png index 304c1422a2..7eb6f7b652 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_update-balances-via-socket-base-flow-2.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_update-balances-via-socket-base-flow-2.png differ diff --git a/ui/address/__screenshots__/AddressTxs.pw.tsx_default_base-view-desktop-1.png b/ui/address/__screenshots__/AddressTxs.pw.tsx_default_base-view-desktop-1.png new file mode 100644 index 0000000000..8790543f17 Binary files /dev/null and b/ui/address/__screenshots__/AddressTxs.pw.tsx_default_base-view-desktop-1.png differ diff --git a/ui/address/__screenshots__/AddressTxs.pw.tsx_default_base-view-mobile-1.png b/ui/address/__screenshots__/AddressTxs.pw.tsx_default_base-view-mobile-1.png index 9138f10c12..d64e9a3525 100644 Binary files a/ui/address/__screenshots__/AddressTxs.pw.tsx_default_base-view-mobile-1.png and b/ui/address/__screenshots__/AddressTxs.pw.tsx_default_base-view-mobile-1.png differ diff --git a/ui/address/__screenshots__/AddressTxs.pw.tsx_default_base-view-screen-xl-base-view-1.png b/ui/address/__screenshots__/AddressTxs.pw.tsx_default_base-view-screen-xl-base-view-1.png index 19ddbcbbff..3fdc25082f 100644 Binary files a/ui/address/__screenshots__/AddressTxs.pw.tsx_default_base-view-screen-xl-base-view-1.png and b/ui/address/__screenshots__/AddressTxs.pw.tsx_default_base-view-screen-xl-base-view-1.png differ diff --git a/ui/address/__screenshots__/AddressTxs.pw.tsx_mobile_base-view-mobile-1.png b/ui/address/__screenshots__/AddressTxs.pw.tsx_mobile_base-view-mobile-1.png deleted file mode 100644 index c4f406ff15..0000000000 Binary files a/ui/address/__screenshots__/AddressTxs.pw.tsx_mobile_base-view-mobile-1.png and /dev/null differ diff --git a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_dark-color-mode_average-report-dark-mode-mobile-2.png b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_dark-color-mode_average-report-dark-mode-mobile-2.png index b2350c3805..73f6e12049 100644 Binary files a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_dark-color-mode_average-report-dark-mode-mobile-2.png and b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_dark-color-mode_average-report-dark-mode-mobile-2.png differ diff --git a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_average-report-dark-mode-mobile-2.png b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_average-report-dark-mode-mobile-2.png index bb83eb74a5..c34672a8de 100644 Binary files a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_average-report-dark-mode-mobile-2.png and b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_average-report-dark-mode-mobile-2.png differ diff --git a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_great-report-2.png b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_great-report-2.png index e6da9182a9..02781e3826 100644 Binary files a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_great-report-2.png and b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_great-report-2.png differ diff --git a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_low-report-2.png b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_low-report-2.png index 92ac51c72b..a5fbc9d71f 100644 Binary files a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_low-report-2.png and b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_low-report-2.png differ diff --git a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_mobile_average-report-dark-mode-mobile-2.png b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_mobile_average-report-dark-mode-mobile-2.png index 6380efd335..b79fd34987 100644 Binary files a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_mobile_average-report-dark-mode-mobile-2.png and b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_mobile_average-report-dark-mode-mobile-2.png differ diff --git a/ui/address/accountHistory/AddressAccountHistoryListItem.tsx b/ui/address/accountHistory/AddressAccountHistoryListItem.tsx index 55dc187e5a..a4073c2506 100644 --- a/ui/address/accountHistory/AddressAccountHistoryListItem.tsx +++ b/ui/address/accountHistory/AddressAccountHistoryListItem.tsx @@ -3,9 +3,9 @@ import React, { useMemo } from 'react'; import type { NovesResponseData } from 'types/api/noves'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Link } from 'toolkit/chakra/link'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import IconSvg from 'ui/shared/IconSvg'; -import LinkInternal from 'ui/shared/links/LinkInternal'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; import NovesFromTo from 'ui/shared/Noves/NovesFromTo'; import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip'; @@ -26,7 +26,7 @@ const AddressAccountHistoryListItem = (props: Props) => { return ( - + { - - + { parsedDescription } - + diff --git a/ui/address/accountHistory/AddressAccountHistoryTableItem.tsx b/ui/address/accountHistory/AddressAccountHistoryTableItem.tsx index 23bae973c9..bc583c3728 100644 --- a/ui/address/accountHistory/AddressAccountHistoryTableItem.tsx +++ b/ui/address/accountHistory/AddressAccountHistoryTableItem.tsx @@ -1,11 +1,12 @@ -import { Td, Tr, Box } from '@chakra-ui/react'; +import { Box } from '@chakra-ui/react'; import React, { useMemo } from 'react'; import type { NovesResponseData } from 'types/api/noves'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Link } from 'toolkit/chakra/link'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import { TableCell, TableRow } from 'toolkit/chakra/table'; import IconSvg from 'ui/shared/IconSvg'; -import LinkInternal from 'ui/shared/links/LinkInternal'; import NovesFromTo from 'ui/shared/Noves/NovesFromTo'; import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip'; @@ -24,18 +25,18 @@ const AddressAccountHistoryTableItem = (props: Props) => { }, [ props.tx.classificationData.description ]); return ( - - + + - - - + + + { _dark={{ color: 'gray.400' }} /> - { parsedDescription } - + - - + + - - + + ); }; diff --git a/ui/address/blocksValidated/AddressBlocksValidatedListItem.tsx b/ui/address/blocksValidated/AddressBlocksValidatedListItem.tsx index c1cb7bd75e..bded972a24 100644 --- a/ui/address/blocksValidated/AddressBlocksValidatedListItem.tsx +++ b/ui/address/blocksValidated/AddressBlocksValidatedListItem.tsx @@ -7,8 +7,8 @@ import type { Block } from 'types/api/block'; import config from 'configs/app'; import getBlockTotalReward from 'lib/block/getBlockTotalReward'; import { currencyUnits } from 'lib/units'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import BlockGasUsed from 'ui/shared/block/BlockGasUsed'; -import Skeleton from 'ui/shared/chakra/Skeleton'; import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip'; @@ -22,7 +22,7 @@ const AddressBlocksValidatedListItem = (props: Props) => { const totalReward = getBlockTotalReward(props); return ( - + { timestamp={ props.timestamp } enableIncrement={ props.page === 1 } isLoading={ props.isLoading } - color="text_secondary" + color="text.secondary" display="inline-block" /> - Txn - + Txn + { props.transaction_count } - Gas used - - { BigNumber(props.gas_used || 0).toFormat() } + Gas used + + { BigNumber(props.gas_used || 0).toFormat() } { !config.UI.views.block.hiddenFields?.total_reward && !config.features.rollup.isEnabled && ( - Reward { currencyUnits.ether } - - { totalReward.toFixed() } + Reward { currencyUnits.ether } + + { totalReward.toFixed() } ) } diff --git a/ui/address/blocksValidated/AddressBlocksValidatedTableItem.tsx b/ui/address/blocksValidated/AddressBlocksValidatedTableItem.tsx index 6427902394..fc2a5c45b5 100644 --- a/ui/address/blocksValidated/AddressBlocksValidatedTableItem.tsx +++ b/ui/address/blocksValidated/AddressBlocksValidatedTableItem.tsx @@ -1,4 +1,4 @@ -import { Td, Tr, Flex } from '@chakra-ui/react'; +import { Flex } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; import React from 'react'; @@ -6,8 +6,9 @@ import type { Block } from 'types/api/block'; import config from 'configs/app'; import getBlockTotalReward from 'lib/block/getBlockTotalReward'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import { TableCell, TableRow } from 'toolkit/chakra/table'; import BlockGasUsed from 'ui/shared/block/BlockGasUsed'; -import Skeleton from 'ui/shared/chakra/Skeleton'; import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip'; @@ -20,51 +21,50 @@ const AddressBlocksValidatedTableItem = (props: Props) => { const totalReward = getBlockTotalReward(props); return ( - - + + - - + + - - - + + + { props.transaction_count } - - + + - + { BigNumber(props.gas_used || 0).toFormat() } - + { !config.UI.views.block.hiddenFields?.total_reward && !config.features.rollup.isEnabled && ( - - + + { totalReward.toFixed() } - + ) } - + ); }; diff --git a/ui/address/coinBalance/AddressCoinBalanceChart.tsx b/ui/address/coinBalance/AddressCoinBalanceChart.tsx index 94542df368..c51b410891 100644 --- a/ui/address/coinBalance/AddressCoinBalanceChart.tsx +++ b/ui/address/coinBalance/AddressCoinBalanceChart.tsx @@ -34,7 +34,7 @@ const AddressCoinBalanceChart = ({ addressHash }: Props) => { isLoading={ isPending } h="300px" units={ currencyUnits.ether } - emptyText={ data?.days && `Insufficient data for the past ${ data.days } days` } + emptyText={ data?.days ? `Insufficient data for the past ${ data.days } days` : undefined } /> ); }; diff --git a/ui/address/coinBalance/AddressCoinBalanceHistory.tsx b/ui/address/coinBalance/AddressCoinBalanceHistory.tsx index f084e92654..8e179f7cd0 100644 --- a/ui/address/coinBalance/AddressCoinBalanceHistory.tsx +++ b/ui/address/coinBalance/AddressCoinBalanceHistory.tsx @@ -1,4 +1,4 @@ -import { Hide, Show, Table, Tbody, Th, Tr } from '@chakra-ui/react'; +import { Box } from '@chakra-ui/react'; import type { UseQueryResult } from '@tanstack/react-query'; import React from 'react'; @@ -7,10 +7,10 @@ import type { PaginationParams } from 'ui/shared/pagination/types'; import type { ResourceError } from 'lib/api/resources'; import { currencyUnits } from 'lib/units'; +import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table'; import ActionBar, { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; import DataListDisplay from 'ui/shared/DataListDisplay'; import Pagination from 'ui/shared/pagination/Pagination'; -import { default as Thead } from 'ui/shared/TheadSticky'; import AddressCoinBalanceListItem from './AddressCoinBalanceListItem'; import AddressCoinBalanceTableItem from './AddressCoinBalanceTableItem'; @@ -25,18 +25,18 @@ const AddressCoinBalanceHistory = ({ query }: Props) => { const content = query.data?.items ? ( <> - - - - - - - - - - - - + + + + + Block + Txn + Age + Balance { currencyUnits.ether } + Delta + + + { query.data.items.map((item, index) => ( { isLoading={ query.isPlaceholderData } /> )) } - -
BlockTxnAgeBalance { currencyUnits.ether }Delta
-
- + + +
+ { query.data.items.map((item, index) => ( { isLoading={ query.isPlaceholderData } /> )) } - + ) : null; @@ -71,11 +71,12 @@ const AddressCoinBalanceHistory = ({ query }: Props) => { + > + { content } + ); }; diff --git a/ui/address/coinBalance/AddressCoinBalanceListItem.tsx b/ui/address/coinBalance/AddressCoinBalanceListItem.tsx index f138215be4..9d7b552a67 100644 --- a/ui/address/coinBalance/AddressCoinBalanceListItem.tsx +++ b/ui/address/coinBalance/AddressCoinBalanceListItem.tsx @@ -1,4 +1,4 @@ -import { Text, Stat, StatHelpText, StatArrow, Flex } from '@chakra-ui/react'; +import { Stat, Flex } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; import React from 'react'; @@ -6,7 +6,7 @@ import type { AddressCoinBalanceHistoryItem } from 'types/api/address'; import { WEI, ZERO } from 'lib/consts'; import { currencyUnits } from 'lib/units'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; @@ -22,24 +22,22 @@ const AddressCoinBalanceListItem = (props: Props) => { const isPositiveDelta = deltaBn.gte(ZERO); return ( - + - + { BigNumber(props.value).div(WEI).dp(8).toFormat() } { currencyUnits.ether } - - - - - - { deltaBn.dp(8).toFormat() } - - - + + + + { deltaBn.dp(8).toFormat() } + + { isPositiveDelta ? : } + - Block + Block { { props.transaction_hash && ( - Txs + Txs { ) } - Age + Age diff --git a/ui/address/coinBalance/AddressCoinBalanceTableItem.tsx b/ui/address/coinBalance/AddressCoinBalanceTableItem.tsx index a12ea53b7a..17ddbd7551 100644 --- a/ui/address/coinBalance/AddressCoinBalanceTableItem.tsx +++ b/ui/address/coinBalance/AddressCoinBalanceTableItem.tsx @@ -1,11 +1,12 @@ -import { Td, Tr, Text, Stat, StatHelpText, StatArrow } from '@chakra-ui/react'; +import { Stat } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; import React from 'react'; import type { AddressCoinBalanceHistoryItem } from 'types/api/address'; import { WEI, ZERO } from 'lib/consts'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import { TableCell, TableRow } from 'toolkit/chakra/table'; import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip'; @@ -20,18 +21,16 @@ const AddressCoinBalanceTableItem = (props: Props) => { const isPositiveDelta = deltaBn.gte(ZERO); return ( - - + + - - + + { props.transaction_hash && ( { maxW="150px" /> ) } - - + + - - - + + + { BigNumber(props.value).div(WEI).dp(8).toFormat() } - - - - - - - - { deltaBn.dp(8).toFormat() } - - - + + + + + + { deltaBn.dp(8).toFormat() } + + { isPositiveDelta ? : } + - - + + ); }; diff --git a/ui/address/contract/ContractCodeIdes.tsx b/ui/address/contract/ContractCodeIdes.tsx index 0c8ca5cbf9..2d49b22cc5 100644 --- a/ui/address/contract/ContractCodeIdes.tsx +++ b/ui/address/contract/ContractCodeIdes.tsx @@ -1,30 +1,24 @@ -import { - Flex, - Button, - chakra, - PopoverTrigger, - PopoverBody, - PopoverContent, - Image, - useDisclosure, - useColorModeValue, -} from '@chakra-ui/react'; +import { Flex, chakra } from '@chakra-ui/react'; import React from 'react'; import config from 'configs/app'; -import Popover from 'ui/shared/chakra/Popover'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Button } from 'toolkit/chakra/button'; +import { useColorModeValue } from 'toolkit/chakra/color-mode'; +import { Image } from 'toolkit/chakra/image'; +import { Link } from 'toolkit/chakra/link'; +import { PopoverRoot, PopoverTrigger, PopoverContent, PopoverBody } from 'toolkit/chakra/popover'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import { useDisclosure } from 'toolkit/hooks/useDisclosure'; import IconSvg from 'ui/shared/IconSvg'; -import LinkExternal from 'ui/shared/links/LinkExternal'; interface Props { className?: string; hash: string; - isLoading?: string; + isLoading?: boolean; } const ContractCodeIde = ({ className, hash, isLoading }: Props) => { - const { isOpen, onToggle, onClose } = useDisclosure(); + const { open, onOpenChange } = useDisclosure(); const defaultIconColor = useColorModeValue('gray.600', 'gray.500'); const ideLinks = React.useMemo(() => { @@ -36,16 +30,16 @@ const ContractCodeIde = ({ className, hash, isLoading }: Props) => { ; return ( - + { icon } { ide.title } - + ); }); }, [ defaultIconColor, hash ]); if (isLoading) { - return ; + return ; } if (ideLinks.length === 0) { @@ -53,28 +47,25 @@ const ContractCodeIde = ({ className, hash, isLoading }: Props) => { } return ( - + - Redactors + Redactors { - + ); }; diff --git a/ui/address/contract/ContractDetails.pw.tsx b/ui/address/contract/ContractDetails.pw.tsx index 1313f33ee3..b4ddb7fa3a 100644 --- a/ui/address/contract/ContractDetails.pw.tsx +++ b/ui/address/contract/ContractDetails.pw.tsx @@ -40,7 +40,8 @@ test.describe('full view', () => { }, }; const component = await render(, { hooksConfig }, { withSocket: true }); - await createSocket(); + const socket = await createSocket(); + await socketServer.joinChannel(socket, `addresses:${ addressMock.contract.hash.toLowerCase() }`); await expect(component).toHaveScreenshot(); }); @@ -51,7 +52,8 @@ test.describe('full view', () => { }, }; const component = await render(, { hooksConfig }, { withSocket: true }); - await createSocket(); + const socket = await createSocket(); + await socketServer.joinChannel(socket, `addresses:${ addressMock.contract.hash.toLowerCase() }`); await expect(component).toHaveScreenshot(); }); @@ -62,7 +64,8 @@ test.describe('full view', () => { }, }; const component = await render(, { hooksConfig }, { withSocket: true }); - await createSocket(); + const socket = await createSocket(); + await socketServer.joinChannel(socket, `addresses:${ addressMock.contract.hash.toLowerCase() }`); await expect(component).toHaveScreenshot(); }); @@ -73,7 +76,8 @@ test.describe('full view', () => { }, }; const component = await render(, { hooksConfig }, { withSocket: true }); - await createSocket(); + const socket = await createSocket(); + await socketServer.joinChannel(socket, `addresses:${ addressMock.contract.hash.toLowerCase() }`); await expect(component).toHaveScreenshot(); }); }); @@ -85,7 +89,8 @@ test.describe('mobile view', () => { await mockApiResponse('contract', contractMock.withChangedByteCode, { pathParams: { hash: addressMock.contract.hash } }); await mockApiResponse('contract', contractMock.withChangedByteCode, { pathParams: { hash: addressMock.contract.implementations?.[0].address as string } }); const component = await render(, { hooksConfig }, { withSocket: true }); - await createSocket(); + const socket = await createSocket(); + await socketServer.joinChannel(socket, `addresses:${ addressMock.contract.hash.toLowerCase() }`); await expect(component).toHaveScreenshot(); }); }); @@ -95,7 +100,7 @@ test('verified via lookup in eth_bytecode_db', async({ render, mockApiResponse, await render(, { hooksConfig }, { withSocket: true }); const socket = await createSocket(); - const channel = await socketServer.joinChannel(socket, 'addresses:' + addressMock.contract.hash.toLowerCase()); + const channel = await socketServer.joinChannel(socket, `addresses:${ addressMock.contract.hash.toLowerCase() }`); await page.waitForResponse(contractApiUrl); socketServer.sendMessage(socket, channel, 'smart_contract_was_verified', {}); const request = await page.waitForRequest(addressApiUrl); @@ -103,9 +108,11 @@ test('verified via lookup in eth_bytecode_db', async({ render, mockApiResponse, expect(request).toBeTruthy(); }); -test('verified with multiple sources', async({ render, page, mockApiResponse }) => { +test('verified with multiple sources', async({ render, page, mockApiResponse, createSocket }) => { await mockApiResponse('contract', contractMock.withMultiplePaths, { pathParams: { hash: addressMock.contract.hash } }); await render(, { hooksConfig }, { withSocket: true }); + const socket = await createSocket(); + await socketServer.joinChannel(socket, `addresses:${ addressMock.contract.hash.toLowerCase() }`); const section = page.locator('section', { hasText: 'Contract source code' }); await expect(section).toHaveScreenshot(); @@ -117,7 +124,7 @@ test('verified with multiple sources', async({ render, page, mockApiResponse }) await expect(section).toHaveScreenshot(); }); -test('self destructed', async({ render, mockApiResponse, page }) => { +test('self destructed', async({ render, mockApiResponse, page, createSocket }) => { const hooksConfig = { router: { query: { hash: addressMock.contract.hash, tab: 'contract_bytecode' }, @@ -125,15 +132,19 @@ test('self destructed', async({ render, mockApiResponse, page }) => { }; await mockApiResponse('contract', contractMock.selfDestructed, { pathParams: { hash: addressMock.contract.hash } }); await render(, { hooksConfig }, { withSocket: true }); + const socket = await createSocket(); + await socketServer.joinChannel(socket, `addresses:${ addressMock.contract.hash.toLowerCase() }`); const section = page.locator('section', { hasText: 'Contract creation code' }); await expect(section).toHaveScreenshot(); }); -test('non verified', async({ render, mockApiResponse }) => { +test('non verified', async({ render, mockApiResponse, createSocket }) => { await mockApiResponse('address', { ...addressMock.contract, name: null }, { pathParams: { hash: addressMock.contract.hash } }); await mockApiResponse('contract', contractMock.nonVerified, { pathParams: { hash: addressMock.contract.hash } }); const component = await render(, { hooksConfig }, { withSocket: true }); + const socket = await createSocket(); + await socketServer.joinChannel(socket, `addresses:${ addressMock.contract.hash.toLowerCase() }`); await expect(component).toHaveScreenshot(); }); diff --git a/ui/address/contract/ContractDetails.tsx b/ui/address/contract/ContractDetails.tsx index 5a0b0147b4..8f369b3d44 100644 --- a/ui/address/contract/ContractDetails.tsx +++ b/ui/address/contract/ContractDetails.tsx @@ -15,8 +15,8 @@ import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery'; import getQueryParamString from 'lib/router/getQueryParamString'; import useSocketMessage from 'lib/socket/useSocketMessage'; import * as stubs from 'stubs/contract'; +import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs'; import DataFetchAlert from 'ui/shared/DataFetchAlert'; -import RoutedTabs from 'ui/shared/Tabs/RoutedTabs'; import ContractDetailsAlerts from './alerts/ContractDetailsAlerts'; import ContractSourceAddressSelector from './ContractSourceAddressSelector'; @@ -57,7 +57,7 @@ const ContractDetails = ({ addressHash, channel, mainContractQuery }: Props) => const contractQuery = useApiQuery('contract', { pathParams: { hash: selectedItem?.address }, queryOptions: { - enabled: Boolean(selectedItem?.address), + enabled: Boolean(selectedItem?.address && !mainContractQuery.isPlaceholderData), refetchOnMount: false, placeholderData: addressInfo?.is_verified ? stubs.CONTRACT_CODE_VERIFIED : stubs.CONTRACT_CODE_UNVERIFIED, }, @@ -115,10 +115,10 @@ const ContractDetails = ({ addressHash, channel, mainContractQuery }: Props) => ) : ( diff --git a/ui/address/contract/ContractDetailsVerificationButton.tsx b/ui/address/contract/ContractDetailsVerificationButton.tsx index 0f23d96f54..5b7e84e72c 100644 --- a/ui/address/contract/ContractDetailsVerificationButton.tsx +++ b/ui/address/contract/ContractDetailsVerificationButton.tsx @@ -1,9 +1,9 @@ -import { Button } from '@chakra-ui/react'; import React from 'react'; import { route } from 'nextjs-routes'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Button } from 'toolkit/chakra/button'; +import { Link } from 'toolkit/chakra/link'; interface Props { isLoading: boolean; @@ -12,29 +12,21 @@ interface Props { } const ContractDetailsVerificationButton = ({ isLoading, addressHash, isPartiallyVerified }: Props) => { - if (isLoading) { - return ( - - ); - } return ( - + + ); }; diff --git a/ui/address/contract/ContractExternalLibraries.tsx b/ui/address/contract/ContractExternalLibraries.tsx index 08a940c540..f5ba405ade 100644 --- a/ui/address/contract/ContractExternalLibraries.tsx +++ b/ui/address/contract/ContractExternalLibraries.tsx @@ -1,27 +1,17 @@ -import { - Alert, - Box, - Button, - Flex, - Heading, - Modal, - ModalCloseButton, - ModalContent, - PopoverBody, - PopoverContent, - PopoverTrigger, - StackDivider, - useDisclosure, - VStack, -} from '@chakra-ui/react'; +import { Box, Flex, Separator, VStack } from '@chakra-ui/react'; import React from 'react'; import type { SmartContractExternalLibrary } from 'types/api/contract'; import useIsMobile from 'lib/hooks/useIsMobile'; import { apos } from 'lib/html-entities'; -import Popover from 'ui/shared/chakra/Popover'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Alert } from 'toolkit/chakra/alert'; +import { Button } from 'toolkit/chakra/button'; +import { DialogBody, DialogContent, DialogHeader, DialogRoot } from 'toolkit/chakra/dialog'; +import { Heading } from 'toolkit/chakra/heading'; +import { PopoverRoot, PopoverBody, PopoverContent, PopoverTrigger } from 'toolkit/chakra/popover'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import { useDisclosure } from 'toolkit/hooks/useDisclosure'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import IconSvg from 'ui/shared/IconSvg'; @@ -47,11 +37,11 @@ const Item = (data: SmartContractExternalLibrary) => { }; const ContractExternalLibraries = ({ className, data, isLoading }: Props) => { - const { isOpen, onToggle, onClose } = useDisclosure(); + const { open, onToggle, onOpenChange } = useDisclosure(); const isMobile = useIsMobile(); if (isLoading) { - return ; + return ; } if (data.length === 0) { @@ -62,17 +52,17 @@ const ContractExternalLibraries = ({ className, data, isLoading }: Props) => { ); @@ -84,8 +74,8 @@ const ContractExternalLibraries = ({ className, data, isLoading }: Props) => { Check the source code at the library address (if any) if you want to be sure in case if there is any library linked } - spacing={ 2 } + separator={ } + gap={ 2 } mt={ 4 } maxH={{ lg: '50vh' }} overflowY="scroll" @@ -99,18 +89,20 @@ const ContractExternalLibraries = ({ className, data, isLoading }: Props) => { return ( <> { button } - - - - { content } - - + + + + + { content } + + + ); } return ( - + { button } @@ -119,7 +111,7 @@ const ContractExternalLibraries = ({ className, data, isLoading }: Props) => { { content } - + ); }; diff --git a/ui/address/contract/ContractSourceAddressSelector.tsx b/ui/address/contract/ContractSourceAddressSelector.tsx index 3e92d3374a..25cf1f9bfa 100644 --- a/ui/address/contract/ContractSourceAddressSelector.tsx +++ b/ui/address/contract/ContractSourceAddressSelector.tsx @@ -1,13 +1,13 @@ -import { chakra, Flex } from '@chakra-ui/react'; +import { chakra, createListCollection, Flex } from '@chakra-ui/react'; import React from 'react'; import { route } from 'nextjs-routes'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Select } from 'toolkit/chakra/select'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import CopyToClipboard from 'ui/shared/CopyToClipboard'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import LinkNewTab from 'ui/shared/links/LinkNewTab'; -import Select from 'ui/shared/select/Select'; export interface Item { address: string; @@ -25,19 +25,20 @@ interface Props { const ContractSourceAddressSelector = ({ className, selectedItem, onItemSelect, items, isLoading, label }: Props) => { - const handleItemSelect = React.useCallback((value: string) => { - const nextOption = items.find(({ address }) => address === value); + const handleItemSelect = React.useCallback(({ value }: { value: Array }) => { + const nextOption = items.find(({ address }) => address === value[0]); if (nextOption) { onItemSelect(nextOption); } }, [ items, onItemSelect ]); - const options = React.useMemo(() => { - return items.map(({ address, name }) => ({ label: name || address, value: address })); + const collection = React.useMemo(() => { + const options = items.map(({ address, name }) => ({ label: name || address, value: address })); + return createListCollection({ items: options }); }, [ items ]); if (isLoading) { - return ; + return ; } if (items.length === 0) { @@ -59,15 +60,15 @@ const ContractSourceAddressSelector = ({ className, selectedItem, onItemSelect, { label } { - ref.current = element; - }, - } : {}) } - // as we use mutable ref, we have to cast it to React.LegacyRef to trick chakra and typescript - ref={ ref as React.LegacyRef | undefined } - onChange={ handleChange } - onPaste={ handlePaste } - required={ !isOptional } - isInvalid={ Boolean(error) } - placeholder={ data.type } - autoComplete="off" - data-1p-ignore - bgColor={ inputBgColor } - paddingRight={ hasMultiplyButton ? '120px' : '40px' } - /> - - { field.value !== undefined && field.value !== '' && } - { data.type === 'address' && } - { argTypeMatchInt && !isNativeCoin && (hasTimestampButton ? ( - - ) : ( - - )) } - { hasMultiplyButton && ( - + + { argTypeMatchInt ? ( + + - ) } - + + ) : | undefined }/> } - { error && { error.message } } - + ); }; diff --git a/ui/address/contract/methods/form/ContractMethodFieldLabel.tsx b/ui/address/contract/methods/form/ContractMethodFieldLabel.tsx index 305738fe12..29f4c4adf9 100644 --- a/ui/address/contract/methods/form/ContractMethodFieldLabel.tsx +++ b/ui/address/contract/methods/form/ContractMethodFieldLabel.tsx @@ -1,4 +1,4 @@ -import { Box, useColorModeValue } from '@chakra-ui/react'; +import { Box } from '@chakra-ui/react'; import React from 'react'; import type { ContractAbiItemInput } from '../types'; @@ -12,17 +12,14 @@ interface Props { } const ContractMethodFieldLabel = ({ data, isOptional, level }: Props) => { - const color = useColorModeValue('blackAlpha.600', 'whiteAlpha.600'); - return ( 1 ? color : undefined } + color={ level > 1 ? { _light: 'blackAlpha.600', _dark: 'whiteAlpha.600' } : undefined } > { getFieldLabel(data, !isOptional) } diff --git a/ui/address/contract/methods/form/ContractMethodForm.pw.tsx b/ui/address/contract/methods/form/ContractMethodForm.pw.tsx index d385156328..30e10273f4 100644 --- a/ui/address/contract/methods/form/ContractMethodForm.pw.tsx +++ b/ui/address/contract/methods/form/ContractMethodForm.pw.tsx @@ -114,8 +114,8 @@ test('base view +@mobile +@dark-mode', async({ render }) => { await component.getByPlaceholder('uint256').last().fill('42'); await component.getByRole('button', { name: '×' }).last().click(); await component.getByPlaceholder('bytes32').last().fill('aa'); - await component.getByRole('button', { name: 'add' }).last().click(); - await component.getByRole('button', { name: 'add' }).last().click(); + await component.getByLabel('add').last().click(); + await component.getByLabel('add').last().click(); await component.getByPlaceholder('int8', { exact: true }).first().fill('1'); await component.getByPlaceholder('int8', { exact: true }).last().fill('3'); @@ -123,15 +123,15 @@ test('base view +@mobile +@dark-mode', async({ render }) => { await component.getByText('parameters').click(); await component.getByText('additionalRecipients').click(); await component.getByText('#1 AdditionalRecipient').click(); - await component.getByRole('button', { name: 'add' }).first().click(); + await component.getByLabel('add').first().click(); await component.getByPlaceholder('uint256').nth(1).fill('42'); await component.getByPlaceholder('address').nth(1).fill('0xd789a607CEac2f0E14867de4EB15b15C9FFB5859'); await component.getByText('struct FulfillmentComponent[][]').click(); - await component.getByRole('button', { name: 'add' }).nth(1).click(); + await component.getByLabel('add').nth(1).click(); await component.getByText('#1 FulfillmentComponent[]').click(); await component.getByLabel('#1 FulfillmentComponent[] (tuple[])').getByText('#1 FulfillmentComponent (tuple)').click(); - await component.getByRole('button', { name: 'add' }).nth(1).click(); + await component.getByLabel('add').nth(1).click(); await component.getByText('ParentArray (int256[2][][3])').click(); await component.getByText('#1 int256[2][] (int256[2][])').click(); diff --git a/ui/address/contract/methods/form/ContractMethodForm.tsx b/ui/address/contract/methods/form/ContractMethodForm.tsx index 9d3722ada7..c350560e96 100644 --- a/ui/address/contract/methods/form/ContractMethodForm.tsx +++ b/ui/address/contract/methods/form/ContractMethodForm.tsx @@ -1,4 +1,4 @@ -import { Box, Button, Flex, Tooltip, chakra, useDisclosure } from '@chakra-ui/react'; +import { Box, Flex, chakra } from '@chakra-ui/react'; import React from 'react'; import type { SubmitHandler } from 'react-hook-form'; import { useForm, FormProvider } from 'react-hook-form'; @@ -9,6 +9,9 @@ import type { FormSubmitHandler, FormSubmitResult, MethodCallStrategy, SmartCont import config from 'configs/app'; import { SECOND } from 'lib/consts'; import * as mixpanel from 'lib/mixpanel/index'; +import { Button } from 'toolkit/chakra/button'; +import { Tooltip } from 'toolkit/chakra/tooltip'; +import { useDisclosure } from 'toolkit/hooks/useDisclosure'; import IconSvg from 'ui/shared/IconSvg'; import { isReadMethod } from '../utils'; @@ -139,10 +142,10 @@ const ContractMethodForm = ({ data, attempt, onSubmit, onReset, isOpen }: Props) const buttonCallStrategy = methodType === 'write' ? 'write' : 'read'; return ( - + - ) } + + { secondaryButton } + { primaryButton } + { copyCallDataButton } + { result && !isLoading && ( + + ) } + { result && result.source === 'wallet_client' && ( diff --git a/ui/address/contract/methods/form/ContractMethodMultiplyButton.tsx b/ui/address/contract/methods/form/ContractMethodMultiplyButton.tsx index a8edadaf3a..ccb62a8ff9 100644 --- a/ui/address/contract/methods/form/ContractMethodMultiplyButton.tsx +++ b/ui/address/contract/methods/form/ContractMethodMultiplyButton.tsx @@ -1,20 +1,11 @@ -import { - chakra, - PopoverBody, - PopoverContent, - PopoverTrigger, - Portal, - Button, - List, - ListItem, - useDisclosure, - Input, - useColorModeValue, -} from '@chakra-ui/react'; +import { chakra, List, Input, ListItem } from '@chakra-ui/react'; import React from 'react'; import { times } from 'lib/html-entities'; -import Popover from 'ui/shared/chakra/Popover'; +import { Button } from 'toolkit/chakra/button'; +import { IconButton } from 'toolkit/chakra/icon-button'; +import { PopoverBody, PopoverContent, PopoverRoot, PopoverTrigger } from 'toolkit/chakra/popover'; +import { useDisclosure } from 'toolkit/hooks/useDisclosure'; import IconSvg from 'ui/shared/IconSvg'; interface Props { @@ -27,19 +18,17 @@ interface Props { const ContractMethodMultiplyButton = ({ onClick, isDisabled, initialValue, onChange }: Props) => { const [ selectedOption, setSelectedOption ] = React.useState(initialValue); const [ customValue, setCustomValue ] = React.useState(); - const { isOpen, onToggle, onClose } = useDisclosure(); - - const dividerColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200'); + const { open, onOpenChange } = useDisclosure(); const handleOptionClick = React.useCallback((event: React.MouseEvent) => { const id = Number((event.currentTarget as HTMLDivElement).getAttribute('data-id')); if (!Object.is(id, NaN)) { setSelectedOption((prev) => prev === id ? undefined : id); setCustomValue(undefined); - onClose(); + onOpenChange({ open: false }); onChange(id); } - }, [ onClose, onChange ]); + }, [ onOpenChange, onChange ]); const handleInputChange = React.useCallback((event: React.ChangeEvent) => { const value = Number(event.target.value); @@ -59,90 +48,84 @@ const ContractMethodMultiplyButton = ({ onClick, isDisabled, initialValue, onCha { Boolean(value) && ( ) } - + - + - - - - - { [ 8, 12, 16, 18, 20 ].map((id) => ( - - 10*{ id } - { selectedOption === id && } - - )) } - + + + { [ 8, 12, 16, 18, 20 ].map((id) => ( + - 10* - - - - - - - + 10*{ id } + { selectedOption === id && } + + )) } + + 10* + + + + + + ); }; diff --git a/ui/address/contract/methods/form/ContractMethodResultPublicClient.tsx b/ui/address/contract/methods/form/ContractMethodResultPublicClient.tsx index d63d452805..c11ccbef7e 100644 --- a/ui/address/contract/methods/form/ContractMethodResultPublicClient.tsx +++ b/ui/address/contract/methods/form/ContractMethodResultPublicClient.tsx @@ -1,9 +1,11 @@ -import { Alert, Flex, useColorModeValue } from '@chakra-ui/react'; +import { Flex } from '@chakra-ui/react'; import React from 'react'; import type { AbiFunction } from 'viem'; import type { FormSubmitResultPublicClient, ResultViewMode } from '../types'; +import { Alert } from 'toolkit/chakra/alert'; + import ResultItem from './resultPublicClient/Item'; export interface Props { @@ -14,8 +16,6 @@ export interface Props { } const ContractMethodResultPublicClient = ({ data, abiItem, onSettle, mode: modeProps }: Props) => { - const bgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50'); - React.useEffect(() => { if (modeProps === 'result') { onSettle(); @@ -32,7 +32,7 @@ const ContractMethodResultPublicClient = ({ data, abiItem, onSettle, mode: modeP return ( <> { isError && ( - + { 'shortMessage' in data && typeof data.shortMessage === 'string' ? data.shortMessage : data.message } ) } @@ -42,7 +42,7 @@ const ContractMethodResultPublicClient = ({ data, abiItem, onSettle, mode: modeP mt={ 3 } p={ 4 } borderRadius="md" - bgColor={ bgColor } + bgColor={{ _light: 'blackAlpha.50', _dark: 'whiteAlpha.50' }} color={ mode === 'preview' ? 'gray.500' : undefined } fontSize="sm" lineHeight="20px" diff --git a/ui/address/contract/methods/form/ContractMethodResultWalletClient.tsx b/ui/address/contract/methods/form/ContractMethodResultWalletClient.tsx index eb4009c77b..6fdf1276db 100644 --- a/ui/address/contract/methods/form/ContractMethodResultWalletClient.tsx +++ b/ui/address/contract/methods/form/ContractMethodResultWalletClient.tsx @@ -1,4 +1,4 @@ -import { chakra, Spinner, Box, Alert } from '@chakra-ui/react'; +import { chakra, Spinner, Box } from '@chakra-ui/react'; import React from 'react'; import type { UseWaitForTransactionReceiptReturnType } from 'wagmi'; import { useWaitForTransactionReceipt } from 'wagmi'; @@ -7,7 +7,8 @@ import type { FormSubmitResultWalletClient } from '../types'; import { route } from 'nextjs-routes'; -import LinkInternal from 'ui/shared/links/LinkInternal'; +import { Alert } from 'toolkit/chakra/alert'; +import { Link } from 'toolkit/chakra/link'; interface Props { data: FormSubmitResultWalletClient['data']; @@ -45,13 +46,13 @@ export const ContractMethodResultWalletClientDumb = ({ data, onSettle, txInfo }: const isErrorResult = 'message' in data; const txLink = txHash ? ( - View transaction details + View transaction details ) : null; const content = (() => { if (isErrorResult) { return ( - + { data.message } ); @@ -81,7 +82,7 @@ export const ContractMethodResultWalletClientDumb = ({ data, onSettle, txInfo }: case 'error': { return ( - + Error: { txInfo.error ? txInfo.error.message : 'Something went wrong' } { txLink } ); @@ -91,7 +92,7 @@ export const ContractMethodResultWalletClientDumb = ({ data, onSettle, txInfo }: return ( { if (abiParameter.type === 'address' && typeof data === 'string') { return ( <> - { data } - + { data } + ); } @@ -54,7 +54,7 @@ const ItemPrimitive = ({ abiParameter, data, level, hideLabel }: Props) => { if (intMatch && typeof data === 'bigint' && intMatch.max > INT_TOOLTIP_THRESHOLD && data > INT_TOOLTIP_THRESHOLD) { const dividedValue = BigNumber(data.toString()).div(WEI); return ( - + { castValueToString(data) } ); diff --git a/ui/address/contract/methods/form/resultPublicClient/ItemTuple.tsx b/ui/address/contract/methods/form/resultPublicClient/ItemTuple.tsx index ff9f4fde64..e990557639 100644 --- a/ui/address/contract/methods/form/resultPublicClient/ItemTuple.tsx +++ b/ui/address/contract/methods/form/resultPublicClient/ItemTuple.tsx @@ -16,7 +16,7 @@ interface Props { const ItemTuple = ({ abiParameter, data, mode, level }: Props) => { return ( -
+

{ printRowOffset(level) } { abiParameter.name || abiParameter.internalType } @@ -48,7 +48,7 @@ const ItemTuple = ({ abiParameter, data, mode, level }: Props) => { ); }) }

{ printRowOffset(level) }{ '}' }

-
+

); }; diff --git a/ui/address/contract/methods/useScrollToMethod.ts b/ui/address/contract/methods/useScrollToMethod.ts index 9649800e50..5034d1fe91 100644 --- a/ui/address/contract/methods/useScrollToMethod.ts +++ b/ui/address/contract/methods/useScrollToMethod.ts @@ -19,7 +19,7 @@ export const getElementName = (data: SmartContractMethod) => { return `method_${ getElementId(data) }`; }; -export default function useScrollToMethod(data: Array, onScroll: (indices: Array) => void) { +export default function useScrollToMethod(data: Array, onScroll: (indices: Array) => void) { React.useEffect(() => { const hash = window.location.hash.replace('#', ''); @@ -34,7 +34,7 @@ export default function useScrollToMethod(data: Array, onSc smooth: true, offset: -100, }); - onScroll([ index ]); + onScroll([ String(index) ]); } }, [ data, onScroll ]); } diff --git a/ui/address/contract/useContractDetailsTabs.tsx b/ui/address/contract/useContractDetailsTabs.tsx index 105f3ee21d..548fdcfb5c 100644 --- a/ui/address/contract/useContractDetailsTabs.tsx +++ b/ui/address/contract/useContractDetailsTabs.tsx @@ -1,8 +1,9 @@ -import { Alert, Flex } from '@chakra-ui/react'; +import { Flex } from '@chakra-ui/react'; import React from 'react'; import type { SmartContract } from 'types/api/contract'; +import { Alert } from 'toolkit/chakra/alert'; import CodeViewSnippet from 'ui/shared/CodeViewSnippet'; import RawDataSnippet from 'ui/shared/RawDataSnippet'; diff --git a/ui/address/details/AddressAlternativeFormat.tsx b/ui/address/details/AddressAlternativeFormat.tsx index 18da689936..2b55a5999f 100644 --- a/ui/address/details/AddressAlternativeFormat.tsx +++ b/ui/address/details/AddressAlternativeFormat.tsx @@ -3,7 +3,7 @@ import React from 'react'; import config from 'configs/app'; import { BECH_32_SEPARATOR, toBech32Address } from 'lib/address/bech32'; import { useSettingsContext } from 'lib/contexts/settings'; -import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem'; +import * as DetailedInfo from 'ui/shared/DetailedInfo/DetailedInfo'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; interface Props { @@ -24,13 +24,13 @@ const AddressAlternativeFormat = ({ isLoading, addressHash }: Props) => { return ( <> - { label } - - + + { noLink noAltHash /> - + ); }; diff --git a/ui/address/details/AddressBalance.tsx b/ui/address/details/AddressBalance.tsx index 59ca449214..e17e7374b0 100644 --- a/ui/address/details/AddressBalance.tsx +++ b/ui/address/details/AddressBalance.tsx @@ -10,7 +10,7 @@ import useSocketChannel from 'lib/socket/useSocketChannel'; import useSocketMessage from 'lib/socket/useSocketMessage'; import { currencyUnits } from 'lib/units'; import CurrencyValue from 'ui/shared/CurrencyValue'; -import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem'; +import * as DetailedInfo from 'ui/shared/DetailedInfo/DetailedInfo'; import NativeTokenIcon from 'ui/shared/NativeTokenIcon'; interface Props { @@ -67,13 +67,13 @@ const AddressBalance = ({ data, isLoading }: Props) => { return ( <> - Balance - - + + { flexWrap="wrap" isLoading={ isLoading } /> - + ); }; diff --git a/ui/address/details/AddressCounterItem.tsx b/ui/address/details/AddressCounterItem.tsx index d8ec5f35e0..465767029d 100644 --- a/ui/address/details/AddressCounterItem.tsx +++ b/ui/address/details/AddressCounterItem.tsx @@ -7,14 +7,13 @@ import type { AddressCounters } from 'types/api/address'; import { route } from 'nextjs-routes'; import type { ResourceError } from 'lib/api/resources'; -import Skeleton from 'ui/shared/chakra/Skeleton'; -import LinkInternal from 'ui/shared/links/LinkInternal'; +import { Link } from 'toolkit/chakra/link'; +import { Skeleton } from 'toolkit/chakra/skeleton'; interface Props { prop: keyof AddressCounters; query: UseQueryResult>; address: string; - onClick: () => void; isAddressQueryLoading: boolean; isDegradedData: boolean; } @@ -25,9 +24,14 @@ const PROP_TO_TAB = { validations_count: 'blocks_validated', }; -const AddressCounterItem = ({ prop, query, address, onClick, isAddressQueryLoading, isDegradedData }: Props) => { +const AddressCounterItem = ({ prop, query, address, isAddressQueryLoading, isDegradedData }: Props) => { + + const handleClick = React.useCallback(() => { + window.scrollTo({ top: 0, behavior: 'smooth' }); + }, []); + if (query.isPlaceholderData || isAddressQueryLoading) { - return ; + return ; } const data = query.data?.[prop]; @@ -51,13 +55,13 @@ const AddressCounterItem = ({ prop, query, address, onClick, isAddressQueryLoadi } return ( - { Number(data).toLocaleString() } - + ); } } diff --git a/ui/address/details/AddressFavoriteButton.tsx b/ui/address/details/AddressFavoriteButton.tsx index 097c93d671..f0ac33bda5 100644 --- a/ui/address/details/AddressFavoriteButton.tsx +++ b/ui/address/details/AddressFavoriteButton.tsx @@ -1,4 +1,4 @@ -import { chakra, Tooltip, IconButton, useDisclosure } from '@chakra-ui/react'; +import { chakra } from '@chakra-ui/react'; import { useQueryClient } from '@tanstack/react-query'; import { useRouter } from 'next/router'; import React from 'react'; @@ -7,6 +7,9 @@ import config from 'configs/app'; import { getResourceKey } from 'lib/api/useApiQuery'; import usePreventFocusAfterModalClosing from 'lib/hooks/usePreventFocusAfterModalClosing'; import * as mixpanel from 'lib/mixpanel/index'; +import { IconButton } from 'toolkit/chakra/icon-button'; +import { Tooltip } from 'toolkit/chakra/tooltip'; +import { useDisclosure } from 'toolkit/hooks/useDisclosure'; import IconSvg from 'ui/shared/IconSvg'; import AuthGuard from 'ui/snippets/auth/AuthGuard'; import WatchlistAddModal from 'ui/watchlist/AddressModal/AddressModal'; @@ -36,14 +39,6 @@ const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => { addModalProps.onClose(); }, [ addModalProps, queryClient, router.query.hash ]); - const handleAddModalClose = React.useCallback(() => { - addModalProps.onClose(); - }, [ addModalProps ]); - - const handleDeleteModalClose = React.useCallback(() => { - deleteModalProps.onClose(); - }, [ deleteModalProps ]); - const formData = React.useMemo(() => { if (typeof watchListId !== 'number') { return { address_hash: hash }; @@ -63,34 +58,30 @@ const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => { <> { ({ onClick }) => ( - + } onFocusCapture={ onFocusCapture } - /> + > + + ) } { formData.id && ( diff --git a/ui/address/details/AddressImplementations.tsx b/ui/address/details/AddressImplementations.tsx index e341625745..58d1e142e0 100644 --- a/ui/address/details/AddressImplementations.tsx +++ b/ui/address/details/AddressImplementations.tsx @@ -3,7 +3,7 @@ import React from 'react'; import type { AddressImplementation } from 'types/api/addressParams'; import type { SmartContractProxyType } from 'types/api/contract'; -import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem'; +import * as DetailedInfo from 'ui/shared/DetailedInfo/DetailedInfo'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; interface Props { @@ -23,14 +23,14 @@ const AddressImplementations = ({ data, isLoading, proxyType }: Props) => { return ( <> - { text } - - + { noIcon /> )) } - + ); }; diff --git a/ui/address/details/AddressMetadataAlert.tsx b/ui/address/details/AddressMetadataAlert.tsx index fc982b229c..87a8ea4ff0 100644 --- a/ui/address/details/AddressMetadataAlert.tsx +++ b/ui/address/details/AddressMetadataAlert.tsx @@ -1,8 +1,10 @@ -import { Alert, Flex, chakra } from '@chakra-ui/react'; +import { Flex, chakra } from '@chakra-ui/react'; import React from 'react'; import type { AddressMetadataTagFormatted } from 'types/client/addressMetadata'; +import { Alert } from 'toolkit/chakra/alert'; + interface Props { tags: Array | undefined; className?: string; @@ -25,16 +27,17 @@ const AddressMetadataAlert = ({ tags, className }: Props) => { color={ noteTag.meta?.alertTextColor } whiteSpace="pre-wrap" display="inline-block" - sx={{ + css={{ '& a': { - color: 'link', + color: 'link.primary', _hover: { - color: 'link_hovered', + color: 'link.primary.hover', }, }, }} - dangerouslySetInnerHTML={{ __html: noteTag.meta?.data ?? '' }} - /> + > +
+ )) } ); diff --git a/ui/address/details/AddressMultichainButton.tsx b/ui/address/details/AddressMultichainButton.tsx index a8dc73382a..0bf483b2b3 100644 --- a/ui/address/details/AddressMultichainButton.tsx +++ b/ui/address/details/AddressMultichainButton.tsx @@ -1,4 +1,3 @@ -import { Image, Tooltip } from '@chakra-ui/react'; import { capitalize } from 'es-toolkit'; import React from 'react'; @@ -6,8 +5,9 @@ import type { MultichainProviderConfigParsed } from 'types/client/multichainProv import { route } from 'nextjs-routes'; -import LinkExternal from 'ui/shared/links/LinkExternal'; -import LinkInternal from 'ui/shared/links/LinkInternal'; +import { Image } from 'toolkit/chakra/image'; +import { Link } from 'toolkit/chakra/link'; +import { Tooltip } from 'toolkit/chakra/tooltip'; const TEMPLATE_ADDRESS = '{address}'; @@ -28,39 +28,29 @@ const AddressMultichainButton = ({ item, addressHash, onClick, hasSingleProvider { capitalize(item.name) } ) : ( - { buttonIcon } + { buttonIcon } ); - const linkProps = { - variant: hasSingleProvider ? 'subtle' as const : undefined, - display: 'flex', - alignItems: 'center', - fontSize: 'sm', - lineHeight: 5, - fontWeight: 500, - onClick, - }; - try { const portfolioUrlString = item.urlTemplate.replace(TEMPLATE_ADDRESS, addressHash); const portfolioUrl = new URL(portfolioUrlString); portfolioUrl.searchParams.append('utm_source', 'blockscout'); portfolioUrl.searchParams.append('utm_medium', 'address'); const dappId = item.dappId; - return typeof dappId === 'string' ? ( - - { buttonContent } - - ) : ( - { buttonContent } - + ); } catch (error) {} diff --git a/ui/address/details/AddressNameInfo.tsx b/ui/address/details/AddressNameInfo.tsx index 46536c99b2..f501919585 100644 --- a/ui/address/details/AddressNameInfo.tsx +++ b/ui/address/details/AddressNameInfo.tsx @@ -2,8 +2,8 @@ import React from 'react'; import type { Address } from 'types/api/address'; -import Skeleton from 'ui/shared/chakra/Skeleton'; -import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import * as DetailedInfo from 'ui/shared/DetailedInfo/DetailedInfo'; import TokenEntity from 'ui/shared/entities/token/TokenEntity'; interface Props { @@ -15,20 +15,20 @@ const AddressNameInfo = ({ data, isLoading }: Props) => { if (data.token) { return ( <> - Token name - - + + - + ); } @@ -36,17 +36,17 @@ const AddressNameInfo = ({ data, isLoading }: Props) => { if (data.is_contract && data.name) { return ( <> - Contract name - - - + + + { data.name } - + ); } @@ -54,17 +54,17 @@ const AddressNameInfo = ({ data, isLoading }: Props) => { if (data.name) { return ( <> - Validator name - - - + + + { data.name } - + ); } diff --git a/ui/address/details/AddressNetWorth.tsx b/ui/address/details/AddressNetWorth.tsx index a00d9c38f5..b2a9d5c738 100644 --- a/ui/address/details/AddressNetWorth.tsx +++ b/ui/address/details/AddressNetWorth.tsx @@ -6,7 +6,7 @@ import type { Address } from 'types/api/address'; import config from 'configs/app'; import getCurrencyValue from 'lib/getCurrencyValue'; import * as mixpanel from 'lib/mixpanel/index'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import TextSeparator from 'ui/shared/TextSeparator'; import { getTokensTotalInfo } from '../utils/tokenUtils'; @@ -68,7 +68,7 @@ const AddressNetWorth = ({ addressData, isLoading, addressHash }: Props) => { } return ( - + { (isError || !addressData?.exchange_rate) ? 'N/A' : `${ prefix }$${ totalUsd.toFormat(2) }` } diff --git a/ui/address/details/AddressQrCode.pw.tsx b/ui/address/details/AddressQrCode.pw.tsx index 1ae1c71187..5d88a498b2 100644 --- a/ui/address/details/AddressQrCode.pw.tsx +++ b/ui/address/details/AddressQrCode.pw.tsx @@ -6,7 +6,7 @@ import { test, expect } from 'playwright/lib'; import AddressQrCode from './AddressQrCode'; test('default view +@mobile +@dark-mode', async({ render, page }) => { - await render(); + await render(); await page.getByRole('button', { name: /qr code/i }).click(); await expect(page).toHaveScreenshot(); }); diff --git a/ui/address/details/AddressQrCode.tsx b/ui/address/details/AddressQrCode.tsx index 210e731dda..7c18cd90a2 100644 --- a/ui/address/details/AddressQrCode.tsx +++ b/ui/address/details/AddressQrCode.tsx @@ -1,28 +1,17 @@ -import { - chakra, - Alert, - Modal, - ModalBody, - ModalContent, - ModalCloseButton, - ModalHeader, - ModalOverlay, - LightMode, - Box, - useDisclosure, - Tooltip, - IconButton, -} from '@chakra-ui/react'; +import { chakra, Box } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import QRCode from 'qrcode'; import React from 'react'; -import type { Address as AddressType } from 'types/api/address'; - import getPageType from 'lib/mixpanel/getPageType'; import * as mixpanel from 'lib/mixpanel/index'; import { useRollbar } from 'lib/rollbar'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Alert } from 'toolkit/chakra/alert'; +import { DialogBody, DialogContent, DialogHeader, DialogRoot } from 'toolkit/chakra/dialog'; +import { IconButton } from 'toolkit/chakra/icon-button'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import { Tooltip } from 'toolkit/chakra/tooltip'; +import { useDisclosure } from 'toolkit/hooks/useDisclosure'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import IconSvg from 'ui/shared/IconSvg'; @@ -32,12 +21,12 @@ const SVG_OPTIONS = { interface Props { className?: string; - address: AddressType; + hash: string; isLoading?: boolean; } -const AddressQrCode = ({ address, className, isLoading }: Props) => { - const { isOpen, onOpen, onClose } = useDisclosure(); +const AddressQrCode = ({ hash, className, isLoading }: Props) => { + const { open, onOpen, onOpenChange } = useDisclosure(); const router = useRouter(); const rollbar = useRollbar(); @@ -48,8 +37,8 @@ const AddressQrCode = ({ address, className, isLoading }: Props) => { const pageType = getPageType(router.pathname); React.useEffect(() => { - if (isOpen) { - QRCode.toString(address.hash, SVG_OPTIONS, (error: Error | null | undefined, svg: string) => { + if (open) { + QRCode.toString(hash, SVG_OPTIONS, (error: Error | null | undefined, svg: string) => { if (error) { setError('We were unable to generate QR code.'); rollbar?.warn('QR code generation failed'); @@ -61,58 +50,51 @@ const AddressQrCode = ({ address, className, isLoading }: Props) => { mixpanel.logEvent(mixpanel.EventTypes.QR_CODE, { 'Page type': pageType }); }); } - }, [ address.hash, isOpen, onClose, pageType, rollbar ]); + }, [ hash, open, pageType, rollbar ]); if (isLoading) { - return ; + return ; } return ( <> - + } - flexShrink={ 0 } - /> + > + + { error && ( - - - - + + + { error } - - - + + + ) } { !error && ( - - - - - Address QR code - - - - - - - - + + + Address QR code + + + + + + ) } ); diff --git a/ui/address/details/AddressSaveOnGas.tsx b/ui/address/details/AddressSaveOnGas.tsx index 3290774b69..bb9d0d03fa 100644 --- a/ui/address/details/AddressSaveOnGas.tsx +++ b/ui/address/details/AddressSaveOnGas.tsx @@ -1,11 +1,11 @@ -import { Image } from '@chakra-ui/react'; import { useQuery } from '@tanstack/react-query'; import React from 'react'; import * as v from 'valibot'; import config from 'configs/app'; -import Skeleton from 'ui/shared/chakra/Skeleton'; -import LinkExternal from 'ui/shared/links/LinkExternal'; +import { Image } from 'toolkit/chakra/image'; +import { Link } from 'toolkit/chakra/link'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import TextSeparator from 'ui/shared/TextSeparator'; const feature = config.features.saveOnGas; @@ -75,12 +75,12 @@ const AddressSaveOnGas = ({ gasUsed, address }: Props) => { return ( <> - - + + GasHawk logo - + Save { percent.toLocaleString(undefined, { maximumFractionDigits: 0 }) }% with GasHawk - + ); diff --git a/ui/address/details/__screenshots__/AddressMetadataAlert.pw.tsx_default_base-view-1.png b/ui/address/details/__screenshots__/AddressMetadataAlert.pw.tsx_default_base-view-1.png index 0152abc7b7..bf1698cb81 100644 Binary files a/ui/address/details/__screenshots__/AddressMetadataAlert.pw.tsx_default_base-view-1.png and b/ui/address/details/__screenshots__/AddressMetadataAlert.pw.tsx_default_base-view-1.png differ diff --git a/ui/address/details/__screenshots__/AddressNetWorth.pw.tsx_dark-color-mode_with-single-multichain-button-internal-dark-mode-1.png b/ui/address/details/__screenshots__/AddressNetWorth.pw.tsx_dark-color-mode_with-single-multichain-button-internal-dark-mode-1.png index 0de6e82e33..61b690c74c 100644 Binary files a/ui/address/details/__screenshots__/AddressNetWorth.pw.tsx_dark-color-mode_with-single-multichain-button-internal-dark-mode-1.png and b/ui/address/details/__screenshots__/AddressNetWorth.pw.tsx_dark-color-mode_with-single-multichain-button-internal-dark-mode-1.png differ diff --git a/ui/address/details/__screenshots__/AddressNetWorth.pw.tsx_default_with-multichain-button-internal-small-screen-1.png b/ui/address/details/__screenshots__/AddressNetWorth.pw.tsx_default_with-multichain-button-internal-small-screen-1.png index d408289004..24624a6c6e 100644 Binary files a/ui/address/details/__screenshots__/AddressNetWorth.pw.tsx_default_with-multichain-button-internal-small-screen-1.png and b/ui/address/details/__screenshots__/AddressNetWorth.pw.tsx_default_with-multichain-button-internal-small-screen-1.png differ diff --git a/ui/address/details/__screenshots__/AddressNetWorth.pw.tsx_default_with-single-multichain-button-external-1.png b/ui/address/details/__screenshots__/AddressNetWorth.pw.tsx_default_with-single-multichain-button-external-1.png index 211f9b39fd..ef913e4423 100644 Binary files a/ui/address/details/__screenshots__/AddressNetWorth.pw.tsx_default_with-single-multichain-button-external-1.png and b/ui/address/details/__screenshots__/AddressNetWorth.pw.tsx_default_with-single-multichain-button-external-1.png differ diff --git a/ui/address/details/__screenshots__/AddressNetWorth.pw.tsx_default_with-single-multichain-button-internal-dark-mode-1.png b/ui/address/details/__screenshots__/AddressNetWorth.pw.tsx_default_with-single-multichain-button-internal-dark-mode-1.png index fef96dbe82..4c8595e555 100644 Binary files a/ui/address/details/__screenshots__/AddressNetWorth.pw.tsx_default_with-single-multichain-button-internal-dark-mode-1.png and b/ui/address/details/__screenshots__/AddressNetWorth.pw.tsx_default_with-single-multichain-button-internal-dark-mode-1.png differ diff --git a/ui/address/details/__screenshots__/AddressNetWorth.pw.tsx_default_with-two-multichain-button-external-1.png b/ui/address/details/__screenshots__/AddressNetWorth.pw.tsx_default_with-two-multichain-button-external-1.png index 665fdc0204..3f9ca78d1e 100644 Binary files a/ui/address/details/__screenshots__/AddressNetWorth.pw.tsx_default_with-two-multichain-button-external-1.png and b/ui/address/details/__screenshots__/AddressNetWorth.pw.tsx_default_with-two-multichain-button-external-1.png differ diff --git a/ui/address/details/__screenshots__/AddressQrCode.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png b/ui/address/details/__screenshots__/AddressQrCode.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png index e04068ef63..1172727e8d 100644 Binary files a/ui/address/details/__screenshots__/AddressQrCode.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png and b/ui/address/details/__screenshots__/AddressQrCode.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png differ diff --git a/ui/address/details/__screenshots__/AddressQrCode.pw.tsx_default_default-view-mobile-dark-mode-1.png b/ui/address/details/__screenshots__/AddressQrCode.pw.tsx_default_default-view-mobile-dark-mode-1.png index 63dd0a0c6a..41fd38a539 100644 Binary files a/ui/address/details/__screenshots__/AddressQrCode.pw.tsx_default_default-view-mobile-dark-mode-1.png and b/ui/address/details/__screenshots__/AddressQrCode.pw.tsx_default_default-view-mobile-dark-mode-1.png differ diff --git a/ui/address/details/__screenshots__/AddressQrCode.pw.tsx_mobile_default-view-mobile-dark-mode-1.png b/ui/address/details/__screenshots__/AddressQrCode.pw.tsx_mobile_default-view-mobile-dark-mode-1.png index 709ddca5c6..6c37e885d2 100644 Binary files a/ui/address/details/__screenshots__/AddressQrCode.pw.tsx_mobile_default-view-mobile-dark-mode-1.png and b/ui/address/details/__screenshots__/AddressQrCode.pw.tsx_mobile_default-view-mobile-dark-mode-1.png differ diff --git a/ui/address/ensDomains/AddressEnsDomains.pw.tsx b/ui/address/ensDomains/AddressEnsDomains.pw.tsx index 81ff0f41d3..342073a84c 100644 --- a/ui/address/ensDomains/AddressEnsDomains.pw.tsx +++ b/ui/address/ensDomains/AddressEnsDomains.pw.tsx @@ -34,6 +34,6 @@ test('base view', async({ render, page, mockAssetResponse }) => { mainDomainName={ ensDomainMock.ensDomainA.name } />, ); - await component.getByText('4').click(); + await component.getByLabel('Address domains').click(); await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 550, height: 350 } }); }); diff --git a/ui/address/ensDomains/AddressEnsDomains.tsx b/ui/address/ensDomains/AddressEnsDomains.tsx index 0f488cfa84..e8ac35c8c5 100644 --- a/ui/address/ensDomains/AddressEnsDomains.tsx +++ b/ui/address/ensDomains/AddressEnsDomains.tsx @@ -1,16 +1,4 @@ -import { - Box, - Button, - Flex, - Grid, - Hide, - PopoverBody, - PopoverContent, - PopoverTrigger, - Show, - useDisclosure, - chakra, -} from '@chakra-ui/react'; +import { Box, Flex, Grid, chakra } from '@chakra-ui/react'; import type { UseQueryResult } from '@tanstack/react-query'; import { clamp } from 'es-toolkit'; import React from 'react'; @@ -21,12 +9,13 @@ import { route } from 'nextjs-routes'; import type { ResourceError } from 'lib/api/resources'; import dayjs from 'lib/date/dayjs'; -import Popover from 'ui/shared/chakra/Popover'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Button } from 'toolkit/chakra/button'; +import { Link } from 'toolkit/chakra/link'; +import { PopoverBody, PopoverContent, PopoverRoot, PopoverTrigger } from 'toolkit/chakra/popover'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import { Tooltip } from 'toolkit/chakra/tooltip'; import EnsEntity from 'ui/shared/entities/ens/EnsEntity'; import IconSvg from 'ui/shared/IconSvg'; -import LinkInternal from 'ui/shared/links/LinkInternal'; -import PopoverTriggerTooltip from 'ui/shared/PopoverTriggerTooltip'; interface Props { query: UseQueryResult>; @@ -48,8 +37,6 @@ const DomainsGrid = ({ data }: { data: Array }) => { }; const AddressEnsDomains = ({ query, addressHash, mainDomainName }: Props) => { - const { isOpen, onToggle, onClose } = useDisclosure(); - const { data, isPending, isError } = query; if (isError) { @@ -57,7 +44,7 @@ const AddressEnsDomains = ({ query, addressHash, mainDomainName }: Props) => { } if (isPending) { - return ; + return ; } if (data.items.length === 0) { @@ -95,62 +82,56 @@ const AddressEnsDomains = ({ query, addressHash, mainDomainName }: Props) => { const totalRecords = data.items.length > 40 ? '40+' : data.items.length; return ( - - - - - - - - + + +
+ + + +
+
+ + { mainDomain && ( - Primary* - + Primary* + { mainDomain.expiry_date && - (expires { dayjs(mainDomain.expiry_date).fromNow() }) } + (expires { dayjs(mainDomain.expiry_date).fromNow() }) } ) } { ownedDomains.length > 0 && (
- Owned by this address + Owned by this address
) } { resolvedDomains.length > 0 && (
- Resolved to this address + Resolved to this address
) } { (ownedDomains.length > 9 || resolvedDomains.length > 9) && ( - More results - ({ totalRecords }) - + ({ totalRecords }) + ) } { mainDomain && ( @@ -159,7 +140,7 @@ const AddressEnsDomains = ({ query, addressHash, mainDomainName }: Props) => { ) }
-
+ ); }; diff --git a/ui/address/ensDomains/__screenshots__/AddressEnsDomains.pw.tsx_default_base-view-1.png b/ui/address/ensDomains/__screenshots__/AddressEnsDomains.pw.tsx_default_base-view-1.png index e1e238e65d..aa89e5ab69 100644 Binary files a/ui/address/ensDomains/__screenshots__/AddressEnsDomains.pw.tsx_default_base-view-1.png and b/ui/address/ensDomains/__screenshots__/AddressEnsDomains.pw.tsx_default_base-view-1.png differ diff --git a/ui/address/epochRewards/AddressEpochRewardsListItem.tsx b/ui/address/epochRewards/AddressEpochRewardsListItem.tsx index bf09fd8714..2f02ea262c 100644 --- a/ui/address/epochRewards/AddressEpochRewardsListItem.tsx +++ b/ui/address/epochRewards/AddressEpochRewardsListItem.tsx @@ -3,7 +3,7 @@ import React from 'react'; import type { AddressEpochRewardsItem } from 'types/api/address'; import getCurrencyValue from 'lib/getCurrencyValue'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity'; @@ -32,7 +32,7 @@ const AddressEpochRewardsListItem = ({ item, isLoading }: Props) => { Epoch # - + { item.epoch_number } @@ -42,7 +42,7 @@ const AddressEpochRewardsListItem = ({ item, isLoading }: Props) => { @@ -62,7 +62,7 @@ const AddressEpochRewardsListItem = ({ item, isLoading }: Props) => { Value - + { valueStr } diff --git a/ui/address/epochRewards/AddressEpochRewardsTable.tsx b/ui/address/epochRewards/AddressEpochRewardsTable.tsx index 5032eaf02b..30f06a903e 100644 --- a/ui/address/epochRewards/AddressEpochRewardsTable.tsx +++ b/ui/address/epochRewards/AddressEpochRewardsTable.tsx @@ -1,9 +1,8 @@ -import { Table, Tbody, Th, Tr } from '@chakra-ui/react'; import React from 'react'; import type { AddressEpochRewardsItem } from 'types/api/address'; -import { default as Thead } from 'ui/shared/TheadSticky'; +import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table'; import AddressEpochRewardsTableItem from './AddressEpochRewardsTableItem'; @@ -15,16 +14,16 @@ import AddressEpochRewardsTableItem from './AddressEpochRewardsTableItem'; const AddressEpochRewardsTable = ({ items, isLoading, top }: Props) => { return ( - - - - - - - - - - + + + + Block + Reward type + Associated address + Value + + + { items.map((item, index) => { return ( { /> ); }) } - -
BlockReward typeAssociated addressValue
+ + ); }; diff --git a/ui/address/epochRewards/AddressEpochRewardsTableItem.tsx b/ui/address/epochRewards/AddressEpochRewardsTableItem.tsx index 4f1985b40c..13e2482aac 100644 --- a/ui/address/epochRewards/AddressEpochRewardsTableItem.tsx +++ b/ui/address/epochRewards/AddressEpochRewardsTableItem.tsx @@ -1,10 +1,11 @@ -import { Flex, Td, Tr, Text } from '@chakra-ui/react'; +import { Flex, Text } from '@chakra-ui/react'; import React from 'react'; import type { AddressEpochRewardsItem } from 'types/api/address'; import getCurrencyValue from 'lib/getCurrencyValue'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import { TableCell, TableRow } from 'toolkit/chakra/table'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity'; @@ -19,29 +20,29 @@ import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip'; const AddressEpochRewardsTableItem = ({ item, isLoading }: Props) => { const { valueStr } = getCurrencyValue({ value: item.amount, decimals: item.token.decimals }); return ( - - + + - - { `Epoch # ${ item.epoch_number }` } + + { `Epoch # ${ item.epoch_number }` } - + - - + + - - + + - - - + + + { valueStr } - - + + ); }; diff --git a/ui/address/filecoin/FilecoinActorTag.tsx b/ui/address/filecoin/FilecoinActorTag.tsx index 92e52f500d..3e94ae07ca 100644 --- a/ui/address/filecoin/FilecoinActorTag.tsx +++ b/ui/address/filecoin/FilecoinActorTag.tsx @@ -1,8 +1,9 @@ -import { Tag } from '@chakra-ui/react'; import React from 'react'; import type { FilecoinActorType } from 'types/api/addressParams'; +import { Badge } from 'toolkit/chakra/badge'; + const ACTOR_TYPES: Record = { account: 'Account', cron: 'Scheduled Tasks', @@ -33,7 +34,7 @@ const FilecoinActorTag = ({ actorType }: Props) => { return null; } - return { text }; + return { text }; }; export default FilecoinActorTag; diff --git a/ui/address/mud/AddressMudBreadcrumbs.tsx b/ui/address/mud/AddressMudBreadcrumbs.tsx index 1504618ece..8d13294287 100644 --- a/ui/address/mud/AddressMudBreadcrumbs.tsx +++ b/ui/address/mud/AddressMudBreadcrumbs.tsx @@ -1,47 +1,42 @@ -import { Box, useColorModeValue, chakra, Grid } from '@chakra-ui/react'; +import { Box, chakra, Grid } from '@chakra-ui/react'; import React from 'react'; import { route } from 'nextjs-routes'; import useIsMobile from 'lib/hooks/useIsMobile'; import isBrowser from 'lib/isBrowser'; +import { Link } from 'toolkit/chakra/link'; import CopyToClipboard from 'ui/shared/CopyToClipboard'; import IconSvg from 'ui/shared/IconSvg'; -import LinkInternal from 'ui/shared/links/LinkInternal'; import useAddressQuery from '../utils/useAddressQuery'; type TableViewProps = { - scrollRef?: React.RefObject; className?: string; hash: string; tableId: string; tableName: string; + recordId?: never; + recordName?: never; }; -type RecordViewProps = TableViewProps & { +type RecordViewProps = Omit & { recordId: string; recordName: string; }; type BreadcrumbItemProps = { - scrollRef?: React.RefObject; text: string; href: string; isLast?: boolean; }; -const BreadcrumbItem = ({ text, href, isLast, scrollRef }: BreadcrumbItemProps) => { - const iconColor = useColorModeValue('gray.300', 'gray.600'); - +const BreadcrumbItem = ({ text, href, isLast }: BreadcrumbItemProps) => { const currentUrl = isBrowser() ? window.location.href : ''; const onLinkClick = React.useCallback(() => { - window.setTimeout(() => { - // cannot do scroll instantly, have to wait a little - scrollRef?.current?.scrollIntoView({ behavior: 'smooth' }); - }, 500); - }, [ scrollRef ]); + window.scrollTo({ top: 0, behavior: 'smooth' }); + }, []); if (isLast) { return ( @@ -53,14 +48,14 @@ const BreadcrumbItem = ({ text, href, isLast, scrollRef }: BreadcrumbItemProps) > { text } - + ); } return ( - { text } - - { !isLast && } + + { !isLast && } ); }; @@ -91,24 +86,21 @@ const AddressMudBreadcrumbs = (props: TableViewProps | RecordViewProps) => { width="fit-content" fontSize="sm" > - + - { ('recordId' in props) && ( + { ('recordId' in props && typeof props.recordId === 'string') && ('recordName' in props && typeof props.recordName === 'string') && ( ) } diff --git a/ui/address/mud/AddressMudRecord.tsx b/ui/address/mud/AddressMudRecord.tsx index 981bd5adff..594a85db27 100644 --- a/ui/address/mud/AddressMudRecord.tsx +++ b/ui/address/mud/AddressMudRecord.tsx @@ -1,10 +1,11 @@ -import { Box, Td, Tr, Flex, Text, Table, Show, Hide, Divider, VStack } from '@chakra-ui/react'; +import { Box, Flex, Separator, Text, VStack } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import React from 'react'; import useApiQuery from 'lib/api/useApiQuery'; import dayjs from 'lib/date/dayjs'; import getQueryParamString from 'lib/router/getQueryParamString'; +import { TableRoot, TableRow, TableCell } from 'toolkit/chakra/table'; import ContentLoader from 'ui/shared/ContentLoader'; import DataFetchAlert from 'ui/shared/DataFetchAlert'; import TruncatedValue from 'ui/shared/TruncatedValue'; @@ -14,13 +15,12 @@ import AddressMudRecordValues from './AddressMudRecordValues'; import { getValueString } from './utils'; type Props = { - scrollRef?: React.RefObject; isQueryEnabled?: boolean; tableId: string; recordId: string; }; -const AddressMudRecord = ({ tableId, recordId, isQueryEnabled = true, scrollRef }: Props) => { +const AddressMudRecord = ({ tableId, recordId, isQueryEnabled = true }: Props) => { const router = useRouter(); const hash = getQueryParamString(router.query.hash); @@ -50,44 +50,43 @@ const AddressMudRecord = ({ tableId, recordId, isQueryEnabled = true, scrollRef recordId={ recordId } recordName={ data.record.id } mb={ 6 } - scrollRef={ scrollRef } /> ) } - - + + { data?.schema.key_names.length && data?.schema.key_names.map((keyName, index) => ( - - - - + + )) } -
+ + { keyName } ({ data.schema.key_types[index] }) - + + - { index === 0 && { dayjs(data.record.timestamp).format('lll') } } + { index === 0 && { dayjs(data.record.timestamp).format('lll') } } -
-
- + + + <> { data?.schema.key_names.length && data?.schema.key_names.map((keyName, index) => ( - + { keyName } ({ data.schema.key_types[index] }) { getValueString(data.record.decoded[keyName]) } - { index === 0 && { dayjs(data.record.timestamp).format('lll') } } + { index === 0 && { dayjs(data.record.timestamp).format('lll') } } )) } - + -
+ -
+ ); }; diff --git a/ui/address/mud/AddressMudRecordValues.tsx b/ui/address/mud/AddressMudRecordValues.tsx index eea3c9aa13..b342358696 100644 --- a/ui/address/mud/AddressMudRecordValues.tsx +++ b/ui/address/mud/AddressMudRecordValues.tsx @@ -1,8 +1,10 @@ -import { Box, Td, Tr, useColorModeValue } from '@chakra-ui/react'; +import { Box } from '@chakra-ui/react'; import React from 'react'; import type { AddressMudRecord } from 'types/api/address'; +import { TableCell, TableRow } from 'toolkit/chakra/table'; + import { getValueString } from './utils'; type Props = { @@ -10,7 +12,7 @@ type Props = { }; const AddressMudRecordValues = ({ data }: Props) => { - const valuesBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50'); + const valuesBgColor = { _light: 'blackAlpha.50', _dark: 'whiteAlpha.50' }; if (!data?.schema.value_names.length) { return null; @@ -18,22 +20,22 @@ const AddressMudRecordValues = ({ data }: Props) => { return ( <> - - Field - Type - Value - + + Field + Type + Value + { data?.schema.value_names.map((valName, index) => ( - - { valName } - { data.schema.value_types[index] } - + + { valName } + { data.schema.value_types[index] } + { getValueString(data.record.decoded[valName]) } - - + + )) } diff --git a/ui/address/mud/AddressMudRecordsKeyFilter.tsx b/ui/address/mud/AddressMudRecordsKeyFilter.tsx index 5466d9efe0..7567d75573 100644 --- a/ui/address/mud/AddressMudRecordsKeyFilter.tsx +++ b/ui/address/mud/AddressMudRecordsKeyFilter.tsx @@ -16,7 +16,7 @@ const AddressMudRecordsKeyFilter = ({ value = '', handleFilterChange, columnName return ( diff --git a/ui/address/mud/AddressMudRecordsKeyFilterContent.tsx b/ui/address/mud/AddressMudRecordsKeyFilterContent.tsx index 7bac574400..e589a75ba3 100644 --- a/ui/address/mud/AddressMudRecordsKeyFilterContent.tsx +++ b/ui/address/mud/AddressMudRecordsKeyFilterContent.tsx @@ -8,10 +8,9 @@ type Props = { handleFilterChange: (val: string) => void; title: string; columnName: string; - onClose?: () => void; }; -const AddressMudRecordsKeyFilter = ({ value = '', handleFilterChange, columnName, title, onClose }: Props) => { +const AddressMudRecordsKeyFilterContent = ({ value = '', handleFilterChange, columnName, title }: Props) => { const [ filterValue, setFilterValue ] = React.useState(value); const onFilter = React.useCallback(() => { @@ -23,12 +22,11 @@ const AddressMudRecordsKeyFilter = ({ value = '', handleFilterChange, columnName title={ title } isFilled={ Boolean(filterValue) } onFilter={ onFilter } - onClose={ onClose } isTouched={ filterValue !== value } > @@ -36,4 +34,4 @@ const AddressMudRecordsKeyFilter = ({ value = '', handleFilterChange, columnName ); }; -export default AddressMudRecordsKeyFilter; +export default AddressMudRecordsKeyFilterContent; diff --git a/ui/address/mud/AddressMudRecordsTable.tsx b/ui/address/mud/AddressMudRecordsTable.tsx index a9ccc174cc..c2d5134464 100644 --- a/ui/address/mud/AddressMudRecordsTable.tsx +++ b/ui/address/mud/AddressMudRecordsTable.tsx @@ -1,5 +1,4 @@ -import type { StyleProps } from '@chakra-ui/react'; -import { Box, Link, Table, Tbody, Td, Th, Tr, Flex, useColorModeValue, useBoolean, Tooltip } from '@chakra-ui/react'; +import { Box, Flex } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import React from 'react'; @@ -10,10 +9,13 @@ import { route } from 'nextjs-routes'; import capitalizeFirstLetter from 'lib/capitalizeFirstLetter'; import dayjs from 'lib/date/dayjs'; import useIsMobile from 'lib/hooks/useIsMobile'; +import { middot } from 'lib/html-entities'; +import { Link } from 'toolkit/chakra/link'; +import { TableBody, TableCell, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table'; +import type { TableColumnHeaderProps } from 'toolkit/chakra/table'; +import { Tooltip } from 'toolkit/chakra/tooltip'; import CopyToClipboard from 'ui/shared/CopyToClipboard'; import IconSvg from 'ui/shared/IconSvg'; -import LinkInternal from 'ui/shared/links/LinkInternal'; -import { default as Thead } from 'ui/shared/TheadSticky'; import AddressMudRecordsKeyFilter from './AddressMudRecordsKeyFilter'; import { getNameTypeText, getValueString } from './utils'; @@ -49,8 +51,8 @@ const AddressMudRecordsTable = ({ const totalColsCut = data.schema.key_names.length + data.schema.value_names.length; const isMobile = useIsMobile(false); const [ colsCutCount, setColsCutCount ] = React.useState(isMobile ? MIN_CUT_COUNT : 0); - const [ isOpened, setIsOpened ] = useBoolean(false); - const [ hasCut, setHasCut ] = useBoolean(isMobile ? totalColsCut > MIN_CUT_COUNT : true); + const [ isOpened, setIsOpened ] = React.useState(false); + const [ hasCut, setHasCut ] = React.useState(isMobile ? totalColsCut > MIN_CUT_COUNT : true); const containerRef = React.useRef(null); const tableRef = React.useRef(null); @@ -59,7 +61,7 @@ const AddressMudRecordsTable = ({ const toggleIsOpen = React.useCallback(() => { isOpened && tableRef.current?.scroll({ left: 0 }); - setIsOpened.toggle(); + setIsOpened((prev) => !prev); toggleTableHasHorizontalScroll(); }, [ setIsOpened, toggleTableHasHorizontalScroll, isOpened ]); @@ -95,15 +97,13 @@ const AddressMudRecordsTable = ({ [ toggleSorting ], ); - const keyBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50'); - React.useEffect(() => { if (hasCut && !colsCutCount && containerRef.current) { const count = Math.floor((containerRef.current.getBoundingClientRect().width - CUT_COL_WIDTH) / COL_MIN_WIDTH); if (totalColsCut > MIN_CUT_COUNT && count - 1 < totalColsCut) { setColsCutCount(count - 1); } else { - setHasCut.off(); + setHasCut(false); } } }, [ colsCutCount, data.schema, hasCut, setHasCut, totalColsCut ]); @@ -114,7 +114,7 @@ const AddressMudRecordsTable = ({ const values = (isOpened || !hasCut) ? data.schema.value_names : data.schema.value_names.slice(0, colsCutCount - data.schema.key_names.length); const colsCount = (isOpened || !hasCut) ? totalColsCut : colsCutCount; - const tdStyles: StyleProps = { + const tdStyles: TableColumnHeaderProps = { wordBreak: 'break-word', whiteSpace: 'normal', minW: `${ colW }px`, @@ -130,23 +130,23 @@ const AddressMudRecordsTable = ({ } const cutButton = ( - - - ... + + + { middot }{ middot }{ middot } - + ); return ( // can't implement both horizontal table scroll and sticky header - - - + + + { keys.map((keyName, index) => { const text = getNameTypeText(keyName, data.schema.key_types[index]); return ( - + ); }) } { values.map((valName, index) => ( - + )) } { hasCut && !isOpened && cutButton } - + Modified { hasCut && isOpened && cutButton } - - - + + + { data.items.map((item) => ( - + { keys.map((keyName, index) => ( - + + )) } { values.map((valName) => - ) } - { hasCut && !isOpened && } - - { hasCut && isOpened && } - + { getValueString(item.decoded[valName]) }) } + { hasCut && !isOpened && } + { dayjs(item.timestamp).format('lll') } + { hasCut && isOpened && } + )) } - -
+ { index < 2 ? ( ) : text } - + { capitalizeFirstLetter(valName) } ({ data.schema.value_types[index] }) - Modified
+ { index === 0 ? ( - { getValueString(item.decoded[keyName]) } - + ) : getValueString(item.decoded[keyName]) } - - { getValueString(item.decoded[valName]) }{ dayjs(item.timestamp).format('lll') }
+ +
); }; diff --git a/ui/address/mud/AddressMudTable.pw.tsx b/ui/address/mud/AddressMudTable.pw.tsx index a5e5b1116c..dc1bb7703d 100644 --- a/ui/address/mud/AddressMudTable.pw.tsx +++ b/ui/address/mud/AddressMudTable.pw.tsx @@ -37,7 +37,7 @@ test('expanded view +@mobile', async({ render, mockApiResponse }) => { { hooksConfig }, ); - await component.locator('a[aria-label="show/hide columns"]').first().click(); + await component.getByLabel('show/hide columns').click(); await expect(component).toHaveScreenshot(); }); diff --git a/ui/address/mud/AddressMudTable.tsx b/ui/address/mud/AddressMudTable.tsx index 0b23c58978..112dbbad5f 100644 --- a/ui/address/mud/AddressMudTable.tsx +++ b/ui/address/mud/AddressMudTable.tsx @@ -1,12 +1,13 @@ -import { Box, HStack, Tag, TagCloseButton, chakra, useBoolean } from '@chakra-ui/react'; +import { Box, HStack, chakra } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import React from 'react'; import type { AddressMudRecordsFilter, AddressMudRecordsSorting } from 'types/api/address'; import useIsMobile from 'lib/hooks/useIsMobile'; -import { apos, nbsp } from 'lib/html-entities'; +import { apos } from 'lib/html-entities'; import getQueryParamString from 'lib/router/getQueryParamString'; +import { Tag } from 'toolkit/chakra/tag'; import ActionBar, { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; import ContentLoader from 'ui/shared/ContentLoader'; import DataListDisplay from 'ui/shared/DataListDisplay'; @@ -23,20 +24,19 @@ const BREADCRUMBS_HEIGHT = 60; const FILTERS_HEIGHT = 44; type Props = { - scrollRef?: React.RefObject; isQueryEnabled?: boolean; tableId: string; }; type FilterKeys = keyof AddressMudRecordsFilter; -const AddressMudTable = ({ scrollRef, tableId, isQueryEnabled = true }: Props) => { +const AddressMudTable = ({ tableId, isQueryEnabled = true }: Props) => { const router = useRouter(); const [ sorting, setSorting ] = React.useState(getSortParamsFromQuery(router.query, SORT_SEQUENCE)); const [ filters, setFilters ] = React.useState({}); const isMobile = useIsMobile(); - const [ tableHasHorizontalScroll, setTableHasHorizontalScroll ] = useBoolean(isMobile); + const [ tableHasHorizontalScroll, setTableHasHorizontalScroll ] = React.useState(isMobile); const hash = getQueryParamString(router.query.hash); @@ -45,13 +45,16 @@ const AddressMudTable = ({ scrollRef, tableId, isQueryEnabled = true }: Props) = pathParams: { hash, table_id: tableId }, filters, sorting, - scrollRef, options: { // no placeholder data because the structure of a table is unpredictable enabled: isQueryEnabled, }, }); + const handleTableHasHorizontalScroll = React.useCallback(() => { + setTableHasHorizontalScroll((prev) => !prev); + }, []); + const toggleSorting = React.useCallback((val: AddressMudRecordsSorting['sort']) => { const newSorting = { sort: val, order: getNextOrderValue(sorting?.sort === val ? sorting.order : undefined) }; setSorting(newSorting); @@ -83,15 +86,15 @@ const AddressMudTable = ({ scrollRef, tableId, isQueryEnabled = true }: Props) = { Object.entries(filters).map(([ key, value ]) => { const index = key as FilterKeys === 'filter_key0' ? 0 : 1; return ( - - { - getNameTypeText(data?.schema.key_names[index] || '', data?.schema.key_types[index] || '') } - - - { nbsp } - { value } - - + + { value } ); }) } @@ -103,7 +106,6 @@ const AddressMudTable = ({ scrollRef, tableId, isQueryEnabled = true }: Props) = hash={ hash } tableId={ tableId } tableName={ data?.table.table_full_name } - scrollRef={ scrollRef } mb={ hasActiveFilters ? 4 : 0 } /> ) : null; @@ -126,8 +128,7 @@ const AddressMudTable = ({ scrollRef, tableId, isQueryEnabled = true }: Props) = toggleSorting={ toggleSorting } setFilters={ setFilters } filters={ filters } - toggleTableHasHorizontalScroll={ setTableHasHorizontalScroll.toggle } - scrollRef={ scrollRef } + toggleTableHasHorizontalScroll={ handleTableHasHorizontalScroll } hash={ hash } /> ) : null; @@ -146,17 +147,18 @@ const AddressMudTable = ({ scrollRef, tableId, isQueryEnabled = true }: Props) = ) } + > + { content } + ); }; diff --git a/ui/address/mud/AddressMudTables.pw.tsx b/ui/address/mud/AddressMudTables.pw.tsx index b9bdbabd5f..7e1dfe13f8 100644 --- a/ui/address/mud/AddressMudTables.pw.tsx +++ b/ui/address/mud/AddressMudTables.pw.tsx @@ -26,7 +26,7 @@ test('base view +@mobile', async({ render, mockApiResponse }) => { await expect(component).toHaveScreenshot(); }); -test('with schema opened +@mobile', async({ render, mockApiResponse }) => { +test('with schema opened +@mobile', async({ render, mockApiResponse }, testInfo) => { await mockApiResponse('address_mud_tables', mudTables, { pathParams: { hash: ADDRESS_HASH }, queryParams: { q: 'o' } }); const component = await render( @@ -36,7 +36,7 @@ test('with schema opened +@mobile', async({ render, mockApiResponse }) => { { hooksConfig }, ); - await component.locator('div[aria-label="View schema"]').first().click(); + await component.getByLabel('View schema').nth(testInfo.project.name === 'mobile' ? 2 : 0).click(); await expect(component).toHaveScreenshot(); }); diff --git a/ui/address/mud/AddressMudTables.tsx b/ui/address/mud/AddressMudTables.tsx index 3d1a73aad6..d09cbfa980 100644 --- a/ui/address/mud/AddressMudTables.tsx +++ b/ui/address/mud/AddressMudTables.tsx @@ -1,4 +1,4 @@ -import { Hide, Show } from '@chakra-ui/react'; +import { Box } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import React from 'react'; @@ -18,11 +18,10 @@ import AddressMudTablesListItem from './AddressMudTablesListItem'; import AddressMudTablesTable from './AddressMudTablesTable'; type Props = { - scrollRef?: React.RefObject; isQueryEnabled?: boolean; }; -const AddressMudTables = ({ scrollRef, isQueryEnabled = true }: Props) => { +const AddressMudTables = ({ isQueryEnabled = true }: Props) => { const router = useRouter(); const hash = getQueryParamString(router.query.hash); @@ -34,7 +33,6 @@ const AddressMudTables = ({ scrollRef, isQueryEnabled = true }: Props) => { resourceName: 'address_mud_tables', pathParams: { hash }, filters: { q: debouncedSearchTerm }, - scrollRef, options: { enabled: isQueryEnabled, placeholderData: generateListStub<'address_mud_tables'>(ADDRESS_MUD_TABLE_ITEM, 3, { next_page_params: { @@ -50,11 +48,11 @@ const AddressMudTables = ({ scrollRef, isQueryEnabled = true }: Props) => { ); @@ -67,16 +65,15 @@ const AddressMudTables = ({ scrollRef, isQueryEnabled = true }: Props) => { const content = data?.items ? ( <> - + - - + + { data.items.map((item, index) => ( { hash={ hash } /> )) } - + ) : null; return ( + > + { content } + ); }; diff --git a/ui/address/mud/AddressMudTablesListItem.tsx b/ui/address/mud/AddressMudTablesListItem.tsx index 33c479ca21..3c09a36a70 100644 --- a/ui/address/mud/AddressMudTablesListItem.tsx +++ b/ui/address/mud/AddressMudTablesListItem.tsx @@ -1,4 +1,4 @@ -import { Divider, Text, useBoolean, Flex, Link, VStack, chakra, Box, Grid, GridItem } from '@chakra-ui/react'; +import { Text, Flex, VStack, chakra, Box, Grid, GridItem, Separator } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import React from 'react'; @@ -6,11 +6,11 @@ import type { AddressMudTableItem } from 'types/api/address'; import { route } from 'nextjs-routes'; -import Skeleton from 'ui/shared/chakra/Skeleton'; -import Tag from 'ui/shared/chakra/Tag'; +import { Badge } from 'toolkit/chakra/badge'; +import { Link } from 'toolkit/chakra/link'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import HashStringShorten from 'ui/shared/HashStringShorten'; import IconSvg from 'ui/shared/IconSvg'; -import LinkInternal from 'ui/shared/links/LinkInternal'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; type Props = { @@ -21,10 +21,14 @@ type Props = { }; const AddressMudTablesListItem = ({ item, isLoading, scrollRef, hash }: Props) => { - const [ isOpened, setIsOpened ] = useBoolean(false); + const [ isOpened, setIsOpened ] = React.useState(false); const router = useRouter(); + const handleIconClick = React.useCallback(() => { + setIsOpened((prev) => !prev); + }, []); + const onTableClick = React.useCallback((e: React.MouseEvent) => { if (e.metaKey || e.ctrlKey) { // Allow opening in a new tab/window with right-click or ctrl/cmd+click @@ -48,14 +52,14 @@ const AddressMudTablesListItem = ({ item, isLoading, scrollRef, hash }: Props) = return ( - + @@ -63,21 +67,21 @@ const AddressMudTablesListItem = ({ item, isLoading, scrollRef, hash }: Props) = - - + { item.table.table_full_name } - + - + { item.table.table_type } - + @@ -90,14 +94,14 @@ const AddressMudTablesListItem = ({ item, isLoading, scrollRef, hash }: Props) = Key { item.schema.key_names.map((name, index) => ( - + { item.schema.key_types[index] } { name } - + )) } ) } - + Value { item.schema.value_names.map((name, index) => ( diff --git a/ui/address/mud/AddressMudTablesTable.tsx b/ui/address/mud/AddressMudTablesTable.tsx index 9d21f23f45..321e07bc1a 100644 --- a/ui/address/mud/AddressMudTablesTable.tsx +++ b/ui/address/mud/AddressMudTablesTable.tsx @@ -1,9 +1,8 @@ -import { Table, Tbody, Th, Tr } from '@chakra-ui/react'; import React from 'react'; import type { AddressMudTables } from 'types/api/address'; -import { default as Thead } from 'ui/shared/TheadSticky'; +import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table'; import AddressMudTablesTableItem from './AddressMudTablesTableItem'; @@ -11,34 +10,32 @@ type Props = { items: AddressMudTables['items']; isLoading: boolean; top: number; - scrollRef?: React.RefObject; hash: string; }; //sorry for the naming -const AddressMudTablesTable = ({ items, isLoading, top, scrollRef, hash }: Props) => { +const AddressMudTablesTable = ({ items, isLoading, top, hash }: Props) => { return ( - - - - - - - - - - + + + + + Full name + Table ID + Type + + + { items.map((item, index) => ( )) } - -
Full nameTable IDType
+ + ); }; diff --git a/ui/address/mud/AddressMudTablesTableItem.tsx b/ui/address/mud/AddressMudTablesTableItem.tsx index 4796d5d097..1f0712a6d9 100644 --- a/ui/address/mud/AddressMudTablesTableItem.tsx +++ b/ui/address/mud/AddressMudTablesTableItem.tsx @@ -1,4 +1,4 @@ -import { Td, Tr, Text, useBoolean, Link, Table, VStack, chakra } from '@chakra-ui/react'; +import { Text, VStack, chakra } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import React from 'react'; @@ -6,23 +6,26 @@ import type { AddressMudTableItem } from 'types/api/address'; import { route } from 'nextjs-routes'; -import Skeleton from 'ui/shared/chakra/Skeleton'; -import Tag from 'ui/shared/chakra/Tag'; +import { Badge } from 'toolkit/chakra/badge'; +import { Link } from 'toolkit/chakra/link'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import { TableBody, TableCell, TableRoot, TableRow } from 'toolkit/chakra/table'; import IconSvg from 'ui/shared/IconSvg'; -import LinkInternal from 'ui/shared/links/LinkInternal'; - type Props = { item: AddressMudTableItem; isLoading: boolean; - scrollRef?: React.RefObject; hash: string; }; -const AddressMudTablesTableItem = ({ item, isLoading, scrollRef, hash }: Props) => { - const [ isOpened, setIsOpened ] = useBoolean(false); +const AddressMudTablesTableItem = ({ item, isLoading, hash }: Props) => { + const [ isOpened, setIsOpened ] = React.useState(false); const router = useRouter(); + const handleIconClick = React.useCallback(() => { + setIsOpened((prev) => !prev); + }, []); + const onTableClick = React.useCallback((e: React.MouseEvent) => { if (e.metaKey || e.ctrlKey) { // Allow opening in a new tab/window with right-click or ctrl/cmd+click @@ -39,84 +42,87 @@ const AddressMudTablesTableItem = ({ item, isLoading, scrollRef, hash }: Props) { shallow: true }, ); } - scrollRef?.current?.scrollIntoView(); - }, [ router, scrollRef, hash ]); + + window.scrollTo({ top: 0, behavior: 'smooth' }); + }, [ router, hash ]); return ( <> - - - + + + - - - - + + + { item.table.table_full_name } - + - - - + + + { item.table.table_id } - - - + + + { item.table.table_type } - - + + { isOpened && ( - - - - - { Boolean(item.schema.key_names.length) && ( - - - - - ) } - - - - -
Key + + + + + + { Boolean(item.schema.key_names.length) && ( + + Key + + + { item.schema.key_names.map((name, index) => ( + + { item.schema.key_types[index] } { name } + + )) } + + + + ) } + + Value + - { item.schema.key_names.map((name, index) => ( - - { item.schema.key_types[index] } { name } - + { item.schema.value_names.map((name, index) => ( + + { item.schema.value_types[index] } { name } + )) } -
Value - - { item.schema.value_names.map((name, index) => ( - - { item.schema.value_types[index] } { name } - - )) } - -
- - + + + + + + ) } ); diff --git a/ui/address/mud/__screenshots__/AddressMudTable.pw.tsx_default_base-view-mobile-1.png b/ui/address/mud/__screenshots__/AddressMudTable.pw.tsx_default_base-view-mobile-1.png index 957801fc71..0e91469e29 100644 Binary files a/ui/address/mud/__screenshots__/AddressMudTable.pw.tsx_default_base-view-mobile-1.png and b/ui/address/mud/__screenshots__/AddressMudTable.pw.tsx_default_base-view-mobile-1.png differ diff --git a/ui/address/mud/__screenshots__/AddressMudTable.pw.tsx_default_empty-mobile-1.png b/ui/address/mud/__screenshots__/AddressMudTable.pw.tsx_default_empty-mobile-1.png index 98d02d1309..9734f73fe5 100644 Binary files a/ui/address/mud/__screenshots__/AddressMudTable.pw.tsx_default_empty-mobile-1.png and b/ui/address/mud/__screenshots__/AddressMudTable.pw.tsx_default_empty-mobile-1.png differ diff --git a/ui/address/mud/__screenshots__/AddressMudTable.pw.tsx_default_expanded-view-mobile-1.png b/ui/address/mud/__screenshots__/AddressMudTable.pw.tsx_default_expanded-view-mobile-1.png index 2500a668a3..e610e9880f 100644 Binary files a/ui/address/mud/__screenshots__/AddressMudTable.pw.tsx_default_expanded-view-mobile-1.png and b/ui/address/mud/__screenshots__/AddressMudTable.pw.tsx_default_expanded-view-mobile-1.png differ diff --git a/ui/address/mud/__screenshots__/AddressMudTable.pw.tsx_mobile_base-view-mobile-1.png b/ui/address/mud/__screenshots__/AddressMudTable.pw.tsx_mobile_base-view-mobile-1.png index c53de56964..ac9b3ff044 100644 Binary files a/ui/address/mud/__screenshots__/AddressMudTable.pw.tsx_mobile_base-view-mobile-1.png and b/ui/address/mud/__screenshots__/AddressMudTable.pw.tsx_mobile_base-view-mobile-1.png differ diff --git a/ui/address/mud/__screenshots__/AddressMudTable.pw.tsx_mobile_expanded-view-mobile-1.png b/ui/address/mud/__screenshots__/AddressMudTable.pw.tsx_mobile_expanded-view-mobile-1.png index 1b8a8c500a..580c52c275 100644 Binary files a/ui/address/mud/__screenshots__/AddressMudTable.pw.tsx_mobile_expanded-view-mobile-1.png and b/ui/address/mud/__screenshots__/AddressMudTable.pw.tsx_mobile_expanded-view-mobile-1.png differ diff --git a/ui/address/mud/__screenshots__/AddressMudTables.pw.tsx_default_base-view-mobile-1.png b/ui/address/mud/__screenshots__/AddressMudTables.pw.tsx_default_base-view-mobile-1.png index 8bee44beb6..59837a270c 100644 Binary files a/ui/address/mud/__screenshots__/AddressMudTables.pw.tsx_default_base-view-mobile-1.png and b/ui/address/mud/__screenshots__/AddressMudTables.pw.tsx_default_base-view-mobile-1.png differ diff --git a/ui/address/mud/__screenshots__/AddressMudTables.pw.tsx_default_with-schema-opened-mobile-1.png b/ui/address/mud/__screenshots__/AddressMudTables.pw.tsx_default_with-schema-opened-mobile-1.png index 4698597b85..e54bc580a1 100644 Binary files a/ui/address/mud/__screenshots__/AddressMudTables.pw.tsx_default_with-schema-opened-mobile-1.png and b/ui/address/mud/__screenshots__/AddressMudTables.pw.tsx_default_with-schema-opened-mobile-1.png differ diff --git a/ui/address/mud/__screenshots__/AddressMudTables.pw.tsx_mobile_base-view-mobile-1.png b/ui/address/mud/__screenshots__/AddressMudTables.pw.tsx_mobile_base-view-mobile-1.png index d8c124a586..7141be7cbb 100644 Binary files a/ui/address/mud/__screenshots__/AddressMudTables.pw.tsx_mobile_base-view-mobile-1.png and b/ui/address/mud/__screenshots__/AddressMudTables.pw.tsx_mobile_base-view-mobile-1.png differ diff --git a/ui/address/mud/__screenshots__/AddressMudTables.pw.tsx_mobile_with-schema-opened-mobile-1.png b/ui/address/mud/__screenshots__/AddressMudTables.pw.tsx_mobile_with-schema-opened-mobile-1.png index 99e2ae3301..c0de9269aa 100644 Binary files a/ui/address/mud/__screenshots__/AddressMudTables.pw.tsx_mobile_with-schema-opened-mobile-1.png and b/ui/address/mud/__screenshots__/AddressMudTables.pw.tsx_mobile_with-schema-opened-mobile-1.png differ diff --git a/ui/address/tokenSelect/TokenSelect.pw.tsx b/ui/address/tokenSelect/TokenSelect.pw.tsx index e354233650..5ec31b1d73 100644 --- a/ui/address/tokenSelect/TokenSelect.pw.tsx +++ b/ui/address/tokenSelect/TokenSelect.pw.tsx @@ -75,14 +75,14 @@ test('sort', async({ render, page }) => { { hooksConfig }, ); await page.getByRole('button', { name: /select/i }).click(); - await page.locator('a[aria-label="Sort ERC-20 tokens"]').click(); + await page.locator('[aria-label="Sort ERC-20 tokens"]').click(); await page.mouse.wheel(0, -1000); await expect(page).toHaveScreenshot({ clip: CLIPPING_AREA }); await page.mouse.move(100, 200); await page.mouse.wheel(0, 1000); - await page.locator('a[aria-label="Sort ERC-1155 tokens"]').click(); + await page.locator('[aria-label="Sort ERC-1155 tokens"]').click(); await expect(page).toHaveScreenshot({ clip: CLIPPING_AREA }); }); diff --git a/ui/address/tokenSelect/TokenSelect.tsx b/ui/address/tokenSelect/TokenSelect.tsx index 576d77eda6..9692df1004 100644 --- a/ui/address/tokenSelect/TokenSelect.tsx +++ b/ui/address/tokenSelect/TokenSelect.tsx @@ -1,4 +1,4 @@ -import { Box, Flex, IconButton, Tooltip } from '@chakra-ui/react'; +import { Box, Flex } from '@chakra-ui/react'; import { useQueryClient, useIsFetching } from '@tanstack/react-query'; import { sumBy } from 'es-toolkit'; import { useRouter } from 'next/router'; @@ -6,23 +6,23 @@ import React from 'react'; import type { Address } from 'types/api/address'; +import { route } from 'nextjs-routes'; + import { getResourceKey } from 'lib/api/useApiQuery'; import useIsMobile from 'lib/hooks/useIsMobile'; import * as mixpanel from 'lib/mixpanel/index'; import getQueryParamString from 'lib/router/getQueryParamString'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { IconButton } from 'toolkit/chakra/icon-button'; +import { Link } from 'toolkit/chakra/link'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import { Tooltip } from 'toolkit/chakra/tooltip'; import IconSvg from 'ui/shared/IconSvg'; -import NextLink from 'ui/shared/NextLink'; import useFetchTokens from '../utils/useFetchTokens'; import TokenSelectDesktop from './TokenSelectDesktop'; import TokenSelectMobile from './TokenSelectMobile'; -interface Props { - onClick?: () => void; -} - -const TokenSelect = ({ onClick }: Props) => { +const TokenSelect = () => { const router = useRouter(); const isMobile = useIsMobile(); const queryClient = useQueryClient(); @@ -38,14 +38,14 @@ const TokenSelect = ({ onClick }: Props) => { const handleIconButtonClick = React.useCallback(() => { mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'Tokens show all (icon)' }); - onClick?.(); - }, [ onClick ]); + window.scrollTo({ top: 0, behavior: 'smooth' }); + }, [ ]); if (isPending) { return ( - - + + ); } @@ -61,26 +61,21 @@ const TokenSelect = ({ onClick }: Props) => { : } - - - + + - } - as="a" - onClick={ handleIconButtonClick } - /> - - + + +
); diff --git a/ui/address/tokenSelect/TokenSelectButton.tsx b/ui/address/tokenSelect/TokenSelectButton.tsx index 77398e6c63..06afc17bb2 100644 --- a/ui/address/tokenSelect/TokenSelectButton.tsx +++ b/ui/address/tokenSelect/TokenSelectButton.tsx @@ -1,11 +1,12 @@ -import { Box, Button, chakra, useColorModeValue } from '@chakra-ui/react'; +import { Box, chakra } from '@chakra-ui/react'; import React from 'react'; import type { FormattedData } from './types'; import { space } from 'lib/html-entities'; import * as mixpanel from 'lib/mixpanel/index'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Button } from 'toolkit/chakra/button'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import IconSvg from 'ui/shared/IconSvg'; import { getTokensTotalInfo } from '../utils/tokenUtils'; @@ -13,13 +14,11 @@ import { getTokensTotalInfo } from '../utils/tokenUtils'; interface Props { isOpen: boolean; isLoading: boolean; - onClick: () => void; data: FormattedData; } -const TokenSelectButton = ({ isOpen, isLoading, onClick, data }: Props, ref: React.ForwardedRef) => { +const TokenSelectButton = ({ isOpen, isLoading, data, ...rest }: Props, ref: React.ForwardedRef) => { const { usd, num, isOverflow } = getTokensTotalInfo(data); - const skeletonBgColor = useColorModeValue('white', 'black'); const prefix = isOverflow ? '>' : ''; @@ -29,27 +28,27 @@ const TokenSelectButton = ({ isOpen, isLoading, onClick, data }: Props, ref: Rea } mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'Tokens dropdown' }); - onClick(); - }, [ isLoading, isOpen, onClick ]); + }, [ isLoading, isOpen ]); return ( - + - { isLoading && !isOpen && } + { isLoading && !isOpen && ( + + ) } ); }; diff --git a/ui/address/tokenSelect/TokenSelectDesktop.tsx b/ui/address/tokenSelect/TokenSelectDesktop.tsx index c354866114..4de14b2b81 100644 --- a/ui/address/tokenSelect/TokenSelectDesktop.tsx +++ b/ui/address/tokenSelect/TokenSelectDesktop.tsx @@ -1,9 +1,9 @@ -import { PopoverTrigger, PopoverContent, PopoverBody, useDisclosure } from '@chakra-ui/react'; import React from 'react'; import type { FormattedData } from './types'; -import Popover from 'ui/shared/chakra/Popover'; +import { PopoverRoot, PopoverTrigger, PopoverContent, PopoverBody } from 'toolkit/chakra/popover'; +import { useDisclosure } from 'toolkit/hooks/useDisclosure'; import TokenSelectButton from './TokenSelectButton'; import TokenSelectMenu from './TokenSelectMenu'; @@ -15,21 +15,21 @@ interface Props { } const TokenSelectDesktop = ({ data, isLoading }: Props) => { - const { isOpen, onToggle, onClose } = useDisclosure(); + const { open, onOpenChange } = useDisclosure(); const result = useTokenSelect(data); return ( - + - + - + - + ); }; diff --git a/ui/address/tokenSelect/TokenSelectItem.tsx b/ui/address/tokenSelect/TokenSelectItem.tsx index 94d12f24a5..529b03e910 100644 --- a/ui/address/tokenSelect/TokenSelectItem.tsx +++ b/ui/address/tokenSelect/TokenSelectItem.tsx @@ -1,12 +1,12 @@ -import { chakra, Flex, useColorModeValue } from '@chakra-ui/react'; +import { chakra, Flex } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; import React from 'react'; import { route } from 'nextjs-routes'; import getCurrencyValue from 'lib/getCurrencyValue'; +import { Link } from 'toolkit/chakra/link'; import TokenEntity from 'ui/shared/entities/token/TokenEntity'; -import LinkInternal from 'ui/shared/links/LinkInternal'; import TruncatedValue from 'ui/shared/TruncatedValue'; import type { TokenEnhancedData } from '../utils/tokenUtils'; @@ -20,7 +20,7 @@ const TokenSelectItem = ({ data }: Props) => { const secondRow = (() => { switch (data.token.type) { case 'ERC-20': { - const tokenDecimals = Number(data.token.decimals) || 18; + const tokenDecimals = Number(data.token.decimals ?? 18); const text = `${ BigNumber(data.value).dividedBy(10 ** tokenDecimals).dp(8).toFormat() } ${ data.token.symbol || '' }`; return ( @@ -71,16 +71,16 @@ const TokenSelectItem = ({ data }: Props) => { const url = route({ pathname: '/token/[hash]', query: { hash: data.token.address } }); return ( - { { secondRow } - + ); }; diff --git a/ui/address/tokenSelect/TokenSelectMenu.tsx b/ui/address/tokenSelect/TokenSelectMenu.tsx index bb25a39f76..98b20c00c8 100644 --- a/ui/address/tokenSelect/TokenSelectMenu.tsx +++ b/ui/address/tokenSelect/TokenSelectMenu.tsx @@ -1,12 +1,13 @@ -import { Text, Box, Input, InputGroup, InputLeftElement, useColorModeValue, Flex, Link } from '@chakra-ui/react'; +import { Text, Box, Flex } from '@chakra-ui/react'; import { sumBy } from 'es-toolkit'; -import type { ChangeEvent } from 'react'; import React from 'react'; import type { FormattedData } from './types'; import type { TokenType } from 'types/api/token'; import { getTokenTypeName } from 'lib/token/tokenTypes'; +import { Link } from 'toolkit/chakra/link'; +import FilterInput from 'ui/shared/filters/FilterInput'; import IconSvg from 'ui/shared/IconSvg'; import type { Sort } from '../utils/tokenUtils'; @@ -19,29 +20,22 @@ interface Props { erc1155sort: Sort; erc404sort: Sort; filteredData: FormattedData; - onInputChange: (event: ChangeEvent) => void; + onInputChange: (searchTerm: string) => void; onSortClick: (event: React.SyntheticEvent) => void; } const TokenSelectMenu = ({ erc20sort, erc1155sort, erc404sort, filteredData, onInputChange, onSortClick, searchTerm }: Props) => { - const searchIconColor = useColorModeValue('blackAlpha.600', 'whiteAlpha.600'); - const hasFilteredResult = sumBy(Object.values(filteredData), ({ items }) => items.length) > 0; return ( <> - - - - - - + { Object.entries(filteredData).sort(sortTokenGroups).map(([ tokenType, tokenInfo ]) => { if (tokenInfo.items.length === 0) { diff --git a/ui/address/tokenSelect/TokenSelectMobile.tsx b/ui/address/tokenSelect/TokenSelectMobile.tsx index 3d29cd8fdf..b77c6afdae 100644 --- a/ui/address/tokenSelect/TokenSelectMobile.tsx +++ b/ui/address/tokenSelect/TokenSelectMobile.tsx @@ -1,8 +1,10 @@ -import { useDisclosure, Modal, ModalContent, ModalCloseButton } from '@chakra-ui/react'; import React from 'react'; import type { FormattedData } from './types'; +import { DialogBody, DialogContent, DialogHeader, DialogRoot, DialogTrigger } from 'toolkit/chakra/dialog'; +import { useDisclosure } from 'toolkit/hooks/useDisclosure'; + import TokenSelectButton from './TokenSelectButton'; import TokenSelectMenu from './TokenSelectMenu'; import useTokenSelect from './useTokenSelect'; @@ -13,19 +15,21 @@ interface Props { } const TokenSelectMobile = ({ data, isLoading }: Props) => { - const { isOpen, onToggle, onClose } = useDisclosure(); + const { open, onOpenChange } = useDisclosure(); const result = useTokenSelect(data); return ( - <> - - - - + + + + + + Tokens + - - - + + + ); }; diff --git a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_dark-color-mode_base-view-dark-mode-1.png b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_dark-color-mode_base-view-dark-mode-1.png index bcf909abb9..30392f5041 100644 Binary files a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_dark-color-mode_base-view-dark-mode-1.png and b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_dark-color-mode_base-view-dark-mode-1.png differ diff --git a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_dark-color-mode_base-view-dark-mode-2.png b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_dark-color-mode_base-view-dark-mode-2.png index e37cc05502..6f1ea38caf 100644 Binary files a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_dark-color-mode_base-view-dark-mode-2.png and b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_dark-color-mode_base-view-dark-mode-2.png differ diff --git a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_base-view-dark-mode-1.png b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_base-view-dark-mode-1.png index a566383356..076d22f62b 100644 Binary files a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_base-view-dark-mode-1.png and b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_base-view-dark-mode-1.png differ diff --git a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_base-view-dark-mode-2.png b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_base-view-dark-mode-2.png index d84b120d53..939d180875 100644 Binary files a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_base-view-dark-mode-2.png and b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_base-view-dark-mode-2.png differ diff --git a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_filter-1.png b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_filter-1.png index 29f3efdf59..d7fc7c4c43 100644 Binary files a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_filter-1.png and b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_filter-1.png differ diff --git a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_long-values-1.png b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_long-values-1.png index 27520e990e..a83905daf0 100644 Binary files a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_long-values-1.png and b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_long-values-1.png differ diff --git a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_mobile-base-view-1.png b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_mobile-base-view-1.png index 74a69703ff..eb424b2b18 100644 Binary files a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_mobile-base-view-1.png and b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_mobile-base-view-1.png differ diff --git a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_sort-1.png b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_sort-1.png index 76a799de19..bc8a479c2e 100644 Binary files a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_sort-1.png and b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_sort-1.png differ diff --git a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_sort-2.png b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_sort-2.png index 5f83b97aad..d73d1dff93 100644 Binary files a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_sort-2.png and b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_sort-2.png differ diff --git a/ui/address/tokenSelect/useTokenSelect.ts b/ui/address/tokenSelect/useTokenSelect.ts index 35ecc004d1..8a4a0d4b29 100644 --- a/ui/address/tokenSelect/useTokenSelect.ts +++ b/ui/address/tokenSelect/useTokenSelect.ts @@ -1,5 +1,4 @@ import { mapValues } from 'es-toolkit'; -import type { ChangeEvent } from 'react'; import React from 'react'; import type { FormattedData } from './types'; @@ -13,8 +12,8 @@ export default function useTokenSelect(data: FormattedData) { const [ erc404sort, setErc404Sort ] = React.useState('desc'); const [ erc20sort, setErc20Sort ] = React.useState('desc'); - const onInputChange = React.useCallback((event: ChangeEvent) => { - setSearchTerm(event.target.value); + const onInputChange = React.useCallback((searchTerm: string) => { + setSearchTerm(searchTerm); }, []); const onSortClick = React.useCallback((event: React.SyntheticEvent) => { diff --git a/ui/address/tokens/AddressCollections.tsx b/ui/address/tokens/AddressCollections.tsx index 3ef99fc515..c0f289dd78 100644 --- a/ui/address/tokens/AddressCollections.tsx +++ b/ui/address/tokens/AddressCollections.tsx @@ -5,11 +5,11 @@ import { route } from 'nextjs-routes'; import useIsMobile from 'lib/hooks/useIsMobile'; import { apos } from 'lib/html-entities'; +import { Link } from 'toolkit/chakra/link'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import ActionBar from 'ui/shared/ActionBar'; -import Skeleton from 'ui/shared/chakra/Skeleton'; import DataListDisplay from 'ui/shared/DataListDisplay'; import TokenEntity from 'ui/shared/entities/token/TokenEntity'; -import LinkInternal from 'ui/shared/links/LinkInternal'; import NftFallback from 'ui/shared/nft/NftFallback'; import Pagination from 'ui/shared/pagination/Pagination'; import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages'; @@ -56,12 +56,12 @@ const AddressCollections = ({ collectionsQuery, address, hasActiveFilters }: Pro noCopy fontWeight="600" /> - - { ` - ${ Number(item.amount).toLocaleString() } item${ Number(item.amount) > 1 ? 's' : '' }` } + + { ` - ${ Number(item.amount).toLocaleString() } item${ Number(item.amount) > 1 ? 's' : '' }` } - - View in collection - + + View in collection + + - - - + + + View all NFTs - + ) } @@ -102,15 +102,16 @@ const AddressCollections = ({ collectionsQuery, address, hasActiveFilters }: Pro return ( + > + { content } + ); }; diff --git a/ui/address/tokens/AddressNFTs.tsx b/ui/address/tokens/AddressNFTs.tsx index 5b41a6911a..60c5015b83 100644 --- a/ui/address/tokens/AddressNFTs.tsx +++ b/ui/address/tokens/AddressNFTs.tsx @@ -51,15 +51,16 @@ const AddressNFTs = ({ tokensQuery, hasActiveFilters }: Props) => { return ( + > + { content } + ); }; diff --git a/ui/address/tokens/ERC20Tokens.tsx b/ui/address/tokens/ERC20Tokens.tsx index dea07b99a8..670c4f05c4 100644 --- a/ui/address/tokens/ERC20Tokens.tsx +++ b/ui/address/tokens/ERC20Tokens.tsx @@ -1,4 +1,4 @@ -import { Show, Hide } from '@chakra-ui/react'; +import { Box } from '@chakra-ui/react'; import React from 'react'; import useIsMobile from 'lib/hooks/useIsMobile'; @@ -27,24 +27,27 @@ const ERC20Tokens = ({ tokensQuery }: Props) => { const content = data?.items ? ( <> - - { data.items.map((item, index) => ( + + { data.items.map((item, index) => ( - )) } + )) } + + ) : null; return ( + > + { content } + ); }; diff --git a/ui/address/tokens/ERC20TokensListItem.tsx b/ui/address/tokens/ERC20TokensListItem.tsx index 43bb92c04c..988e8890d7 100644 --- a/ui/address/tokens/ERC20TokensListItem.tsx +++ b/ui/address/tokens/ERC20TokensListItem.tsx @@ -4,8 +4,8 @@ import React from 'react'; import type { AddressTokenBalance } from 'types/api/address'; import getCurrencyValue from 'lib/getCurrencyValue'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet'; -import Skeleton from 'ui/shared/chakra/Skeleton'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; @@ -40,23 +40,23 @@ const ERC20TokensListItem = ({ token, value, isLoading }: Props) => { { token.exchange_rate !== undefined && token.exchange_rate !== null && ( - - Price - + + Price + { `$${ Number(token.exchange_rate).toLocaleString() }` } ) } - - Quantity - + + Quantity + { tokenQuantity } { tokenValue !== undefined && ( - - Value - + + Value + ${ tokenValue } diff --git a/ui/address/tokens/ERC20TokensTable.tsx b/ui/address/tokens/ERC20TokensTable.tsx index cd844faa6e..afa9cc873f 100644 --- a/ui/address/tokens/ERC20TokensTable.tsx +++ b/ui/address/tokens/ERC20TokensTable.tsx @@ -1,9 +1,8 @@ -import { Table, Tbody, Tr, Th } from '@chakra-ui/react'; import React from 'react'; import type { AddressTokenBalance } from 'types/api/address'; -import { default as Thead } from 'ui/shared/TheadSticky'; +import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table'; import ERC20TokensTableItem from './ERC20TokensTableItem'; @@ -15,22 +14,22 @@ interface Props { const ERC20TokensTable = ({ data, top, isLoading }: Props) => { return ( - - - - - - - - - - - + + + + Asset + Contract address + Price + Quantity + Value + + + { data.map((item, index) => ( )) } - -
AssetContract addressPriceQuantityValue
+ + ); }; diff --git a/ui/address/tokens/ERC20TokensTableItem.tsx b/ui/address/tokens/ERC20TokensTableItem.tsx index 84e7d1547a..2be666d982 100644 --- a/ui/address/tokens/ERC20TokensTableItem.tsx +++ b/ui/address/tokens/ERC20TokensTableItem.tsx @@ -1,11 +1,12 @@ -import { Tr, Td, Flex } from '@chakra-ui/react'; +import { Flex } from '@chakra-ui/react'; import React from 'react'; import type { AddressTokenBalance } from 'types/api/address'; import getCurrencyValue from 'lib/getCurrencyValue'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import { TableCell, TableRow } from 'toolkit/chakra/table'; import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet'; -import Skeleton from 'ui/shared/chakra/Skeleton'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity'; @@ -23,10 +24,8 @@ const ERC20TokensTableItem = ({ } = getCurrencyValue({ value: value, exchangeRate: token.exchange_rate, decimals: token.decimals, accuracy: 8, accuracyUsd: 2 }); return ( - - + + - - + + - - - + + + { token.exchange_rate && `$${ Number(token.exchange_rate).toLocaleString() }` } - - - + + + { tokenQuantity } - - - + + + { tokenValue && `$${ tokenValue }` } - - + + ); }; diff --git a/ui/address/tokens/NFTItem.tsx b/ui/address/tokens/NFTItem.tsx index ee95353a8d..75126c4a9a 100644 --- a/ui/address/tokens/NFTItem.tsx +++ b/ui/address/tokens/NFTItem.tsx @@ -1,4 +1,4 @@ -import { Tag, Flex, Text, Link, LightMode } from '@chakra-ui/react'; +import { Flex, Text } from '@chakra-ui/react'; import React from 'react'; import type { AddressNFT } from 'types/api/address'; @@ -7,7 +7,9 @@ import { route } from 'nextjs-routes'; import getCurrencyValue from 'lib/getCurrencyValue'; import { getTokenTypeName } from 'lib/token/tokenTypes'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Link } from 'toolkit/chakra/link'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import { Tag } from 'toolkit/chakra/tag'; import NftEntity from 'ui/shared/entities/nft/NftEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity'; import NftMedia from 'ui/shared/nft/NftMedia'; @@ -24,26 +26,27 @@ const NFTItem = ({ token, value, isLoading, withTokenLink, ...tokenInstance }: P return ( - - { getTokenTypeName(token.type) } + + { getTokenTypeName(token.type) } - + - ID# + ID# - + { valueResult && ( - Qty + Qty { valueResult } ) } diff --git a/ui/address/tokens/NFTItemContainer.tsx b/ui/address/tokens/NFTItemContainer.tsx index fc3d906d3e..4d43467eb8 100644 --- a/ui/address/tokens/NFTItemContainer.tsx +++ b/ui/address/tokens/NFTItemContainer.tsx @@ -1,4 +1,4 @@ -import { Box, useColorModeValue, chakra } from '@chakra-ui/react'; +import { Box, chakra } from '@chakra-ui/react'; import React from 'react'; type Props = { @@ -11,7 +11,7 @@ const NFTItemContainer = ({ children, className }: Props) => { { name="Net Worth" value={ addressData?.exchange_rate ? `${ prefix }$${ totalUsd.toFormat(2) }` : 'N/A' } isLoading={ addressQuery.isPending || tokenQuery.isPending } - icon={ } + icon={ } /> { value={ tokensNumText } valueSecondary={ `${ prefix }$${ tokensInfo.usd.toFormat(2) }` } isLoading={ addressQuery.isPending || tokenQuery.isPending } - icon={ } + icon={ } /> ); diff --git a/ui/address/tokens/TokenBalancesItem.tsx b/ui/address/tokens/TokenBalancesItem.tsx index c733f1529c..a8ef452325 100644 --- a/ui/address/tokens/TokenBalancesItem.tsx +++ b/ui/address/tokens/TokenBalancesItem.tsx @@ -1,7 +1,8 @@ -import { Box, Flex, Text, useColorModeValue } from '@chakra-ui/react'; +import { Box, Flex, Text } from '@chakra-ui/react'; import React from 'react'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Skeleton } from 'toolkit/chakra/skeleton'; + type Props = { name: string; value: string; @@ -12,16 +13,14 @@ type Props = { const TokenBalancesItem = ({ name, icon, value, valueSecondary, isLoading }: Props) => { - const bgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50'); - return ( - - { name } + + { name } { icon } - + { value } - { Boolean(valueSecondary) && ({ valueSecondary }) } + { Boolean(valueSecondary) && ({ valueSecondary }) } diff --git a/ui/addressVerification/AddressVerificationModal.tsx b/ui/addressVerification/AddressVerificationModal.tsx index b36de3fd7e..0c95880f94 100644 --- a/ui/addressVerification/AddressVerificationModal.tsx +++ b/ui/addressVerification/AddressVerificationModal.tsx @@ -1,4 +1,3 @@ -import { Modal, ModalBody, ModalCloseButton, ModalContent, ModalHeader, ModalOverlay, Link } from '@chakra-ui/react'; import React from 'react'; import type { AddressVerificationFormFirstStepFields, AddressCheckStatusSuccess } from './types'; @@ -6,7 +5,7 @@ import type { VerifiedAddress } from 'types/api/account'; import config from 'configs/app'; import * as mixpanel from 'lib/mixpanel/index'; -import IconSvg from 'ui/shared/IconSvg'; +import { DialogBody, DialogContent, DialogHeader, DialogRoot } from 'toolkit/chakra/dialog'; import Web3ModalProvider from 'ui/shared/Web3ModalProvider'; import AddressVerificationStepAddress from './steps/AddressVerificationStepAddress'; @@ -16,8 +15,8 @@ import AddressVerificationStepSuccess from './steps/AddressVerificationStepSucce type StateData = AddressVerificationFormFirstStepFields & AddressCheckStatusSuccess & { isToken?: boolean }; interface Props { - isOpen: boolean; - onClose: () => void; + open: boolean; + onOpenChange: ({ open }: { open: boolean }) => void; onSubmit: (address: VerifiedAddress) => void; onAddTokenInfoClick: (address: string) => void; onShowListClick: () => void; @@ -25,16 +24,16 @@ interface Props { pageType: string; } -const AddressVerificationModal = ({ defaultAddress, isOpen, onClose, onSubmit, onAddTokenInfoClick, onShowListClick, pageType }: Props) => { +const AddressVerificationModal = ({ defaultAddress, open, onOpenChange, onSubmit, onAddTokenInfoClick, onShowListClick, pageType }: Props) => { const [ stepIndex, setStepIndex ] = React.useState(0); const [ data, setData ] = React.useState({ address: '', signingMessage: '' }); React.useEffect(() => { - isOpen && mixpanel.logEvent( + open && mixpanel.logEvent( mixpanel.EventTypes.VERIFY_ADDRESS, { Action: 'Form opened', 'Page type': pageType }, ); - }, [ isOpen, pageType ]); + }, [ open, pageType ]); const handleGoToSecondStep = React.useCallback((firstStepResult: typeof data) => { setData(firstStepResult); @@ -59,16 +58,18 @@ const AddressVerificationModal = ({ defaultAddress, isOpen, onClose, onSubmit, o setStepIndex((prev) => prev - 1); }, []); - const handleClose = React.useCallback(() => { - onClose(); - setStepIndex(0); - setData({ address: '', signingMessage: '' }); - }, [ onClose ]); + const handleOpenChange = React.useCallback(({ open }: { open: boolean }) => { + onOpenChange({ open }); + if (!open) { + setStepIndex(0); + setData({ address: '', signingMessage: '' }); + } + }, [ onOpenChange ]); const handleAddTokenInfoClick = React.useCallback(() => { onAddTokenInfoClick(data.address); - handleClose(); - }, [ handleClose, data.address, onAddTokenInfoClick ]); + handleOpenChange({ open: false }); + }, [ handleOpenChange, data.address, onAddTokenInfoClick ]); const steps = [ { @@ -100,25 +101,26 @@ const AddressVerificationModal = ({ defaultAddress, isOpen, onClose, onSubmit, o const step = steps[stepIndex]; return ( - - - - - { stepIndex !== 0 && ( - - - - ) } - { step.title } - - - + + + + { step.title } + + { step.content } - - - + + + ); }; diff --git a/ui/addressVerification/steps/AddressVerificationStepAddress.tsx b/ui/addressVerification/steps/AddressVerificationStepAddress.tsx index 7619d69864..a46e4fabd9 100644 --- a/ui/addressVerification/steps/AddressVerificationStepAddress.tsx +++ b/ui/addressVerification/steps/AddressVerificationStepAddress.tsx @@ -1,4 +1,4 @@ -import { Alert, Box, Button, Flex } from '@chakra-ui/react'; +import { Box, Flex } from '@chakra-ui/react'; import React from 'react'; import type { SubmitHandler } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form'; @@ -16,8 +16,10 @@ import { route } from 'nextjs-routes'; import config from 'configs/app'; import type { ResourceError } from 'lib/api/resources'; import useApiFetch from 'lib/api/useApiFetch'; +import { Alert } from 'toolkit/chakra/alert'; +import { Button } from 'toolkit/chakra/button'; +import { Link } from 'toolkit/chakra/link'; import FormFieldAddress from 'ui/shared/forms/fields/FormFieldAddress'; -import LinkInternal from 'ui/shared/links/LinkInternal'; import AdminSupportText from 'ui/shared/texts/AdminSupportText'; type Fields = RootFields & AddressVerificationFormFirstStepFields; @@ -85,7 +87,7 @@ const AddressVerificationStepAddress = ({ defaultAddress, onContinue }: Props) = return ( The contract source code you entered is not yet verified. Please follow these steps to - verify the contract + verify the contract . ); @@ -106,13 +108,13 @@ const AddressVerificationStepAddress = ({ defaultAddress, onContinue }: Props) = { rootError && { rootError } } name="address" - isRequired - bgColor="dialog_bg" + required + bgColor="dialog.bg" placeholder="Smart contract address (0x...)" mt={ 8 } /> - diff --git a/ui/addressVerification/steps/AddressVerificationStepSignature.pw.tsx b/ui/addressVerification/steps/AddressVerificationStepSignature.pw.tsx index 004d9cff30..e6823b89b8 100644 --- a/ui/addressVerification/steps/AddressVerificationStepSignature.pw.tsx +++ b/ui/addressVerification/steps/AddressVerificationStepSignature.pw.tsx @@ -44,5 +44,5 @@ test('INVALID_SIGNER_ERROR view +@mobile', async({ render, page }) => { await signatureInput.fill(mocks.SIGNATURE); await page.getByRole('button', { name: /verify/i }).click(); - await expect(page).toHaveScreenshot(); + await expect(page).toHaveScreenshot({ fullPage: true }); }); diff --git a/ui/addressVerification/steps/AddressVerificationStepSignature.tsx b/ui/addressVerification/steps/AddressVerificationStepSignature.tsx index 65b8928d8d..45fd5f024e 100644 --- a/ui/addressVerification/steps/AddressVerificationStepSignature.tsx +++ b/ui/addressVerification/steps/AddressVerificationStepSignature.tsx @@ -1,4 +1,4 @@ -import { Alert, Box, Button, chakra, Flex, Link, Radio, RadioGroup } from '@chakra-ui/react'; +import { Box, chakra, Flex } from '@chakra-ui/react'; import { useAppKit } from '@reown/appkit/react'; import React from 'react'; import type { SubmitHandler } from 'react-hook-form'; @@ -18,6 +18,10 @@ import type { VerifiedAddress } from 'types/api/account'; import config from 'configs/app'; import useApiFetch from 'lib/api/useApiFetch'; import shortenString from 'lib/shortenString'; +import { Alert } from 'toolkit/chakra/alert'; +import { Button } from 'toolkit/chakra/button'; +import { Link } from 'toolkit/chakra/link'; +import { Radio, RadioGroup } from 'toolkit/chakra/radio'; import CopyToClipboard from 'ui/shared/CopyToClipboard'; import FormFieldText from 'ui/shared/forms/fields/FormFieldText'; import { SIGNATURE_REGEXP } from 'ui/shared/forms/validators/signature'; @@ -81,8 +85,12 @@ const AddressVerificationStepSignature = ({ address, signingMessage, contractCre const { signMessage, isPending: isSigning } = useSignMessage(); - const handleSignMethodChange = React.useCallback((value: typeof signMethod) => { - setSignMethod(value); + const handleSignMethodChange = React.useCallback(({ value }: { value: string | null }) => { + if (!value) { + return; + } + + setSignMethod(value as SignMethod); clearErrors('root'); }, [ clearErrors ]); @@ -119,9 +127,8 @@ const AddressVerificationStepSignature = ({ address, signingMessage, contractCre if (signMethod === 'manual') { return ( { isToken && ( - ) } diff --git a/ui/addressVerification/steps/__screenshots__/AddressVerificationStepAddress.pw.tsx_default_SOURCE-CODE-NOT-VERIFIED-ERROR-view-mobile-1.png b/ui/addressVerification/steps/__screenshots__/AddressVerificationStepAddress.pw.tsx_default_SOURCE-CODE-NOT-VERIFIED-ERROR-view-mobile-1.png index 436ebf372c..7d65ecde56 100644 Binary files a/ui/addressVerification/steps/__screenshots__/AddressVerificationStepAddress.pw.tsx_default_SOURCE-CODE-NOT-VERIFIED-ERROR-view-mobile-1.png and b/ui/addressVerification/steps/__screenshots__/AddressVerificationStepAddress.pw.tsx_default_SOURCE-CODE-NOT-VERIFIED-ERROR-view-mobile-1.png differ diff --git a/ui/addressVerification/steps/__screenshots__/AddressVerificationStepAddress.pw.tsx_default_base-view-1.png b/ui/addressVerification/steps/__screenshots__/AddressVerificationStepAddress.pw.tsx_default_base-view-1.png index baad951227..d972fe41da 100644 Binary files a/ui/addressVerification/steps/__screenshots__/AddressVerificationStepAddress.pw.tsx_default_base-view-1.png and b/ui/addressVerification/steps/__screenshots__/AddressVerificationStepAddress.pw.tsx_default_base-view-1.png differ diff --git a/ui/addressVerification/steps/__screenshots__/AddressVerificationStepAddress.pw.tsx_mobile_SOURCE-CODE-NOT-VERIFIED-ERROR-view-mobile-1.png b/ui/addressVerification/steps/__screenshots__/AddressVerificationStepAddress.pw.tsx_mobile_SOURCE-CODE-NOT-VERIFIED-ERROR-view-mobile-1.png index 33bbf8c61f..b92de7c713 100644 Binary files a/ui/addressVerification/steps/__screenshots__/AddressVerificationStepAddress.pw.tsx_mobile_SOURCE-CODE-NOT-VERIFIED-ERROR-view-mobile-1.png and b/ui/addressVerification/steps/__screenshots__/AddressVerificationStepAddress.pw.tsx_mobile_SOURCE-CODE-NOT-VERIFIED-ERROR-view-mobile-1.png differ diff --git a/ui/addressVerification/steps/__screenshots__/AddressVerificationStepSignature.pw.tsx_default_INVALID-SIGNER-ERROR-view-mobile-1.png b/ui/addressVerification/steps/__screenshots__/AddressVerificationStepSignature.pw.tsx_default_INVALID-SIGNER-ERROR-view-mobile-1.png index 6706039911..39720430a0 100644 Binary files a/ui/addressVerification/steps/__screenshots__/AddressVerificationStepSignature.pw.tsx_default_INVALID-SIGNER-ERROR-view-mobile-1.png and b/ui/addressVerification/steps/__screenshots__/AddressVerificationStepSignature.pw.tsx_default_INVALID-SIGNER-ERROR-view-mobile-1.png differ diff --git a/ui/addressVerification/steps/__screenshots__/AddressVerificationStepSignature.pw.tsx_default_base-view-1.png b/ui/addressVerification/steps/__screenshots__/AddressVerificationStepSignature.pw.tsx_default_base-view-1.png index 27083e4b03..a6f6816b59 100644 Binary files a/ui/addressVerification/steps/__screenshots__/AddressVerificationStepSignature.pw.tsx_default_base-view-1.png and b/ui/addressVerification/steps/__screenshots__/AddressVerificationStepSignature.pw.tsx_default_base-view-1.png differ diff --git a/ui/addressVerification/steps/__screenshots__/AddressVerificationStepSignature.pw.tsx_mobile_INVALID-SIGNER-ERROR-view-mobile-1.png b/ui/addressVerification/steps/__screenshots__/AddressVerificationStepSignature.pw.tsx_mobile_INVALID-SIGNER-ERROR-view-mobile-1.png index 1991f1e1fb..be55c0d5ee 100644 Binary files a/ui/addressVerification/steps/__screenshots__/AddressVerificationStepSignature.pw.tsx_mobile_INVALID-SIGNER-ERROR-view-mobile-1.png and b/ui/addressVerification/steps/__screenshots__/AddressVerificationStepSignature.pw.tsx_mobile_INVALID-SIGNER-ERROR-view-mobile-1.png differ diff --git a/ui/addresses/AddressesListItem.tsx b/ui/addresses/AddressesListItem.tsx index f9b845a70d..0eaea642cd 100644 --- a/ui/addresses/AddressesListItem.tsx +++ b/ui/addresses/AddressesListItem.tsx @@ -7,8 +7,8 @@ import type { AddressesItem } from 'types/api/addresses'; import config from 'configs/app'; import { ZERO } from 'lib/consts'; import { currencyUnits } from 'lib/units'; -import Skeleton from 'ui/shared/chakra/Skeleton'; -import Tag from 'ui/shared/chakra/Tag'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import { Tag } from 'toolkit/chakra/tag'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; @@ -38,30 +38,30 @@ const AddressesListItem = ({ mr={ 2 } truncation="constant" /> - + { index } { item.public_tags !== null && item.public_tags.length > 0 && item.public_tags.map(tag => ( - { tag.display_name } + { tag.display_name } )) } - - { `Balance ${ currencyUnits.ether }` } - + + { `Balance ${ currencyUnits.ether }` } + { addressBalance.dp(8).toFormat() } { !totalSupply.eq(ZERO) && ( - - Percentage - + + Percentage + { addressBalance.div(BigNumber(totalSupply)).multipliedBy(100).dp(8).toFormat() + '%' } ) } - - Txn count - + + Txn count + { Number(item.transaction_count).toLocaleString() } diff --git a/ui/addresses/AddressesTable.tsx b/ui/addresses/AddressesTable.tsx index be25767839..22ad7b1354 100644 --- a/ui/addresses/AddressesTable.tsx +++ b/ui/addresses/AddressesTable.tsx @@ -1,4 +1,3 @@ -import { Table, Tbody, Tr, Th } from '@chakra-ui/react'; import type BigNumber from 'bignumber.js'; import React from 'react'; @@ -6,7 +5,7 @@ import type { AddressesItem } from 'types/api/addresses'; import { ZERO } from 'lib/consts'; import { currencyUnits } from 'lib/units'; -import { default as Thead } from 'ui/shared/TheadSticky'; +import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table'; import AddressesTableItem from './AddressesTableItem'; @@ -21,17 +20,17 @@ interface Props { const AddressesTable = ({ items, totalSupply, pageStartIndex, top, isLoading }: Props) => { const hasPercentage = !totalSupply.eq(ZERO); return ( - - - - - - - { hasPercentage && } - - - - + + + + Rank + Address + { `Balance ${ currencyUnits.ether }` } + { hasPercentage && Percentage } + Txn count + + + { items.map((item, index) => ( )) } - -
RankAddress{ `Balance ${ currencyUnits.ether }` }PercentageTxn count
+ + ); }; diff --git a/ui/addresses/AddressesTableItem.tsx b/ui/addresses/AddressesTableItem.tsx index 4bad73729c..acef99bb1a 100644 --- a/ui/addresses/AddressesTableItem.tsx +++ b/ui/addresses/AddressesTableItem.tsx @@ -1,12 +1,13 @@ -import { Tr, Td, Text, Flex } from '@chakra-ui/react'; +import { Text, Flex } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; import React from 'react'; import type { AddressesItem } from 'types/api/addresses'; import config from 'configs/app'; -import Skeleton from 'ui/shared/chakra/Skeleton'; -import Tag from 'ui/shared/chakra/Tag'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import { TableCell, TableRow } from 'toolkit/chakra/table'; +import { Tag } from 'toolkit/chakra/tag'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; type Props = { @@ -29,13 +30,13 @@ const AddressesTableItem = ({ const addressBalanceChunks = addressBalance.dp(8).toFormat().split('.'); return ( - - - + + + { index } - - + + { item.public_tags && item.public_tags.length ? item.public_tags.map(tag => ( - { tag.display_name } + { tag.display_name } )) : null } - - - + + + { addressBalanceChunks[0] + (addressBalanceChunks[1] ? '.' : '') } - { addressBalanceChunks[1] } + { addressBalanceChunks[1] } - + { hasPercentage && ( - + { addressBalance.div(totalSupply).multipliedBy(100).dp(8).toFormat() + '%' } - + ) } - - + + { Number(item.transaction_count).toLocaleString() } - - + + ); }; diff --git a/ui/addressesLabelSearch/AddressesLabelSearchListItem.tsx b/ui/addressesLabelSearch/AddressesLabelSearchListItem.tsx index 988773cb2d..df7cc52ccc 100644 --- a/ui/addressesLabelSearch/AddressesLabelSearchListItem.tsx +++ b/ui/addressesLabelSearch/AddressesLabelSearchListItem.tsx @@ -6,7 +6,7 @@ import type { AddressesItem } from 'types/api/addresses'; import config from 'configs/app'; import { currencyUnits } from 'lib/units'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; @@ -30,15 +30,15 @@ const AddressesLabelSearchListItem = ({ fontWeight={ 700 } w="100%" /> - - { `Balance ${ currencyUnits.ether }` } - + + { `Balance ${ currencyUnits.ether }` } + { addressBalance.dp(8).toFormat() } - - Txn count - + + Txn count + { Number(item.transaction_count).toLocaleString() } diff --git a/ui/addressesLabelSearch/AddressesLabelSearchTable.tsx b/ui/addressesLabelSearch/AddressesLabelSearchTable.tsx index 364bee5a9d..656edbd0ef 100644 --- a/ui/addressesLabelSearch/AddressesLabelSearchTable.tsx +++ b/ui/addressesLabelSearch/AddressesLabelSearchTable.tsx @@ -1,10 +1,9 @@ -import { Table, Tbody, Tr, Th } from '@chakra-ui/react'; import React from 'react'; import type { AddressesItem } from 'types/api/addresses'; import { currencyUnits } from 'lib/units'; -import { default as Thead } from 'ui/shared/TheadSticky'; +import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table'; import AddressesLabelSearchTableItem from './AddressesLabelSearchTableItem'; @@ -16,15 +15,15 @@ interface Props { const AddressesLabelSearchTable = ({ items, top, isLoading }: Props) => { return ( - - - - - - - - - + + + + Address + { `Balance ${ currencyUnits.ether }` } + Txn count + + + { items.map((item, index) => ( { isLoading={ isLoading } /> )) } - -
Address{ `Balance ${ currencyUnits.ether }` }Txn count
+ + ); }; diff --git a/ui/addressesLabelSearch/AddressesLabelSearchTableItem.tsx b/ui/addressesLabelSearch/AddressesLabelSearchTableItem.tsx index 14a672df47..9bbbaffd86 100644 --- a/ui/addressesLabelSearch/AddressesLabelSearchTableItem.tsx +++ b/ui/addressesLabelSearch/AddressesLabelSearchTableItem.tsx @@ -1,11 +1,12 @@ -import { Tr, Td, Text } from '@chakra-ui/react'; +import { Text } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; import React from 'react'; import type { AddressesItem } from 'types/api/addresses'; import config from 'configs/app'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import { TableCell, TableRow } from 'toolkit/chakra/table'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; type Props = { @@ -22,27 +23,27 @@ const AddressesLabelSearchTableItem = ({ const addressBalanceChunks = addressBalance.dp(8).toFormat().split('.'); return ( - - + + - - - + + + { addressBalanceChunks[0] + (addressBalanceChunks[1] ? '.' : '') } - { addressBalanceChunks[1] } + { addressBalanceChunks[1] } - - - + + + { Number(item.transaction_count).toLocaleString() } - - + + ); }; diff --git a/ui/advancedFilter/ColumnFilter.tsx b/ui/advancedFilter/ColumnFilter.tsx deleted file mode 100644 index bd1d91a961..0000000000 --- a/ui/advancedFilter/ColumnFilter.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { - chakra, - Flex, - Text, - Link, - Button, -} from '@chakra-ui/react'; -import React from 'react'; - -import ColumnFilterWrapper from './ColumnFilterWrapper'; - -type Props = { - columnName: string; - title: string; - isActive?: boolean; - isFilled?: boolean; - onFilter: () => void; - onReset?: () => void; - onClose?: () => void; - isLoading?: boolean; - className?: string; - children: React.ReactNode; -}; - -type ContentProps = { - title: string; - isFilled?: boolean; - onFilter: () => void; - onReset?: () => void; - onClose?: () => void; - children: React.ReactNode; -}; - -const ColumnFilterContent = ({ title, isFilled, onFilter, onReset, onClose, children }: ContentProps) => { - const onFilterClick = React.useCallback(() => { - onClose && onClose(); - onFilter(); - }, [ onClose, onFilter ]); - return ( - <> - - { title } - - Reset - - - { children } - - - ); -}; - -const ColumnFilter = ({ columnName, isActive, className, isLoading, ...props }: Props) => { - return ( - - - - ); -}; - -export default chakra(ColumnFilter); diff --git a/ui/advancedFilter/ColumnFilterWrapper.tsx b/ui/advancedFilter/ColumnFilterWrapper.tsx deleted file mode 100644 index 184ff04def..0000000000 --- a/ui/advancedFilter/ColumnFilterWrapper.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { - PopoverTrigger, - PopoverContent, - PopoverBody, - useDisclosure, - IconButton, - chakra, -} from '@chakra-ui/react'; -import React from 'react'; - -import Popover from 'ui/shared/chakra/Popover'; -import IconSvg from 'ui/shared/IconSvg'; - -interface Props { - columnName: string; - isActive?: boolean; - isLoading?: boolean; - className?: string; - children: React.ReactNode; -} - -const ColumnFilterWrapper = ({ columnName, isActive, className, children, isLoading }: Props) => { - const { isOpen, onToggle, onClose } = useDisclosure(); - - const child = React.Children.only(children) as React.ReactElement & { - ref?: React.Ref; - }; - - const modifiedChildren = React.cloneElement( - child, - { onClose }, - ); - - return ( - - - } - isActive={ isActive } - isDisabled={ isLoading } - /> - - - - { modifiedChildren } - - - - ); -}; - -export default chakra(ColumnFilterWrapper); diff --git a/ui/advancedFilter/ColumnsButton.tsx b/ui/advancedFilter/ColumnsButton.tsx index 77659617a5..21bc5cbaae 100644 --- a/ui/advancedFilter/ColumnsButton.tsx +++ b/ui/advancedFilter/ColumnsButton.tsx @@ -1,18 +1,11 @@ -import { - Button, - Grid, - PopoverTrigger, - PopoverContent, - PopoverBody, - useDisclosure, - Checkbox, -} from '@chakra-ui/react'; +import { chakra } from '@chakra-ui/react'; import React from 'react'; -import type { ChangeEvent } from 'react'; +import { Button } from 'toolkit/chakra/button'; +import { Checkbox, CheckboxGroup } from 'toolkit/chakra/checkbox'; +import { PopoverBody, PopoverContent, PopoverRoot, PopoverTrigger } from 'toolkit/chakra/popover'; import type { ColumnsIds } from 'ui/advancedFilter/constants'; import { TABLE_COLUMNS } from 'ui/advancedFilter/constants'; -import Popover from 'ui/shared/chakra/Popover'; import IconSvg from 'ui/shared/IconSvg'; interface Props { @@ -21,46 +14,48 @@ interface Props { } const ColumnsButton = ({ columns, onChange }: Props) => { - const { isOpen, onToggle, onClose } = useDisclosure(); - - const onCheckboxClick = React.useCallback((event: ChangeEvent) => { - const newCols = { ...columns }; - const id = event.target.id as ColumnsIds; - newCols[id] = event.target.checked; + const handleValueChange = React.useCallback((value: Array) => { + const newCols = value.reduce((acc, key) => { + acc[key as ColumnsIds] = true; + return acc; + }, {} as Record); onChange(newCols); - }, [ onChange, columns ]); + }, [ onChange ]); return ( - + - + columns[key as ColumnsIds]) } + onValueChange={ handleValueChange } + display="grid" + gridTemplateColumns="160px 160px" + gap={ 3 } + > { TABLE_COLUMNS.map(col => ( { col.id === 'or_and' ? 'And/Or' : col.name } )) } - + - + ); }; diff --git a/ui/advancedFilter/ExportCSV.tsx b/ui/advancedFilter/ExportCSV.tsx index cade60a19c..56db40de63 100644 --- a/ui/advancedFilter/ExportCSV.tsx +++ b/ui/advancedFilter/ExportCSV.tsx @@ -1,4 +1,3 @@ -import { Button } from '@chakra-ui/react'; import React from 'react'; import type { AdvancedFilterParams } from 'types/api/advancedFilter'; @@ -7,7 +6,8 @@ import config from 'configs/app'; import buildUrl from 'lib/api/buildUrl'; import dayjs from 'lib/date/dayjs'; import downloadBlob from 'lib/downloadBlob'; -import useToast from 'lib/hooks/useToast'; +import { Button } from 'toolkit/chakra/button'; +import { toaster } from 'toolkit/chakra/toaster'; import ReCaptcha from 'ui/shared/reCaptcha/ReCaptcha'; import useReCaptcha from 'ui/shared/reCaptcha/useReCaptcha'; @@ -17,7 +17,6 @@ type Props = { const ExportCSV = ({ filters }: Props) => { const recaptcha = useReCaptcha(); - const toast = useToast(); const [ isLoading, setIsLoading ] = React.useState(false); const handleExportCSV = React.useCallback(async() => { @@ -49,18 +48,14 @@ const ExportCSV = ({ filters }: Props) => { downloadBlob(blob, fileName); } catch (error) { - toast({ - position: 'top-right', + toaster.error({ title: 'Error', description: (error as Error)?.message || 'Something went wrong. Try again later.', - status: 'error', - variant: 'subtle', - isClosable: true, }); } finally { setIsLoading(false); } - }, [ toast, filters, recaptcha ]); + }, [ filters, recaptcha ]); if (!config.services.reCaptchaV2.siteKey) { return null; @@ -71,7 +66,7 @@ const ExportCSV = ({ filters }: Props) => { diff --git a/ui/apiKey/ApiKeyModal/ApiKeyModal.tsx b/ui/apiKey/ApiKeyModal/ApiKeyModal.tsx index 7a3a4131b4..2e596e85b9 100644 --- a/ui/apiKey/ApiKeyModal/ApiKeyModal.tsx +++ b/ui/apiKey/ApiKeyModal/ApiKeyModal.tsx @@ -7,24 +7,24 @@ import FormModal from 'ui/shared/FormModal'; import ApiKeyForm from './ApiKeyForm'; type Props = { - isOpen: boolean; - onClose: () => void; + open: boolean; + onOpenChange: ({ open }: { open: boolean }) => void; data?: ApiKey; }; -const ApiKeyModal: React.FC = ({ isOpen, onClose, data }) => { +const ApiKeyModal: React.FC = ({ open, onOpenChange, data }) => { const title = data ? 'Edit API key' : 'New API key'; const text = !data ? 'Add an application name to identify your API key. Click the button below to auto-generate the associated key.' : ''; const [ isAlertVisible, setAlertVisible ] = useState(false); const renderForm = useCallback(() => { - return ; - }, [ data, onClose ]); + return ; + }, [ data, onOpenChange ]); return ( - isOpen={ isOpen } - onClose={ onClose } + open={ open } + onOpenChange={ onOpenChange } title={ title } text={ text } renderForm={ renderForm } diff --git a/ui/apiKey/ApiKeyTable/ApiKeyTable.tsx b/ui/apiKey/ApiKeyTable/ApiKeyTable.tsx index 2f2c4aaea4..f4f2d06ba3 100644 --- a/ui/apiKey/ApiKeyTable/ApiKeyTable.tsx +++ b/ui/apiKey/ApiKeyTable/ApiKeyTable.tsx @@ -1,14 +1,9 @@ -import { - Table, - Thead, - Tbody, - Tr, - Th, -} from '@chakra-ui/react'; import React from 'react'; import type { ApiKeys, ApiKey } from 'types/api/account'; +import { TableBody, TableColumnHeader, TableHeader, TableRoot, TableRow } from 'toolkit/chakra/table'; + import ApiKeyTableItem from './ApiKeyTableItem'; interface Props { @@ -21,14 +16,14 @@ interface Props { const ApiKeyTable = ({ data, isLoading, onDeleteClick, onEditClick, limit }: Props) => { return ( - - - - - - - - + + + + { `API key token (limit ${ limit } keys)` } + + + + { data?.map((item, index) => ( )) } - -
{ `API key token (limit ${ limit } keys)` }
+ + ); }; diff --git a/ui/apiKey/ApiKeyTable/ApiKeyTableItem.tsx b/ui/apiKey/ApiKeyTable/ApiKeyTableItem.tsx index 752ed5f7ba..39d5746338 100644 --- a/ui/apiKey/ApiKeyTable/ApiKeyTableItem.tsx +++ b/ui/apiKey/ApiKeyTable/ApiKeyTableItem.tsx @@ -1,11 +1,8 @@ -import { - Tr, - Td, -} from '@chakra-ui/react'; import React, { useCallback } from 'react'; import type { ApiKey } from 'types/api/account'; +import { TableCell, TableRow } from 'toolkit/chakra/table'; import ApiKeySnippet from 'ui/shared/ApiKeySnippet'; import TableItemActionButtons from 'ui/shared/TableItemActionButtons'; @@ -27,14 +24,14 @@ const ApiKeyTableItem = ({ item, isLoading, onEditClick, onDeleteClick }: Props) }, [ item, onDeleteClick ]); return ( - - + + - - + + - - + + ); }; diff --git a/ui/apiKey/DeleteApiKeyModal.tsx b/ui/apiKey/DeleteApiKeyModal.tsx index a84465ee11..0e7d54fcb0 100644 --- a/ui/apiKey/DeleteApiKeyModal.tsx +++ b/ui/apiKey/DeleteApiKeyModal.tsx @@ -9,12 +9,12 @@ import useApiFetch from 'lib/api/useApiFetch'; import DeleteModal from 'ui/shared/DeleteModal'; type Props = { - isOpen: boolean; - onClose: () => void; + open: boolean; + onOpenChange: ({ open }: { open: boolean }) => void; data: ApiKey; }; -const DeleteApiKeyModal: React.FC = ({ isOpen, onClose, data }) => { +const DeleteApiKeyModal: React.FC = ({ open, onOpenChange, data }) => { const queryClient = useQueryClient(); const apiFetch = useApiFetch(); @@ -39,8 +39,8 @@ const DeleteApiKeyModal: React.FC = ({ isOpen, onClose, data }) => { return ( { +test('text', async({ render, page }) => { // eslint-disable-next-line max-len const data = '0xE2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A280E2A3A4E2A1B6E2A0BFE2A0BFE2A0B7E2A3B6E2A384E2A080E2A080E2A080E2A080E2A0800AE2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A3B0E2A1BFE2A081E2A080E2A080E2A280E2A380E2A180E2A099E2A3B7E2A180E2A080E2A080E2A0800AE2A080E2A080E2A080E2A180E2A080E2A080E2A080E2A080E2A080E2A2A0E2A3BFE2A081E2A080E2A080E2A080E2A098E2A0BFE2A083E2A080E2A2B8E2A3BFE2A3BFE2A3BFE2A3BF0AE2A080E2A3A0E2A1BFE2A09BE2A2B7E2A3A6E2A180E2A080E2A080E2A088E2A3BFE2A184E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A3B8E2A3BFE2A3BFE2A3BFE2A09F0AE2A2B0E2A1BFE2A081E2A080E2A080E2A099E2A2BFE2A3A6E2A3A4E2A3A4E2A3BCE2A3BFE2A384E2A080E2A080E2A080E2A080E2A080E2A2B4E2A19FE2A09BE2A08BE2A081E2A0800AE2A3BFE2A087E2A080E2A080E2A080E2A080E2A080E2A089E2A089E2A089E2A089E2A089E2A081E2A080E2A080E2A080E2A080E2A080E2A088E2A3BFE2A180E2A080E2A080E2A0800AE2A3BFE2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A2B9E2A187E2A080E2A080E2A0800AE2A3BFE2A186E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A3BCE2A187E2A080E2A080E2A0800AE2A0B8E2A3B7E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A2A0E2A1BFE2A080E2A080E2A080E2A0800AE2A080E2A0B9E2A3B7E2A3A4E2A380E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A380E2A3B0E2A1BFE2A081E2A080E2A080E2A080E2A0800AE2A080E2A080E2A080E2A089E2A099E2A09BE2A0BFE2A0B6E2A3B6E2A3B6E2A3B6E2A3B6E2A3B6E2A0B6E2A0BFE2A09FE2A09BE2A089E2A080E2A080E2A080E2A080E2A080E2A080'; const component = await render(); await expect(component).toHaveScreenshot(); - await component.getByRole('button', { name: 'Raw' }).click(); - await component.getByText('UTF-8').click(); + await component.getByRole('combobox').click(); + await page.getByRole('option', { name: /utf/i }).click(); await expect(component).toHaveScreenshot(); }); -test('image', async({ render }) => { +test('image', async({ render, page }) => { // eslint-disable-next-line max-len const data = '0x89504E470D0A1A0A0000000D494844520000003C0000003C0403000000C8D2C4410000000467414D410000B18F0BFC6105000000017352474200AECE1CE900000027504C54454C69712B6CB02A6CB02B6CB02B6CB02B6CB02B6CB02B6CB02B6CB02B6CB02B6CB02B6CB02B6CB0F4205A540000000C74524E5300ED2F788CD91B99475C09B969CFA99D0000004F7A5458745261772070726F66696C65207479706520697074630000789CE3CA2C2849E6520003230B2E630B1323134B9314031320448034C3640323B35420CBD8D4C8C4CCC41CC407CB8048A04A2E0028950EE32A226D1F0000000970485973000084DF000084DF0195C81C33000000F24944415438CB636000018E983367CE482780D90CDA40F6991D0C4820152472A60ACCE6DA03629F4E40929E03961602B39964C09C0624691B24690E88F48461215D03160903B3D962C01C07842C2758C341A80643B0B40484C3646C6C5C78E6E016171723A8E215262EEE31670E161B1B7731304C05AB155EC08002C0D172E6F80206884DBB50651938CF4003FE0CBA4390E3C56064482F53525252C329CD562A2828283A0197340B22AAB0494332C311FCD2C747A547A58996C69998D8F12745B68DA0846C85331B2CEAE8E8681A81D91F8B348C4605D0527B02A4283FA88026CD05163EAAC0900ED21EC9800EC0C2110C002BBA9FE999B920330000000049454E44AE426082'; const component = await render(); await expect(component).toHaveScreenshot(); - await component.getByRole('button', { name: 'Image' }).click(); - await component.getByText('Base64').click(); + await component.getByRole('combobox').click(); + await page.getByRole('option', { name: /base64/i }).click(); await expect(component).toHaveScreenshot(); }); diff --git a/ui/blob/BlobData.tsx b/ui/blob/BlobData.tsx index b17142fba8..36c35ac201 100644 --- a/ui/blob/BlobData.tsx +++ b/ui/blob/BlobData.tsx @@ -1,4 +1,4 @@ -import { Flex, GridItem, Button } from '@chakra-ui/react'; +import { createListCollection, Flex, GridItem } from '@chakra-ui/react'; import React from 'react'; import * as blobUtils from 'lib/blob'; @@ -8,10 +8,12 @@ import downloadBlob from 'lib/downloadBlob'; import hexToBase64 from 'lib/hexToBase64'; import hexToBytes from 'lib/hexToBytes'; import hexToUtf8 from 'lib/hexToUtf8'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Button } from 'toolkit/chakra/button'; +import type { SelectOption } from 'toolkit/chakra/select'; +import { Select } from 'toolkit/chakra/select'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import CopyToClipboard from 'ui/shared/CopyToClipboard'; import RawDataSnippet from 'ui/shared/RawDataSnippet'; -import Select from 'ui/shared/select/Select'; import BlobDataImage from './BlobDataImage'; @@ -31,7 +33,7 @@ interface Props { } const BlobData = ({ data, isLoading, hash }: Props) => { - const [ format, setFormat ] = React.useState('Raw'); + const [ format, setFormat ] = React.useState>([ 'Raw' ]); const guessedType = React.useMemo(() => { if (isLoading) { @@ -41,17 +43,26 @@ const BlobData = ({ data, isLoading, hash }: Props) => { }, [ data, isLoading ]); const isImage = guessedType?.mime?.startsWith('image/'); - const formats = isImage ? FORMATS : FORMATS.filter((format) => format.value !== 'Image'); + const collection = React.useMemo(() => { + const formats = isImage ? FORMATS : FORMATS.filter((format) => format.value !== 'Image'); + return createListCollection({ + items: formats, + }); + }, [ isImage ]); React.useEffect(() => { if (isImage) { - setFormat('Image'); + setFormat([ 'Image' ]); } }, [ isImage ]); + const handleFormatChange = React.useCallback(({ value }: { value: Array }) => { + setFormat(value as Array); + }, []); + const handleDownloadButtonClick = React.useCallback(() => { const fileBlob = (() => { - switch (format) { + switch (format[0]) { case 'Image': { const bytes = hexToBytes(data); const filteredBytes = removeNonSignificantZeroBytes(bytes); @@ -74,7 +85,7 @@ const BlobData = ({ data, isLoading, hash }: Props) => { }, [ data, format, guessedType, hash ]); const content = (() => { - switch (format) { + switch (format[0]) { case 'Image': { if (!guessedType?.mime?.startsWith('image/')) { return ; @@ -102,20 +113,19 @@ const BlobData = ({ data, isLoading, hash }: Props) => { return ( - + Blob data - - + -
+
); }, [ multiple, title ]); @@ -115,7 +116,13 @@ const ContractVerificationFieldSources = ({ fileTypes, multiple, required, title rowGap={ 2 } w="100%" > - + { hasValue ? renderFiles(field.value) : renderUploadButton() }
@@ -124,7 +131,7 @@ const ContractVerificationFieldSources = ({ fileTypes, multiple, required, title { errorElement } ); - }, [ fileTypes, multiple, commonError, formState.isSubmitting, renderFiles, renderUploadButton, fullFilePath ]); + }, [ fileTypes, multiple, commonError?.type, commonError?.message, fullFilePath, formState.isSubmitting, error, renderFiles, renderUploadButton ]); const validateFileType = React.useCallback(async(value: FieldPathValue): Promise => { if (Array.isArray(value)) { diff --git a/ui/contractVerification/fields/ContractVerificationFieldZkCompiler.tsx b/ui/contractVerification/fields/ContractVerificationFieldZkCompiler.tsx index 2cba3cc2e2..7ebf8eb9e0 100644 --- a/ui/contractVerification/fields/ContractVerificationFieldZkCompiler.tsx +++ b/ui/contractVerification/fields/ContractVerificationFieldZkCompiler.tsx @@ -1,45 +1,49 @@ -import { Box, Link } from '@chakra-ui/react'; -import { useQueryClient } from '@tanstack/react-query'; +import { Box, createListCollection } from '@chakra-ui/react'; import React from 'react'; import type { FormFields } from '../types'; import type { SmartContractVerificationConfig } from 'types/client/contract'; -import { getResourceKey } from 'lib/api/useApiQuery'; -import FormFieldFancySelect from 'ui/shared/forms/fields/FormFieldFancySelect'; -import IconSvg from 'ui/shared/IconSvg'; +import { Link } from 'toolkit/chakra/link'; +import FormFieldSelectAsync from 'ui/shared/forms/fields/FormFieldSelectAsync'; import ContractVerificationFormRow from '../ContractVerificationFormRow'; const OPTIONS_LIMIT = 50; -const ContractVerificationFieldZkCompiler = () => { - const queryClient = useQueryClient(); - const config = queryClient.getQueryData(getResourceKey('contract_verification_config')); - - const options = React.useMemo(() => ( - config?.zk_compiler_versions?.map((option) => ({ label: option, value: option })) || [] +const ContractVerificationFieldZkCompiler = ({ config }: { config: SmartContractVerificationConfig }) => { + const versions = React.useMemo(() => ( + config?.zk_compiler_versions || [] ), [ config?.zk_compiler_versions ]); - const loadOptions = React.useCallback(async(inputValue: string) => { - return options - .filter(({ label }) => !inputValue || label.toLowerCase().includes(inputValue.toLowerCase())) - .slice(0, OPTIONS_LIMIT); - }, [ options ]); + const loadOptions = React.useCallback(async(inputValue: string, currentValue: Array) => { + const items = versions + ?.filter((value) => !inputValue || currentValue.includes(value) || value.toLowerCase().includes(inputValue.toLowerCase())) + .sort((a, b) => { + if (currentValue.includes(a)) { + return -1; + } + if (currentValue.includes(b)) { + return 1; + } + return 0; + }) + .slice(0, OPTIONS_LIMIT) + .map((value) => ({ label: value, value })) ?? []; + + return createListCollection({ items }); + }, [ versions ]); return ( - + name="zk_compiler" - placeholder="ZK compiler (enter version or use the dropdown)" - placeholderIcon={ } + placeholder="ZK compiler" loadOptions={ loadOptions } - defaultOptions - isRequired - isAsync + required /> - zksolc + zksolc compiler version. diff --git a/ui/contractVerification/methods/ContractVerificationFlattenSourceCode.tsx b/ui/contractVerification/methods/ContractVerificationFlattenSourceCode.tsx index ee258e1c97..fcd8f7301f 100644 --- a/ui/contractVerification/methods/ContractVerificationFlattenSourceCode.tsx +++ b/ui/contractVerification/methods/ContractVerificationFlattenSourceCode.tsx @@ -17,8 +17,8 @@ const ContractVerificationFlattenSourceCode = ({ config }: { config: SmartContra { !config?.is_rust_verifier_microservice_enabled && } { config?.is_rust_verifier_microservice_enabled && } - - + + { !config?.is_rust_verifier_microservice_enabled && } diff --git a/ui/contractVerification/methods/ContractVerificationMultiPartFile.tsx b/ui/contractVerification/methods/ContractVerificationMultiPartFile.tsx index 8212b3b583..73067b01e0 100644 --- a/ui/contractVerification/methods/ContractVerificationMultiPartFile.tsx +++ b/ui/contractVerification/methods/ContractVerificationMultiPartFile.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import type { SmartContractVerificationConfig } from 'types/client/contract'; + import ContractVerificationMethod from '../ContractVerificationMethod'; import ContractVerificationFieldCompiler from '../fields/ContractVerificationFieldCompiler'; import ContractVerificationFieldEvmVersion from '../fields/ContractVerificationFieldEvmVersion'; @@ -9,11 +11,11 @@ import ContractVerificationFieldSources from '../fields/ContractVerificationFiel const FILE_TYPES = [ '.sol' as const, '.yul' as const ]; -const ContractVerificationMultiPartFile = () => { +const ContractVerificationMultiPartFile = ({ config }: { config: SmartContractVerificationConfig }) => { return ( - - + + { Full tutorial about contract verification via Foundry on Blockscout is available - + here diff --git a/ui/contractVerification/methods/ContractVerificationSolidityHardhat.tsx b/ui/contractVerification/methods/ContractVerificationSolidityHardhat.tsx index 9dda8e2e25..c5c03b52a1 100644 --- a/ui/contractVerification/methods/ContractVerificationSolidityHardhat.tsx +++ b/ui/contractVerification/methods/ContractVerificationSolidityHardhat.tsx @@ -1,4 +1,4 @@ -import { Box, Flex, Link } from '@chakra-ui/react'; +import { Box, Flex } from '@chakra-ui/react'; import React from 'react'; import { useFormContext } from 'react-hook-form'; @@ -6,6 +6,7 @@ import type { FormFields } from '../types'; import type { SmartContractVerificationConfig } from 'types/client/contract'; import config from 'configs/app'; +import { Link } from 'toolkit/chakra/link'; import ContractVerificationFormCodeSnippet from '../ContractVerificationFormCodeSnippet'; import ContractVerificationFormRow from '../ContractVerificationFormRow'; @@ -56,7 +57,7 @@ const ContractVerificationSolidityHardhat = ({ config: formConfig }: { config: S Full tutorial about contract verification via Hardhat on Blockscout is available - + here diff --git a/ui/contractVerification/methods/ContractVerificationStandardInput.tsx b/ui/contractVerification/methods/ContractVerificationStandardInput.tsx index 9f0e9b0974..53a5036077 100644 --- a/ui/contractVerification/methods/ContractVerificationStandardInput.tsx +++ b/ui/contractVerification/methods/ContractVerificationStandardInput.tsx @@ -18,8 +18,8 @@ const ContractVerificationStandardInput = ({ config }: { config: SmartContractVe return ( { !config?.is_rust_verifier_microservice_enabled && } - - { rollupFeature.isEnabled && rollupFeature.type === 'zkSync' && } + + { rollupFeature.isEnabled && rollupFeature.type === 'zkSync' && } { +const ContractVerificationStylusGitHubRepo = ({ config }: { config: SmartContractVerificationConfig }) => { const [ latestCommitHash, setLatestCommitHash ] = React.useState(undefined); return ( - + @@ -23,7 +24,6 @@ const ContractVerificationStylusGitHubRepo = () => { name="path_prefix" placeholder="Path prefix" - size={{ base: 'md', lg: 'lg' }} /> The crate should be located in the root directory. If it is not the case, please specify the relative path from diff --git a/ui/contractVerification/methods/ContractVerificationVyperContract.tsx b/ui/contractVerification/methods/ContractVerificationVyperContract.tsx index f5a975d7ab..80f5a99175 100644 --- a/ui/contractVerification/methods/ContractVerificationVyperContract.tsx +++ b/ui/contractVerification/methods/ContractVerificationVyperContract.tsx @@ -13,8 +13,8 @@ const ContractVerificationVyperContract = ({ config }: { config: SmartContractVe return ( - - { config?.is_rust_verifier_microservice_enabled && } + + { config?.is_rust_verifier_microservice_enabled && } { !config?.is_rust_verifier_microservice_enabled && } diff --git a/ui/contractVerification/methods/ContractVerificationVyperMultiPartFile.tsx b/ui/contractVerification/methods/ContractVerificationVyperMultiPartFile.tsx index 0d3148c448..906060db9e 100644 --- a/ui/contractVerification/methods/ContractVerificationVyperMultiPartFile.tsx +++ b/ui/contractVerification/methods/ContractVerificationVyperMultiPartFile.tsx @@ -1,6 +1,9 @@ -import { Link } from '@chakra-ui/react'; import React from 'react'; +import type { SmartContractVerificationConfig } from 'types/client/contract'; + +import { Link } from 'toolkit/chakra/link'; + import ContractVerificationMethod from '../ContractVerificationMethod'; import ContractVerificationFieldCompiler from '../fields/ContractVerificationFieldCompiler'; import ContractVerificationFieldEvmVersion from '../fields/ContractVerificationFieldEvmVersion'; @@ -9,7 +12,7 @@ import ContractVerificationFieldSources from '../fields/ContractVerificationFiel const MAIN_SOURCES_TYPES = [ '.vy' as const ]; const INTERFACE_TYPES = [ '.vy' as const, '.json' as const ]; -const ContractVerificationVyperMultiPartFile = () => { +const ContractVerificationVyperMultiPartFile = ({ config }: { config: SmartContractVerificationConfig }) => { const interfacesHint = ( <> @@ -21,8 +24,8 @@ const ContractVerificationVyperMultiPartFile = () => { return ( - - + + { +const ContractVerificationVyperStandardInput = ({ config }: { config: SmartContractVerificationConfig }) => { return ( - + ; + license_type: Array; } export interface FormFieldsFlattenSourceCode extends FormFieldsBase { is_yul: boolean; name: string | undefined; - compiler: Option | null; - evm_version: Option | null; + compiler: Array; + evm_version: Array; is_optimization_enabled: boolean; optimization_runs: string; code: string; @@ -38,7 +34,7 @@ export interface FormFieldsFlattenSourceCode extends FormFieldsBase { export interface FormFieldsStandardInput extends FormFieldsBase { name: string; - compiler: Option | null; + compiler: Array; sources: Array; autodetect_constructor_args: boolean; constructor_args: string; @@ -46,8 +42,8 @@ export interface FormFieldsStandardInput extends FormFieldsBase { export interface FormFieldsStandardInputZk extends FormFieldsBase { name: string; - compiler: Option | null; - zk_compiler: Option | null; + compiler: Array; + zk_compiler: Array; sources: Array; autodetect_constructor_args: boolean; constructor_args: string; @@ -55,12 +51,12 @@ export interface FormFieldsStandardInputZk extends FormFieldsBase { export interface FormFieldsSourcify extends FormFieldsBase { sources: Array; - contract_index?: Option; + contract_index?: SelectOption; } export interface FormFieldsMultiPartFile extends FormFieldsBase { - compiler: Option | null; - evm_version: Option | null; + compiler: Array; + evm_version: Array; is_optimization_enabled: boolean; optimization_runs: string; sources: Array; @@ -69,26 +65,26 @@ export interface FormFieldsMultiPartFile extends FormFieldsBase { export interface FormFieldsVyperContract extends FormFieldsBase { name: string; - evm_version: Option | null; - compiler: Option | null; + evm_version: Array; + compiler: Array; code: string; constructor_args: string | undefined; } export interface FormFieldsVyperMultiPartFile extends FormFieldsBase { - compiler: Option | null; - evm_version: Option | null; + compiler: Array; + evm_version: Array; sources: Array; interfaces: Array; } export interface FormFieldsVyperStandardInput extends FormFieldsBase { - compiler: Option | null; + compiler: Array; sources: Array; } export interface FormFieldsStylusGitHubRepo extends FormFieldsBase { - compiler: Option | null; + compiler: Array; repository_url: string; commit_hash: string; path_prefix: string; diff --git a/ui/contractVerification/utils.ts b/ui/contractVerification/utils.ts index 16525b0c10..2ff0b2f7a6 100644 --- a/ui/contractVerification/utils.ts +++ b/ui/contractVerification/utils.ts @@ -51,123 +51,93 @@ export const METHOD_LABELS: Record = { export const DEFAULT_VALUES: Record = { 'flattened-code': { address: '', - method: { - value: 'flattened-code' as const, - label: METHOD_LABELS['flattened-code'], - }, + method: [ 'flattened-code' ], is_yul: false, name: '', - compiler: null, - evm_version: null, + compiler: [], + evm_version: [], is_optimization_enabled: true, optimization_runs: '200', code: '', autodetect_constructor_args: true, constructor_args: '', libraries: [], - license_type: null, + license_type: [], }, 'standard-input': { address: '', - method: { - value: 'standard-input' as const, - label: METHOD_LABELS['standard-input'], - }, + method: [ 'standard-input' ], name: '', - compiler: null, + compiler: [], sources: [], autodetect_constructor_args: true, constructor_args: '', - license_type: null, + license_type: [], }, sourcify: { address: '', - method: { - value: 'sourcify' as const, - label: METHOD_LABELS.sourcify, - }, + method: [ 'sourcify' ], sources: [], - license_type: null, + license_type: [], }, 'multi-part': { address: '', - method: { - value: 'multi-part' as const, - label: METHOD_LABELS['multi-part'], - }, - compiler: null, - evm_version: null, + method: [ 'multi-part' ], + compiler: [], + evm_version: [], is_optimization_enabled: true, optimization_runs: '200', sources: [], libraries: [], - license_type: null, + license_type: [], }, 'vyper-code': { address: '', - method: { - value: 'vyper-code' as const, - label: METHOD_LABELS['vyper-code'], - }, + method: [ 'vyper-code' ], name: '', - compiler: null, - evm_version: null, + compiler: [], + evm_version: [], code: '', constructor_args: '', - license_type: null, + license_type: [], }, 'vyper-multi-part': { address: '', - method: { - value: 'vyper-multi-part' as const, - label: METHOD_LABELS['vyper-multi-part'], - }, - compiler: null, - evm_version: null, + method: [ 'vyper-multi-part' ], + compiler: [], + evm_version: [], sources: [], - license_type: null, + license_type: [], }, 'vyper-standard-input': { address: '', - method: { - value: 'vyper-standard-input' as const, - label: METHOD_LABELS['vyper-standard-input'], - }, - compiler: null, + method: [ 'vyper-standard-input' ], + compiler: [], sources: [], - license_type: null, + license_type: [], }, 'solidity-hardhat': { address: '', - method: { - value: 'solidity-hardhat' as const, - label: METHOD_LABELS['solidity-hardhat'], - }, - compiler: null, + method: [ 'solidity-hardhat' ], + compiler: [], sources: [], - license_type: null, + license_type: [], }, 'solidity-foundry': { address: '', - method: { - value: 'solidity-foundry' as const, - label: METHOD_LABELS['solidity-foundry'], - }, - compiler: null, + method: [ 'solidity-foundry' ], + compiler: [], sources: [], - license_type: null, + license_type: [], }, 'stylus-github-repository': { address: '', - method: { - value: 'stylus-github-repository' as const, - label: METHOD_LABELS['stylus-github-repository'], - }, - compiler: null, + method: [ 'stylus-github-repository' ], + compiler: [], repository_url: '', commit_hash: '', path_prefix: '', - license_type: null, + license_type: [], }, }; @@ -188,11 +158,11 @@ export function getDefaultValues( if ('evm_version' in defaultValues) { if (method === 'flattened-code' || method === 'multi-part') { - defaultValues.evm_version = config.solidity_evm_versions.find((value) => value === 'default') ? { label: 'default', value: 'default' } : null; + defaultValues.evm_version = config.solidity_evm_versions.find((value) => value === 'default') ? [ 'default' ] : []; } if (method === 'vyper-multi-part') { - defaultValues.evm_version = config.vyper_evm_versions.find((value) => value === 'default') ? { label: 'default', value: 'default' } : null; + defaultValues.evm_version = config.vyper_evm_versions.find((value) => value === 'default') ? [ 'default' ] : []; } } @@ -204,10 +174,7 @@ export function getDefaultValues( } if (singleMethod) { - defaultValues.method = { - label: METHOD_LABELS[config.verification_options[0]], - value: config.verification_options[0], - }; + defaultValues.method = config.verification_options; } return defaultValues; @@ -235,21 +202,21 @@ export function sortVerificationMethods(methodA: SmartContractVerificationMethod export function prepareRequestBody(data: FormFields): FetchParams['body'] { const defaultLicenseType: SmartContractLicenseType = 'none'; - switch (data.method.value) { + switch (data.method[0]) { case 'flattened-code': { const _data = data as FormFieldsFlattenSourceCode; return { - compiler_version: _data.compiler?.value, + compiler_version: _data.compiler?.[0], source_code: _data.code, is_optimization_enabled: _data.is_optimization_enabled, is_yul_contract: _data.is_yul, optimization_runs: _data.optimization_runs, contract_name: _data.name || undefined, libraries: reduceLibrariesArray(_data.libraries), - evm_version: _data.evm_version?.value, + evm_version: _data.evm_version?.[0], autodetect_constructor_args: _data.autodetect_constructor_args, constructor_args: _data.constructor_args, - license_type: _data.license_type?.value ?? defaultLicenseType, + license_type: _data.license_type?.[0] ?? defaultLicenseType, }; } @@ -257,15 +224,15 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] { const _data = data as (FormFieldsStandardInput | FormFieldsStandardInputZk); const body = new FormData(); - _data.compiler && body.set('compiler_version', _data.compiler.value); - body.set('license_type', _data.license_type?.value ?? defaultLicenseType); + _data.compiler && body.set('compiler_version', _data.compiler?.[0]); + body.set('license_type', _data.license_type?.[0] ?? defaultLicenseType); body.set('contract_name', _data.name); body.set('autodetect_constructor_args', String(Boolean(_data.autodetect_constructor_args))); body.set('constructor_args', _data.constructor_args); addFilesToFormData(body, _data.sources, 'files'); // zkSync fields - 'zk_compiler' in _data && _data.zk_compiler && body.set('zk_compiler_version', _data.zk_compiler.value); + 'zk_compiler' in _data && _data.zk_compiler && body.set('zk_compiler_version', _data.zk_compiler?.[0]); return body; } @@ -274,8 +241,8 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] { const _data = data as FormFieldsSourcify; const body = new FormData(); addFilesToFormData(body, _data.sources, 'files'); - body.set('chosen_contract_index', _data.contract_index?.value ?? defaultLicenseType); - _data.license_type && body.set('license_type', _data.license_type.value); + body.set('chosen_contract_index', _data.contract_index?.value ?? '0'); + _data.license_type && body.set('license_type', _data.license_type?.[0] ?? defaultLicenseType); return body; } @@ -284,9 +251,9 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] { const _data = data as FormFieldsMultiPartFile; const body = new FormData(); - _data.compiler && body.set('compiler_version', _data.compiler.value); - _data.evm_version && body.set('evm_version', _data.evm_version.value); - body.set('license_type', _data.license_type?.value ?? defaultLicenseType); + _data.compiler && body.set('compiler_version', _data.compiler?.[0]); + _data.evm_version && body.set('evm_version', _data.evm_version?.[0]); + body.set('license_type', _data.license_type?.[0] ?? defaultLicenseType); body.set('is_optimization_enabled', String(Boolean(_data.is_optimization_enabled))); _data.is_optimization_enabled && body.set('optimization_runs', _data.optimization_runs); @@ -301,12 +268,12 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] { const _data = data as FormFieldsVyperContract; return { - compiler_version: _data.compiler?.value, - evm_version: _data.evm_version?.value, + compiler_version: _data.compiler?.[0], + evm_version: _data.evm_version?.[0], source_code: _data.code, contract_name: _data.name, constructor_args: _data.constructor_args, - license_type: _data.license_type?.value ?? defaultLicenseType, + license_type: _data.license_type?.[0] ?? defaultLicenseType, }; } @@ -314,9 +281,9 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] { const _data = data as FormFieldsVyperMultiPartFile; const body = new FormData(); - _data.compiler && body.set('compiler_version', _data.compiler.value); - _data.evm_version && body.set('evm_version', _data.evm_version.value); - body.set('license_type', _data.license_type?.value ?? defaultLicenseType); + _data.compiler && body.set('compiler_version', _data.compiler?.[0]); + _data.evm_version && body.set('evm_version', _data.evm_version?.[0]); + body.set('license_type', _data.license_type?.[0] ?? defaultLicenseType); addFilesToFormData(body, _data.sources, 'files'); addFilesToFormData(body, _data.interfaces, 'interfaces'); @@ -327,8 +294,8 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] { const _data = data as FormFieldsVyperStandardInput; const body = new FormData(); - _data.compiler && body.set('compiler_version', _data.compiler.value); - body.set('license_type', _data.license_type?.value ?? defaultLicenseType); + _data.compiler && body.set('compiler_version', _data.compiler?.[0]); + body.set('license_type', _data.license_type?.[0] ?? defaultLicenseType); addFilesToFormData(body, _data.sources, 'files'); return body; @@ -338,11 +305,11 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] { const _data = data as FormFieldsStylusGitHubRepo; return { - cargo_stylus_version: _data.compiler?.value, + cargo_stylus_version: _data.compiler?.[0], repository_url: _data.repository_url, commit: _data.commit_hash, path_prefix: _data.path_prefix, - license_type: _data.license_type?.value ?? defaultLicenseType, + license_type: _data.license_type?.[0] ?? defaultLicenseType, }; } @@ -357,6 +324,10 @@ function reduceLibrariesArray(libraries: Array | undefined) { return; } + if (libraries.every((item) => item.name === '' && item.address === '')) { + return; + } + return libraries.reduce>((result, item) => { result[item.name] = item.address; return result; diff --git a/ui/csvExport/CsvExportForm.tsx b/ui/csvExport/CsvExportForm.tsx index 12c85568f5..180dfc3588 100644 --- a/ui/csvExport/CsvExportForm.tsx +++ b/ui/csvExport/CsvExportForm.tsx @@ -1,4 +1,4 @@ -import { Alert, Button, chakra, Flex } from '@chakra-ui/react'; +import { chakra, Flex } from '@chakra-ui/react'; import React from 'react'; import type { SubmitHandler } from 'react-hook-form'; import { useForm, FormProvider } from 'react-hook-form'; @@ -11,7 +11,9 @@ import buildUrl from 'lib/api/buildUrl'; import type { ResourceName } from 'lib/api/resources'; import dayjs from 'lib/date/dayjs'; import downloadBlob from 'lib/downloadBlob'; -import useToast from 'lib/hooks/useToast'; +import { Alert } from 'toolkit/chakra/alert'; +import { Button } from 'toolkit/chakra/button'; +import { toaster } from 'toolkit/chakra/toaster'; import ReCaptcha from 'ui/shared/reCaptcha/ReCaptcha'; import useReCaptcha from 'ui/shared/reCaptcha/useReCaptcha'; @@ -35,7 +37,6 @@ const CsvExportForm = ({ hash, resource, filterType, filterValue, fileNameTempla }, }); const { handleSubmit, formState } = formApi; - const toast = useToast(); const recaptcha = useReCaptcha(); const onFormSubmit: SubmitHandler = React.useCallback(async(data) => { @@ -73,17 +74,13 @@ const CsvExportForm = ({ hash, resource, filterType, filterValue, fileNameTempla downloadBlob(blob, fileName); } catch (error) { - toast({ - position: 'top-right', + toaster.error({ title: 'Error', description: (error as Error)?.message || 'Something went wrong. Try again later.', - status: 'error', - variant: 'subtle', - isClosable: true, }); } - }, [ recaptcha, resource, hash, exportType, filterType, filterValue, fileNameTemplate, toast ]); + }, [ recaptcha, resource, hash, exportType, filterType, filterValue, fileNameTemplate ]); if (!config.services.reCaptchaV2.siteKey) { return ( @@ -107,12 +104,11 @@ const CsvExportForm = ({ hash, resource, filterType, filterValue, fileNameTempla diff --git a/ui/csvExport/CsvExportFormField.tsx b/ui/csvExport/CsvExportFormField.tsx index c64eef9b38..518fb2ec3a 100644 --- a/ui/csvExport/CsvExportFormField.tsx +++ b/ui/csvExport/CsvExportFormField.tsx @@ -38,12 +38,10 @@ const CsvExportFormField = ({ formApi, name }: Props) => { return ( name={ name } - type="date" - max={ dayjs().format('YYYY-MM-DD') } + inputProps={{ type: 'date', max: dayjs().format('YYYY-MM-DD') }} placeholder={ capitalize(name) } - isRequired + required rules={{ validate }} - size={{ base: 'md', lg: 'lg' }} maxW={{ base: 'auto', lg: '220px' }} /> ); diff --git a/ui/customAbi/CustomAbiModal/CustomAbiForm.tsx b/ui/customAbi/CustomAbiModal/CustomAbiForm.tsx index 7ed7f0a909..0f72134e02 100644 --- a/ui/customAbi/CustomAbiModal/CustomAbiForm.tsx +++ b/ui/customAbi/CustomAbiModal/CustomAbiForm.tsx @@ -1,7 +1,4 @@ -import { - Box, - Button, -} from '@chakra-ui/react'; +import { Box } from '@chakra-ui/react'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import React, { useCallback } from 'react'; import type { SubmitHandler } from 'react-hook-form'; @@ -13,6 +10,7 @@ import type { ResourceErrorAccount } from 'lib/api/resources'; import { resourceKey } from 'lib/api/resources'; import useApiFetch from 'lib/api/useApiFetch'; import getErrorMessage from 'lib/getErrorMessage'; +import { Button } from 'toolkit/chakra/button'; import FormFieldAddress from 'ui/shared/forms/fields/FormFieldAddress'; import FormFieldText from 'ui/shared/forms/fields/FormFieldText'; @@ -23,7 +21,7 @@ export type FormData = CustomAbi | { type Props = { data: FormData; - onClose: () => void; + onOpenChange: ({ open }: { open: boolean }) => void; onSuccess?: () => Promise; setAlertVisible: (isAlertVisible: boolean) => void; }; @@ -36,7 +34,7 @@ type Inputs = { const NAME_MAX_LENGTH = 255; -const CustomAbiForm: React.FC = ({ data, onClose, onSuccess, setAlertVisible }) => { +const CustomAbiForm: React.FC = ({ data, onOpenChange, onSuccess, setAlertVisible }) => { const formApi = useForm({ defaultValues: { contract_address_hash: data?.contract_address_hash || '', @@ -82,7 +80,7 @@ const CustomAbiForm: React.FC = ({ data, onClose, onSuccess, setAlertVisi return [ response, ...(prevData || []) ]; }); await onSuccess?.(); - onClose(); + onOpenChange({ open: false }); }, onError: (error: ResourceErrorAccount) => { const errorMap = error.payload?.errors; @@ -110,37 +108,36 @@ const CustomAbiForm: React.FC = ({ data, onClose, onSuccess, setAlertVisi name="contract_address_hash" placeholder="Smart contract address (0x...)" - isRequired - bgColor="dialog_bg" - isReadOnly={ Boolean(data && 'contract_address_hash' in data) } + required + bgColor="dialog.bg" + readOnly={ Boolean(data && 'contract_address_hash' in data) } mb={ 5 } /> name="name" placeholder="Project name" - isRequired + required rules={{ maxLength: NAME_MAX_LENGTH, }} - bgColor="dialog_bg" + bgColor="dialog.bg" mb={ 5 } /> name="abi" placeholder="Custom ABI [{...}] (JSON format)" - isRequired + required asComponent="Textarea" - bgColor="dialog_bg" - size="lg" + bgColor="dialog.bg" + size="2xl" minH="300px" mb={ 8 } /> diff --git a/ui/customAbi/CustomAbiModal/CustomAbiModal.tsx b/ui/customAbi/CustomAbiModal/CustomAbiModal.tsx index 435deb380f..1c2e8e9b76 100644 --- a/ui/customAbi/CustomAbiModal/CustomAbiModal.tsx +++ b/ui/customAbi/CustomAbiModal/CustomAbiModal.tsx @@ -7,25 +7,25 @@ import FormModal from 'ui/shared/FormModal'; import CustomAbiForm, { type FormData } from './CustomAbiForm'; type Props = { - isOpen: boolean; - onClose: () => void; + open: boolean; + onOpenChange: ({ open }: { open: boolean }) => void; onSuccess?: () => Promise; data: FormData; }; -const CustomAbiModal: React.FC = ({ isOpen, onClose, data, onSuccess }) => { +const CustomAbiModal: React.FC = ({ open, onOpenChange, data, onSuccess }) => { const title = data && 'id' in data ? 'Edit custom ABI' : 'New custom ABI'; const text = !(data && 'id' in data) ? 'Double check the ABI matches the contract to prevent errors or incorrect results.' : ''; const [ isAlertVisible, setAlertVisible ] = useState(false); const renderForm = useCallback(() => { - return ; - }, [ data, onClose, onSuccess ]); + return ; + }, [ data, onOpenChange, onSuccess ]); return ( - isOpen={ isOpen } - onClose={ onClose } + open={ open } + onOpenChange={ onOpenChange } title={ title } text={ text } renderForm={ renderForm } diff --git a/ui/customAbi/CustomAbiTable/CustomAbiListItem.tsx b/ui/customAbi/CustomAbiTable/CustomAbiListItem.tsx index b0a92b8126..69d2fc28e7 100644 --- a/ui/customAbi/CustomAbiTable/CustomAbiListItem.tsx +++ b/ui/customAbi/CustomAbiTable/CustomAbiListItem.tsx @@ -3,7 +3,7 @@ import React, { useCallback } from 'react'; import type { CustomAbi } from 'types/api/account'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; import TableItemActionButtons from 'ui/shared/TableItemActionButtons'; @@ -33,7 +33,7 @@ const CustomAbiListItem = ({ item, isLoading, onEditClick, onDeleteClick }: Prop fontWeight="600" isLoading={ isLoading } /> - + { item.name } diff --git a/ui/customAbi/CustomAbiTable/CustomAbiTable.tsx b/ui/customAbi/CustomAbiTable/CustomAbiTable.tsx index c8cdef4b71..278423a36b 100644 --- a/ui/customAbi/CustomAbiTable/CustomAbiTable.tsx +++ b/ui/customAbi/CustomAbiTable/CustomAbiTable.tsx @@ -1,14 +1,9 @@ -import { - Table, - Thead, - Tbody, - Tr, - Th, -} from '@chakra-ui/react'; import React from 'react'; import type { CustomAbis, CustomAbi } from 'types/api/account'; +import { TableBody, TableColumnHeader, TableHeader, TableRoot, TableRow } from 'toolkit/chakra/table'; + import CustomAbiTableItem from './CustomAbiTableItem'; interface Props { @@ -20,14 +15,14 @@ interface Props { const CustomAbiTable = ({ data, isLoading, onDeleteClick, onEditClick }: Props) => { return ( - - - - - - - - + + + + ABI for Smart contract address (0x...) + + + + { data?.map((item, index) => ( )) } - -
ABI for Smart contract address (0x...)
+ + ); }; diff --git a/ui/customAbi/CustomAbiTable/CustomAbiTableItem.tsx b/ui/customAbi/CustomAbiTable/CustomAbiTableItem.tsx index b3711a9d26..a23cb121c8 100644 --- a/ui/customAbi/CustomAbiTable/CustomAbiTableItem.tsx +++ b/ui/customAbi/CustomAbiTable/CustomAbiTableItem.tsx @@ -1,13 +1,10 @@ -import { - Tr, - Td, - Box, -} from '@chakra-ui/react'; +import { Box } from '@chakra-ui/react'; import React, { useCallback } from 'react'; import type { CustomAbi } from 'types/api/account'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import { TableCell, TableRow } from 'toolkit/chakra/table'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import TableItemActionButtons from 'ui/shared/TableItemActionButtons'; @@ -29,23 +26,23 @@ const CustomAbiTableItem = ({ item, isLoading, onEditClick, onDeleteClick }: Pro }, [ item, onDeleteClick ]); return ( - - + + - + { item.name } - - + + - - + + ); }; diff --git a/ui/customAbi/DeleteCustomAbiModal.tsx b/ui/customAbi/DeleteCustomAbiModal.tsx index 1282f2af5f..389ff3a5f8 100644 --- a/ui/customAbi/DeleteCustomAbiModal.tsx +++ b/ui/customAbi/DeleteCustomAbiModal.tsx @@ -9,12 +9,12 @@ import useApiFetch from 'lib/api/useApiFetch'; import DeleteModal from 'ui/shared/DeleteModal'; type Props = { - isOpen: boolean; - onClose: () => void; + open: boolean; + onOpenChange: ({ open }: { open: boolean }) => void; data: CustomAbi; }; -const DeleteCustomAbiModal: React.FC = ({ isOpen, onClose, data }) => { +const DeleteCustomAbiModal: React.FC = ({ open, onOpenChange, data }) => { const queryClient = useQueryClient(); const apiFetch = useApiFetch(); @@ -40,8 +40,8 @@ const DeleteCustomAbiModal: React.FC = ({ isOpen, onClose, data }) => { return ( { @@ -40,8 +38,6 @@ const OptimisticDepositsListItem = ({ item, isLoading }: Props) => { @@ -60,8 +56,6 @@ const OptimisticDepositsListItem = ({ item, isLoading }: Props) => { @@ -78,7 +72,7 @@ const OptimisticDepositsListItem = ({ item, isLoading }: Props) => { Gas limit - { BigNumber(item.l2_transaction_gas_limit).toFormat() } + { BigNumber(item.l2_transaction_gas_limit).toFormat() } diff --git a/ui/deposits/optimisticL2/OptimisticDepositsTable.tsx b/ui/deposits/optimisticL2/OptimisticDepositsTable.tsx index a81036f128..a55035a5ae 100644 --- a/ui/deposits/optimisticL2/OptimisticDepositsTable.tsx +++ b/ui/deposits/optimisticL2/OptimisticDepositsTable.tsx @@ -1,9 +1,8 @@ -import { Table, Tbody, Th, Tr } from '@chakra-ui/react'; import React from 'react'; import type { OptimisticL2DepositsItem } from 'types/api/optimisticL2'; -import { default as Thead } from 'ui/shared/TheadSticky'; +import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table'; import OptimisticDepositsTableItem from './OptimisticDepositsTableItem'; @@ -15,23 +14,23 @@ import OptimisticDepositsTableItem from './OptimisticDepositsTableItem'; const OptimisticDepositsTable = ({ items, top, isLoading }: Props) => { return ( - - - - - - - - - - - - + + + + L1 block No + L2 txn hash + Age + L1 txn hash + L1 txn origin + Gas limit + + + { items.map((item, index) => ( )) } - -
L1 block NoL2 txn hashAgeL1 txn hashL1 txn originGas limit
+ + ); }; diff --git a/ui/deposits/optimisticL2/OptimisticDepositsTableItem.tsx b/ui/deposits/optimisticL2/OptimisticDepositsTableItem.tsx index b76a972bac..6b27d38016 100644 --- a/ui/deposits/optimisticL2/OptimisticDepositsTableItem.tsx +++ b/ui/deposits/optimisticL2/OptimisticDepositsTableItem.tsx @@ -1,11 +1,11 @@ -import { Td, Tr } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; import React from 'react'; import type { OptimisticL2DepositsItem } from 'types/api/optimisticL2'; import config from 'configs/app'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import { TableCell, TableRow } from 'toolkit/chakra/table'; import AddressEntityL1 from 'ui/shared/entities/address/AddressEntityL1'; import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; @@ -23,59 +23,53 @@ const OptimisticDepositsTableItem = ({ item, isLoading }: Props) => { } return ( - - + + - - + + - - + + - - + + - - + + - - - + + + { BigNumber(item.l2_transaction_gas_limit).toFormat() } - - + + ); }; diff --git a/ui/deposits/scrollL2/ScrollL2DepositsListItem.tsx b/ui/deposits/scrollL2/ScrollL2DepositsListItem.tsx index 0c4ea8099e..6c5991394d 100644 --- a/ui/deposits/scrollL2/ScrollL2DepositsListItem.tsx +++ b/ui/deposits/scrollL2/ScrollL2DepositsListItem.tsx @@ -5,7 +5,7 @@ import type { ScrollL2MessageItem } from 'types/api/scrollL2'; import config from 'configs/app'; import getCurrencyValue from 'lib/getCurrencyValue'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1'; @@ -31,15 +31,13 @@ const ScrollL2DepositsListItem = ({ item, isLoading }: Props) => { Index - + { item.id } @@ -49,8 +47,6 @@ const ScrollL2DepositsListItem = ({ item, isLoading }: Props) => { @@ -70,8 +66,6 @@ const ScrollL2DepositsListItem = ({ item, isLoading }: Props) => { ) : ( @@ -83,7 +77,7 @@ const ScrollL2DepositsListItem = ({ item, isLoading }: Props) => { Value - + { `${ valueStr } ${ config.chain.currency.symbol }` } diff --git a/ui/deposits/scrollL2/ScrollL2DepositsTable.tsx b/ui/deposits/scrollL2/ScrollL2DepositsTable.tsx index 0a272a7362..7eda226e62 100644 --- a/ui/deposits/scrollL2/ScrollL2DepositsTable.tsx +++ b/ui/deposits/scrollL2/ScrollL2DepositsTable.tsx @@ -1,10 +1,9 @@ -import { Table, Tbody, Th, Tr } from '@chakra-ui/react'; import React from 'react'; import type { ScrollL2MessageItem } from 'types/api/scrollL2'; import config from 'configs/app'; -import { default as Thead } from 'ui/shared/TheadSticky'; +import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table'; import ScrollL2DepositsTableItem from './ScrollL2DepositsTableItem'; @@ -16,23 +15,23 @@ import ScrollL2DepositsTableItem from './ScrollL2DepositsTableItem'; const ScrollL2DepositsTable = ({ items, top, isLoading }: Props) => { return ( - - - - - - - - - - - - + + + + L1 block + Index + L1 txn hash + Age + L2 txn hash + Value { config.chain.currency.symbol } + + + { items.map((item, index) => ( )) } - -
L1 blockIndexL1 txn hashAgeL2 txn hashValue { config.chain.currency.symbol }
+ + ); }; diff --git a/ui/deposits/scrollL2/ScrollL2DepositsTableItem.tsx b/ui/deposits/scrollL2/ScrollL2DepositsTableItem.tsx index 5ec1bbb355..7c0c370e7b 100644 --- a/ui/deposits/scrollL2/ScrollL2DepositsTableItem.tsx +++ b/ui/deposits/scrollL2/ScrollL2DepositsTableItem.tsx @@ -1,11 +1,12 @@ -import { Td, Tr, chakra } from '@chakra-ui/react'; +import { chakra } from '@chakra-ui/react'; import React from 'react'; import type { ScrollL2MessageItem } from 'types/api/scrollL2'; import config from 'configs/app'; import getCurrencyValue from 'lib/getCurrencyValue'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import { TableCell, TableRow } from 'toolkit/chakra/table'; import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1'; @@ -23,61 +24,55 @@ const ScrollL2DepositsTableItem = ({ item, isLoading }: Props) => { const { valueStr } = getCurrencyValue({ value: item.value, decimals: String(config.chain.currency.decimals) }); return ( - - + + - - - + + + { item.id } - - + + - - + + - - + + { item.completion_transaction_hash ? ( ) : ( - + Pending Claim ) } - - - + + + { valueStr } - - + + ); }; diff --git a/ui/deposits/shibarium/DepositsListItem.tsx b/ui/deposits/shibarium/DepositsListItem.tsx index 53d88f515f..827e88cca1 100644 --- a/ui/deposits/shibarium/DepositsListItem.tsx +++ b/ui/deposits/shibarium/DepositsListItem.tsx @@ -27,8 +27,7 @@ const DepositsListItem = ({ item, isLoading }: Props) => { @@ -38,8 +37,7 @@ const DepositsListItem = ({ item, isLoading }: Props) => { @@ -49,8 +47,7 @@ const DepositsListItem = ({ item, isLoading }: Props) => { diff --git a/ui/deposits/shibarium/DepositsTable.tsx b/ui/deposits/shibarium/DepositsTable.tsx index 1bb215f3a6..e5d229b119 100644 --- a/ui/deposits/shibarium/DepositsTable.tsx +++ b/ui/deposits/shibarium/DepositsTable.tsx @@ -1,9 +1,8 @@ -import { Table, Tbody, Th, Tr } from '@chakra-ui/react'; import React from 'react'; import type { ShibariumDepositsItem } from 'types/api/shibarium'; -import { default as Thead } from 'ui/shared/TheadSticky'; +import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table'; import DepositsTableItem from './DepositsTableItem'; @@ -15,22 +14,22 @@ import DepositsTableItem from './DepositsTableItem'; const DepositsTable = ({ items, top, isLoading }: Props) => { return ( - - - - - - - - - - - + + + + L1 block No + L1 txn hash + L2 txn hash + User + Age + + + { items.map((item, index) => ( )) } - -
L1 block NoL1 txn hashL2 txn hashUserAge
+ + ); }; diff --git a/ui/deposits/shibarium/DepositsTableItem.tsx b/ui/deposits/shibarium/DepositsTableItem.tsx index da23f68ecc..439f8f8e44 100644 --- a/ui/deposits/shibarium/DepositsTableItem.tsx +++ b/ui/deposits/shibarium/DepositsTableItem.tsx @@ -1,9 +1,9 @@ -import { Td, Tr } from '@chakra-ui/react'; import React from 'react'; import type { ShibariumDepositsItem } from 'types/api/shibarium'; import config from 'configs/app'; +import { TableCell, TableRow } from 'toolkit/chakra/table'; import AddressStringOrParam from 'ui/shared/entities/address/AddressStringOrParam'; import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; @@ -21,51 +21,48 @@ const DepositsTableItem = ({ item, isLoading }: Props) => { } return ( - - + + - - + + - - + + - - + + - - + + - - + + ); }; diff --git a/ui/deposits/zkEvmL2/ZkEvmL2DepositsListItem.tsx b/ui/deposits/zkEvmL2/ZkEvmL2DepositsListItem.tsx index 0a9de9a39e..00aed4c333 100644 --- a/ui/deposits/zkEvmL2/ZkEvmL2DepositsListItem.tsx +++ b/ui/deposits/zkEvmL2/ZkEvmL2DepositsListItem.tsx @@ -5,7 +5,7 @@ import React from 'react'; import type { ZkEvmL2DepositsItem } from 'types/api/zkEvmL2'; import config from 'configs/app'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1'; @@ -29,15 +29,14 @@ const ZkEvmL2DepositsListItem = ({ item, isLoading }: Props) => { Index - + { item.index } @@ -47,8 +46,7 @@ const ZkEvmL2DepositsListItem = ({ item, isLoading }: Props) => { @@ -68,8 +66,7 @@ const ZkEvmL2DepositsListItem = ({ item, isLoading }: Props) => { ) : ( @@ -81,14 +78,14 @@ const ZkEvmL2DepositsListItem = ({ item, isLoading }: Props) => { Value - + { BigNumber(item.value).toFormat() } Token - + { item.symbol } diff --git a/ui/deposits/zkEvmL2/ZkEvmL2DepositsTable.tsx b/ui/deposits/zkEvmL2/ZkEvmL2DepositsTable.tsx index 69f76ad298..a3b59dbab9 100644 --- a/ui/deposits/zkEvmL2/ZkEvmL2DepositsTable.tsx +++ b/ui/deposits/zkEvmL2/ZkEvmL2DepositsTable.tsx @@ -1,9 +1,8 @@ -import { Table, Tbody, Th, Tr } from '@chakra-ui/react'; import React from 'react'; import type { ZkEvmL2DepositsItem } from 'types/api/zkEvmL2'; -import { default as Thead } from 'ui/shared/TheadSticky'; +import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table'; import ZkEvmL2DepositsTableItem from './ZkEvmL2DepositsTableItem'; @@ -15,24 +14,24 @@ import ZkEvmL2DepositsTableItem from './ZkEvmL2DepositsTableItem'; const ZkEvmL2DepositsTable = ({ items, top, isLoading }: Props) => { return ( - - - - - - - - - - - - - + + + + L1 block + Index + L1 txn hash + Age + L2 txn hash + Value + Token + + + { items.map((item, index) => ( )) } - -
L1 blockIndexL1 txn hashAgeL2 txn hashValueToken
+ + ); }; diff --git a/ui/deposits/zkEvmL2/ZkEvmL2DepositsTableItem.tsx b/ui/deposits/zkEvmL2/ZkEvmL2DepositsTableItem.tsx index 62a52ac14f..f81933bebb 100644 --- a/ui/deposits/zkEvmL2/ZkEvmL2DepositsTableItem.tsx +++ b/ui/deposits/zkEvmL2/ZkEvmL2DepositsTableItem.tsx @@ -1,11 +1,12 @@ -import { Td, Tr, chakra } from '@chakra-ui/react'; +import { chakra } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; import React from 'react'; import type { ZkEvmL2DepositsItem } from 'types/api/zkEvmL2'; import config from 'configs/app'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import { TableCell, TableRow } from 'toolkit/chakra/table'; import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1'; @@ -21,66 +22,63 @@ const ZkEvmL2DepositsTableItem = ({ item, isLoading }: Props) => { } return ( - - + + - - - + + + { item.index } - - + + - - + + - - + + { item.l2_transaction_hash ? ( ) : ( - + Pending Claim ) } - - - + + + { BigNumber(item.value).toFormat() } - - - + + + { item.symbol } - - + + ); }; diff --git a/ui/disputeGames/optimisticL2/OptimisticL2DisputeGamesListItem.tsx b/ui/disputeGames/optimisticL2/OptimisticL2DisputeGamesListItem.tsx index 8080006e60..92848c9d0d 100644 --- a/ui/disputeGames/optimisticL2/OptimisticL2DisputeGamesListItem.tsx +++ b/ui/disputeGames/optimisticL2/OptimisticL2DisputeGamesListItem.tsx @@ -3,7 +3,7 @@ import React from 'react'; import type { OptimisticL2DisputeGamesItem } from 'types/api/optimisticL2'; import config from 'configs/app'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import CopyToClipboard from 'ui/shared/CopyToClipboard'; import BlockEntityL2 from 'ui/shared/entities/block/BlockEntityL2'; import HashStringShorten from 'ui/shared/HashStringShorten'; @@ -24,17 +24,17 @@ const OptimisticL2DisputeGamesListItem = ({ item, isLoading }: Props) => { Index - { item.index } + { item.index } Game type - { item.game_type } + { item.game_type } Address - + @@ -45,8 +45,6 @@ const OptimisticL2DisputeGamesListItem = ({ item, isLoading }: Props) => { @@ -62,7 +60,7 @@ const OptimisticL2DisputeGamesListItem = ({ item, isLoading }: Props) => { Status - { item.status } + { item.status } { item.resolved_at && ( diff --git a/ui/disputeGames/optimisticL2/OptimisticL2DisputeGamesTable.tsx b/ui/disputeGames/optimisticL2/OptimisticL2DisputeGamesTable.tsx index e79acb92d9..d6255be3c9 100644 --- a/ui/disputeGames/optimisticL2/OptimisticL2DisputeGamesTable.tsx +++ b/ui/disputeGames/optimisticL2/OptimisticL2DisputeGamesTable.tsx @@ -1,9 +1,8 @@ -import { Table, Tbody, Th, Tr } from '@chakra-ui/react'; import React from 'react'; import type { OptimisticL2DisputeGamesItem } from 'types/api/optimisticL2'; -import { default as Thead } from 'ui/shared/TheadSticky'; +import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table'; import OptimisticL2DisputeGamesTableItem from './OptimisticL2DisputeGamesTableItem'; @@ -15,19 +14,19 @@ type Props = { const OptimisticL2DisputeGamesTable = ({ items, top, isLoading }: Props) => { return ( - - - - - - - - - - - - - + + + + Index + Game type + Address + L2 block # + Age + Status + Resolution age + + + { items.map((item, index) => ( { isLoading={ isLoading } /> )) } - -
IndexGame typeAddressL2 block #AgeStatusResolution age
+ + ); }; diff --git a/ui/disputeGames/optimisticL2/OptimisticL2DisputeGamesTableItem.tsx b/ui/disputeGames/optimisticL2/OptimisticL2DisputeGamesTableItem.tsx index 0b4d6a5759..6129dd64da 100644 --- a/ui/disputeGames/optimisticL2/OptimisticL2DisputeGamesTableItem.tsx +++ b/ui/disputeGames/optimisticL2/OptimisticL2DisputeGamesTableItem.tsx @@ -1,10 +1,11 @@ -import { Flex, Td, Tr } from '@chakra-ui/react'; +import { Flex } from '@chakra-ui/react'; import React from 'react'; import type { OptimisticL2DisputeGamesItem } from 'types/api/optimisticL2'; import config from 'configs/app'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import { TableCell, TableRow } from 'toolkit/chakra/table'; import CopyToClipboard from 'ui/shared/CopyToClipboard'; import BlockEntityL2 from 'ui/shared/entities/block/BlockEntityL2'; import HashStringShorten from 'ui/shared/HashStringShorten'; @@ -20,49 +21,47 @@ const OptimisticL2DisputeGamesTableItem = ({ item, isLoading }: Props) => { } return ( - - - { item.index } - - - { item.game_type } - - + + + { item.index } + + + { item.game_type } + + - + - - + + - - + + - - - { item.status } - - + + + { item.status } + + - - + + ); }; diff --git a/ui/games/CapybaraRunner.tsx b/ui/games/CapybaraRunner.tsx index 11d01405b6..df96da3c0c 100644 --- a/ui/games/CapybaraRunner.tsx +++ b/ui/games/CapybaraRunner.tsx @@ -1,10 +1,12 @@ /* eslint-disable @next/next/no-img-element */ -import { Box, Text, Button, Flex } from '@chakra-ui/react'; +import { Box, Text, Flex } from '@chakra-ui/react'; import Script from 'next/script'; import React from 'react'; import config from 'configs/app'; import useIsMobile from 'lib/hooks/useIsMobile'; +import { Button } from 'toolkit/chakra/button'; +import { Link } from 'toolkit/chakra/link'; const easterEggBadgeFeature = config.features.easterEggBadge; const CapybaraRunner = () => { @@ -50,7 +52,13 @@ const CapybaraRunner = () => { You unlocked a hidden badge! Congratulations! You’re eligible to claim an epic hidden badge! - + + + ) } diff --git a/ui/gasTracker/GasTrackerChart.tsx b/ui/gasTracker/GasTrackerChart.tsx index a85991db8c..f3f4d6357f 100644 --- a/ui/gasTracker/GasTrackerChart.tsx +++ b/ui/gasTracker/GasTrackerChart.tsx @@ -1,25 +1,29 @@ -import { Box, Flex, chakra, useBoolean } from '@chakra-ui/react'; +import { Box, Flex, chakra } from '@chakra-ui/react'; import React from 'react'; import { route } from 'nextjs-routes'; import useApiQuery from 'lib/api/useApiQuery'; import { STATS_CHARTS } from 'stubs/stats'; +import { Link } from 'toolkit/chakra/link'; import ContentLoader from 'ui/shared/ContentLoader'; import DataFetchAlert from 'ui/shared/DataFetchAlert'; -import LinkInternal from 'ui/shared/links/LinkInternal'; import ChartWidgetContainer from 'ui/stats/ChartWidgetContainer'; const GAS_PRICE_CHART_ID = 'averageGasPrice'; const GasTrackerChart = () => { - const [ isChartLoadingError, setChartLoadingError ] = useBoolean(false); + const [ isChartLoadingError, setChartLoadingError ] = React.useState(false); const { data, isPlaceholderData, isError } = useApiQuery('stats_lines', { queryOptions: { placeholderData: STATS_CHARTS, }, }); + const handleLoadingError = React.useCallback(() => { + setChartLoadingError(true); + }, []); + const content = (() => { if (isPlaceholderData) { return ; @@ -43,7 +47,7 @@ const GasTrackerChart = () => { interval="oneMonth" units={ chart.units || undefined } isPlaceholderData={ isPlaceholderData } - onLoadingError={ setChartLoadingError.on } + onLoadingError={ handleLoadingError } h="320px" /> ); @@ -53,7 +57,7 @@ const GasTrackerChart = () => { Gas price history - Charts & stats + Charts & stats { content } diff --git a/ui/gasTracker/GasTrackerFaq.tsx b/ui/gasTracker/GasTrackerFaq.tsx index 054c0ce990..cd7843db2b 100644 --- a/ui/gasTracker/GasTrackerFaq.tsx +++ b/ui/gasTracker/GasTrackerFaq.tsx @@ -1,12 +1,10 @@ -import { - Box, - Heading, - Accordion, -} from '@chakra-ui/react'; +import { Box } from '@chakra-ui/react'; import React from 'react'; import config from 'configs/app'; import { currencyUnits } from 'lib/units'; +import { AccordionRoot } from 'toolkit/chakra/accordion'; +import { Heading } from 'toolkit/chakra/heading'; import GasTrackerFaqItem from './GasTrackerFaqItem'; @@ -34,12 +32,12 @@ const FAQ_ITEMS = [ const GasTrackerFaq = () => { return ( - FAQ - + FAQ + { FAQ_ITEMS.map((item, index) => ( )) } - + ); }; diff --git a/ui/gasTracker/GasTrackerFaqItem.tsx b/ui/gasTracker/GasTrackerFaqItem.tsx index 8a15b52636..78a252c932 100644 --- a/ui/gasTracker/GasTrackerFaqItem.tsx +++ b/ui/gasTracker/GasTrackerFaqItem.tsx @@ -1,12 +1,6 @@ -import { - AccordionItem, - AccordionButton, - AccordionPanel, - AccordionIcon, - Text, - chakra, - useColorModeValue, -} from '@chakra-ui/react'; +import { Text } from '@chakra-ui/react'; + +import { AccordionItem, AccordionItemTrigger, AccordionItemContent } from 'toolkit/chakra/accordion'; interface Props { question: string; @@ -14,24 +8,14 @@ interface Props { } const GasTrackerFaqItem = ({ question, answer }: Props) => { - const hoverColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50'); - const borderColor = useColorModeValue('blackAlpha.100', 'whiteAlpha.200'); return ( - - { ({ isExpanded }) => ( - <> - - { question } - - - - { answer } - - - ) } + + + { question } + + + { answer } + ); }; diff --git a/ui/gasTracker/GasTrackerNetworkUtilization.tsx b/ui/gasTracker/GasTrackerNetworkUtilization.tsx index a30e520790..9a13af35d3 100644 --- a/ui/gasTracker/GasTrackerNetworkUtilization.tsx +++ b/ui/gasTracker/GasTrackerNetworkUtilization.tsx @@ -2,7 +2,7 @@ import { chakra } from '@chakra-ui/react'; import React from 'react'; import { mdash } from 'lib/html-entities'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Skeleton } from 'toolkit/chakra/skeleton'; interface Props { percentage: number; @@ -30,7 +30,7 @@ const GasTrackerNetworkUtilization = ({ percentage, isLoading }: Props) => { const color = colors[load]; return ( - + Network utilization { percentage.toFixed(2) }% { mdash } { load } load diff --git a/ui/gasTracker/GasTrackerPriceSnippet.tsx b/ui/gasTracker/GasTrackerPriceSnippet.tsx index 14420ba5a6..9858a31c6b 100644 --- a/ui/gasTracker/GasTrackerPriceSnippet.tsx +++ b/ui/gasTracker/GasTrackerPriceSnippet.tsx @@ -1,11 +1,11 @@ -import { Box, Flex, useColorModeValue } from '@chakra-ui/react'; +import { Box, Flex } from '@chakra-ui/react'; import React from 'react'; import type { GasPriceInfo, GasPrices } from 'types/api/stats'; import { SECOND } from 'lib/consts'; import { asymp } from 'lib/html-entities'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import GasPrice from 'ui/shared/gas/GasPrice'; import type { IconName } from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg'; @@ -30,10 +30,10 @@ const ICONS: Record = { const GasTrackerPriceSnippet = ({ data, type, isLoading }: Props) => { const bgColors = { fast: 'transparent', - average: useColorModeValue('gray.50', 'whiteAlpha.200'), - slow: useColorModeValue('gray.50', 'whiteAlpha.200'), + average: { _light: 'gray.50', _dark: 'whiteAlpha.200' }, + slow: { _light: 'gray.50', _dark: 'whiteAlpha.200' }, }; - const borderColor = useColorModeValue('gray.200', 'whiteAlpha.300'); + const borderColor = { _light: 'gray.200', _dark: 'whiteAlpha.300' }; return ( { borderBottomWidth: { base: '2px', lg: '0' }, }} > - { TITLES[type] } + { TITLES[type] } - + - + { data.price !== null && data.fiat_price !== null && } per transaction { typeof data.time === 'number' && data.time > 0 && / { (data.time / SECOND).toLocaleString(undefined, { maximumFractionDigits: 1 }) }s } - + { typeof data.base_fee === 'number' && Base { data.base_fee.toLocaleString(undefined, { maximumFractionDigits: 0 }) } } { typeof data.base_fee === 'number' && typeof data.priority_fee === 'number' && / } { typeof data.priority_fee === 'number' && Priority { data.priority_fee.toLocaleString(undefined, { maximumFractionDigits: 0 }) } } diff --git a/ui/gasTracker/GasTrackerPrices.tsx b/ui/gasTracker/GasTrackerPrices.tsx index 2f90ec16b3..3c6485654b 100644 --- a/ui/gasTracker/GasTrackerPrices.tsx +++ b/ui/gasTracker/GasTrackerPrices.tsx @@ -1,4 +1,4 @@ -import { Flex, useColorModeValue } from '@chakra-ui/react'; +import { Flex } from '@chakra-ui/react'; import React from 'react'; import type { GasPrices } from 'types/api/stats'; @@ -11,13 +11,11 @@ interface Props { } const GasTrackerPrices = ({ prices, isLoading }: Props) => { - const borderColor = useColorModeValue('gray.200', 'whiteAlpha.300'); - return ( { }); return ( - - + + diff --git a/ui/home/HeroBanner.tsx b/ui/home/HeroBanner.tsx index 396987c9d5..7d6450b4fe 100644 --- a/ui/home/HeroBanner.tsx +++ b/ui/home/HeroBanner.tsx @@ -1,4 +1,6 @@ -import { Box, Flex, Heading, useColorModeValue } from '@chakra-ui/react'; +// we use custom heading size for hero banner +// eslint-disable-next-line no-restricted-imports +import { Box, Flex, Heading } from '@chakra-ui/react'; import React from 'react'; import config from 'configs/app'; @@ -8,39 +10,44 @@ import SearchBar from 'ui/snippets/searchBar/SearchBar'; import UserProfileDesktop from 'ui/snippets/user/profile/UserProfileDesktop'; import UserWalletDesktop from 'ui/snippets/user/wallet/UserWalletDesktop'; -const BACKGROUND_DEFAULT = 'radial-gradient(103.03% 103.03% at 0% 0%, rgba(183, 148, 244, 0.8) 0%, rgba(0, 163, 196, 0.8) 100%), var(--chakra-colors-blue-400)'; +export const BACKGROUND_DEFAULT = + 'radial-gradient(103.03% 103.03% at 0% 0%, rgba(183, 148, 244, 0.8) 0%, rgba(0, 163, 196, 0.8) 100%), var(--chakra-colors-blue-400)'; const TEXT_COLOR_DEFAULT = 'white'; const BORDER_DEFAULT = 'none'; const HeroBanner = () => { - const background = useColorModeValue( - // light mode - config.UI.homepage.heroBanner?.background?.[0] || - config.UI.homepage.plate.background || - BACKGROUND_DEFAULT, - // dark mode - config.UI.homepage.heroBanner?.background?.[1] || - config.UI.homepage.heroBanner?.background?.[0] || - config.UI.homepage.plate.background || - BACKGROUND_DEFAULT, - ); + const background = { + _light: + config.UI.homepage.heroBanner?.background?.[0] || + config.UI.homepage.plate.background || + BACKGROUND_DEFAULT, + _dark: + config.UI.homepage.heroBanner?.background?.[1] || + config.UI.homepage.heroBanner?.background?.[0] || + config.UI.homepage.plate.background || + BACKGROUND_DEFAULT, + }; - const textColor = useColorModeValue( - // light mode - config.UI.homepage.heroBanner?.text_color?.[0] || - config.UI.homepage.plate.textColor || - TEXT_COLOR_DEFAULT, + const textColor = { + _light: + // light mode + config.UI.homepage.heroBanner?.text_color?.[0] || + config.UI.homepage.plate.textColor || + TEXT_COLOR_DEFAULT, // dark mode - config.UI.homepage.heroBanner?.text_color?.[1] || - config.UI.homepage.heroBanner?.text_color?.[0] || - config.UI.homepage.plate.textColor || - TEXT_COLOR_DEFAULT, - ); + _dark: + config.UI.homepage.heroBanner?.text_color?.[1] || + config.UI.homepage.heroBanner?.text_color?.[0] || + config.UI.homepage.plate.textColor || + TEXT_COLOR_DEFAULT, + }; - const border = useColorModeValue( - config.UI.homepage.heroBanner?.border?.[0] || BORDER_DEFAULT, - config.UI.homepage.heroBanner?.border?.[1] || config.UI.homepage.heroBanner?.border?.[0] || BORDER_DEFAULT, - ); + const border = { + _light: + config.UI.homepage.heroBanner?.border?.[0] || BORDER_DEFAULT, + _dark: + config.UI.homepage.heroBanner?.border?.[1] || config.UI.homepage.heroBanner?.border?.[0] || BORDER_DEFAULT, + }; return ( { placeholderData: Array(blocksMaxCount).fill(BLOCK), }, }); + const initialList = useInitialList({ + data: data ?? [], + idFn: (block) => block.height, + enabled: !isPlaceholderData, + }); const queryClient = useQueryClient(); const statsQueryResult = useApiQuery('stats', { @@ -78,19 +84,18 @@ const LatestBlocks = () => { content = ( <> - - - { dataToShow.map(((block, index) => ( - - ))) } - + + { dataToShow.map(((block, index) => ( + + ))) } - View all blocks + View all blocks ); @@ -98,19 +103,19 @@ const LatestBlocks = () => { return ( - Latest blocks + Latest blocks { statsQueryResult.data?.network_utilization_percentage !== undefined && ( - - + + Network utilization:{ nbsp } - + { statsQueryResult.data?.network_utilization_percentage.toFixed(2) }% ) } { statsQueryResult.data?.celo && ( - + Current epoch: #{ statsQueryResult.data.celo.epoch_number } diff --git a/ui/home/LatestBlocksItem.tsx b/ui/home/LatestBlocksItem.tsx index e6767a8834..3199bf755c 100644 --- a/ui/home/LatestBlocksItem.tsx +++ b/ui/home/LatestBlocksItem.tsx @@ -1,10 +1,4 @@ -import { - Box, - Flex, - Grid, - Tooltip, -} from '@chakra-ui/react'; -import { motion } from 'framer-motion'; +import { Box, Flex, Grid } from '@chakra-ui/react'; import React from 'react'; import type { Block } from 'types/api/block'; @@ -12,7 +6,8 @@ import type { Block } from 'types/api/block'; import config from 'configs/app'; import getBlockTotalReward from 'lib/block/getBlockTotalReward'; import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import { Tooltip } from 'toolkit/chakra/tooltip'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import IconSvg from 'ui/shared/IconSvg'; @@ -21,21 +16,17 @@ import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip'; type Props = { block: Block; isLoading?: boolean; + animation?: string; }; -const LatestBlocksItem = ({ block, isLoading }: Props) => { +const LatestBlocksItem = ({ block, isLoading, animation }: Props) => { const totalReward = getBlockTotalReward(block); return ( @@ -43,13 +34,12 @@ const LatestBlocksItem = ({ block, isLoading }: Props) => { isLoading={ isLoading } number={ block.height } tailLength={ 2 } - fontSize="xl" - lineHeight={ 7 } + textStyle="xl" fontWeight={ 500 } mr="auto" /> { block.celo?.is_epoch_block && ( - + ) } @@ -57,28 +47,27 @@ const LatestBlocksItem = ({ block, isLoading }: Props) => { timestamp={ block.timestamp } enableIncrement={ !isLoading } isLoading={ isLoading } - color="text_secondary" - fontWeight={ 400 } + color="text.secondary" display="inline-block" - fontSize="sm" + textStyle="sm" flexShrink={ 0 } ml={ 2 } /> - - Txn - { block.transaction_count } + + Txn + { block.transaction_count } { !config.features.rollup.isEnabled && !config.UI.views.block.hiddenFields?.total_reward && ( <> - Reward - { totalReward.dp(10).toFixed() } + Reward + { totalReward.dp(10).toFixed() } ) } { !config.features.rollup.isEnabled && !config.UI.views.block.hiddenFields?.miner && ( <> - { getNetworkValidatorTitle() } + { getNetworkValidatorTitle() } { - View all transactions + View all transactions ); diff --git a/ui/home/LatestTxsItem.tsx b/ui/home/LatestTxsItem.tsx index 0b4d2ceef6..d9f87739e2 100644 --- a/ui/home/LatestTxsItem.tsx +++ b/ui/home/LatestTxsItem.tsx @@ -12,8 +12,8 @@ import type { Transaction } from 'types/api/transaction'; import config from 'configs/app'; import getValueWithUnit from 'lib/getValueWithUnit'; import { currencyUnits } from 'lib/units'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import AddressFromTo from 'ui/shared/address/AddressFromTo'; -import Skeleton from 'ui/shared/chakra/Skeleton'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxStatus from 'ui/shared/statusTag/TxStatus'; import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip'; @@ -40,10 +40,9 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => { gridGap={ 8 } width="100%" minW="700px" - borderTop="1px solid" - borderColor="divider" + borderBottom="1px solid" + borderColor="border.divider" p={ 4 } - _last={{ borderBottom: '1px solid', borderColor: 'divider' }} display={{ base: 'none', lg: 'grid' }} > @@ -68,9 +67,8 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => { timestamp={ tx.timestamp } enableIncrement isLoading={ isLoading } - color="text_secondary" - fontWeight="400" - fontSize="sm" + color="text.secondary" + textStyle="sm" flexShrink={ 0 } ml={ 2 } /> @@ -83,17 +81,17 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => { isLoading={ isLoading } mode="compact" /> - + { !config.UI.views.tx.hiddenFields?.value && ( - + Value - { getValueWithUnit(tx.value).dp(5).toFormat() } { currencyUnits.ether } + { getValueWithUnit(tx.value).dp(5).toFormat() } { currencyUnits.ether } ) } { !config.UI.views.tx.hiddenFields?.tx_fee && ( - + Fee - + ) } diff --git a/ui/home/LatestTxsItemMobile.tsx b/ui/home/LatestTxsItemMobile.tsx index efbe0e05ef..593d26a6a1 100644 --- a/ui/home/LatestTxsItemMobile.tsx +++ b/ui/home/LatestTxsItemMobile.tsx @@ -11,8 +11,8 @@ import type { Transaction } from 'types/api/transaction'; import config from 'configs/app'; import getValueWithUnit from 'lib/getValueWithUnit'; import { currencyUnits } from 'lib/units'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import AddressFromTo from 'ui/shared/address/AddressFromTo'; -import Skeleton from 'ui/shared/chakra/Skeleton'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxStatus from 'ui/shared/statusTag/TxStatus'; import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip'; @@ -32,10 +32,9 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => { return ( @@ -63,7 +62,7 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => { timestamp={ tx.timestamp } enableIncrement isLoading={ isLoading } - color="text_secondary" + color="text.secondary" fontWeight="400" fontSize="sm" ml={ 3 } @@ -78,15 +77,15 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => { mb={ 3 } /> { !config.UI.views.tx.hiddenFields?.value && ( - + Value - { getValueWithUnit(tx.value).dp(5).toFormat() } { currencyUnits.ether } + { getValueWithUnit(tx.value).dp(5).toFormat() } { currencyUnits.ether } ) } { !config.UI.views.tx.hiddenFields?.tx_fee && ( - + Fee - + ) } diff --git a/ui/home/LatestWatchlistTxs.tsx b/ui/home/LatestWatchlistTxs.tsx index 3bf82839c4..9f66712985 100644 --- a/ui/home/LatestWatchlistTxs.tsx +++ b/ui/home/LatestWatchlistTxs.tsx @@ -6,7 +6,7 @@ import { route } from 'nextjs-routes'; import useApiQuery from 'lib/api/useApiQuery'; import useIsMobile from 'lib/hooks/useIsMobile'; import { TX } from 'stubs/tx'; -import LinkInternal from 'ui/shared/links/LinkInternal'; +import { Link } from 'toolkit/chakra/link'; import useRedirectForInvalidAuthToken from 'ui/snippets/auth/useRedirectForInvalidAuthToken'; import LatestTxsItem from './LatestTxsItem'; @@ -53,7 +53,7 @@ const LatestWatchlistTxs = () => { ))) } - View all watch list transactions + View all watch list transactions ); diff --git a/ui/home/Stats.tsx b/ui/home/Stats.tsx index 5ce4e3cc81..cb0b32ef6c 100644 --- a/ui/home/Stats.tsx +++ b/ui/home/Stats.tsx @@ -108,8 +108,8 @@ const Stats = () => { boxSize={ 5 } flexShrink={ 0 } cursor="pointer" - color="icon_info" - _hover={{ color: 'link_hovered' }} + color="icon.info" + _hover={{ color: 'link.primary.hove' }} /> ) : null; diff --git a/ui/home/Transactions.tsx b/ui/home/Transactions.tsx index db24cb4a51..c5ee6f1ec2 100644 --- a/ui/home/Transactions.tsx +++ b/ui/home/Transactions.tsx @@ -1,21 +1,17 @@ -import { Heading } from '@chakra-ui/react'; import React from 'react'; import config from 'configs/app'; +import { Heading } from 'toolkit/chakra/heading'; +import AdaptiveTabs from 'toolkit/components/AdaptiveTabs/AdaptiveTabs'; import LatestOptimisticDeposits from 'ui/home/latestDeposits/LatestOptimisticDeposits'; import LatestTxs from 'ui/home/LatestTxs'; import LatestWatchlistTxs from 'ui/home/LatestWatchlistTxs'; -import TabsWithScroll from 'ui/shared/Tabs/TabsWithScroll'; import useAuth from 'ui/snippets/auth/useIsAuth'; import LatestArbitrumDeposits from './latestDeposits/LatestArbitrumDeposits'; const rollupFeature = config.features.rollup; -const TAB_LIST_PROPS = { - mb: { base: 3, lg: 3 }, -}; - const TransactionsHome = () => { const isAuth = useAuth(); if ((rollupFeature.isEnabled && (rollupFeature.type === 'optimistic' || rollupFeature.type === 'arbitrum')) || isAuth) { @@ -29,15 +25,15 @@ const TransactionsHome = () => { ].filter(Boolean); return ( <> - Transactions - + Transactions + ); } return ( <> - Latest transactions + Latest transactions ); diff --git a/ui/home/__screenshots__/HeroBanner.pw.tsx_dark-color-mode_customization-dark-mode-1.png b/ui/home/__screenshots__/HeroBanner.pw.tsx_dark-color-mode_customization-dark-mode-1.png index eeec90574b..c0226017d9 100644 Binary files a/ui/home/__screenshots__/HeroBanner.pw.tsx_dark-color-mode_customization-dark-mode-1.png and b/ui/home/__screenshots__/HeroBanner.pw.tsx_dark-color-mode_customization-dark-mode-1.png differ diff --git a/ui/home/__screenshots__/HeroBanner.pw.tsx_default_customization-dark-mode-1.png b/ui/home/__screenshots__/HeroBanner.pw.tsx_default_customization-dark-mode-1.png index 217ab92bbb..297071f2df 100644 Binary files a/ui/home/__screenshots__/HeroBanner.pw.tsx_default_customization-dark-mode-1.png and b/ui/home/__screenshots__/HeroBanner.pw.tsx_default_customization-dark-mode-1.png differ diff --git a/ui/home/__screenshots__/LatestBlocks.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png b/ui/home/__screenshots__/LatestBlocks.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png index 20e3e98677..1fa34c6102 100644 Binary files a/ui/home/__screenshots__/LatestBlocks.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png and b/ui/home/__screenshots__/LatestBlocks.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png differ diff --git a/ui/home/__screenshots__/LatestBlocks.pw.tsx_default_L2-view-1.png b/ui/home/__screenshots__/LatestBlocks.pw.tsx_default_L2-view-1.png index 3148f0ec9b..7e50340bdb 100644 Binary files a/ui/home/__screenshots__/LatestBlocks.pw.tsx_default_L2-view-1.png and b/ui/home/__screenshots__/LatestBlocks.pw.tsx_default_L2-view-1.png differ diff --git a/ui/home/__screenshots__/LatestBlocks.pw.tsx_default_default-view-mobile-dark-mode-1.png b/ui/home/__screenshots__/LatestBlocks.pw.tsx_default_default-view-mobile-dark-mode-1.png index 65fb3d8762..16e2dbf320 100644 Binary files a/ui/home/__screenshots__/LatestBlocks.pw.tsx_default_default-view-mobile-dark-mode-1.png and b/ui/home/__screenshots__/LatestBlocks.pw.tsx_default_default-view-mobile-dark-mode-1.png differ diff --git a/ui/home/__screenshots__/LatestBlocks.pw.tsx_default_no-reward-view-1.png b/ui/home/__screenshots__/LatestBlocks.pw.tsx_default_no-reward-view-1.png index 0fe24b83ee..1eb37f403a 100644 Binary files a/ui/home/__screenshots__/LatestBlocks.pw.tsx_default_no-reward-view-1.png and b/ui/home/__screenshots__/LatestBlocks.pw.tsx_default_no-reward-view-1.png differ diff --git a/ui/home/__screenshots__/LatestBlocks.pw.tsx_default_socket-new-item-1.png b/ui/home/__screenshots__/LatestBlocks.pw.tsx_default_socket-new-item-1.png index 59ba3dcdfd..f44bcdcd97 100644 Binary files a/ui/home/__screenshots__/LatestBlocks.pw.tsx_default_socket-new-item-1.png and b/ui/home/__screenshots__/LatestBlocks.pw.tsx_default_socket-new-item-1.png differ diff --git a/ui/home/__screenshots__/LatestBlocks.pw.tsx_default_with-long-block-height-1.png b/ui/home/__screenshots__/LatestBlocks.pw.tsx_default_with-long-block-height-1.png index 90639faa57..628ce4c932 100644 Binary files a/ui/home/__screenshots__/LatestBlocks.pw.tsx_default_with-long-block-height-1.png and b/ui/home/__screenshots__/LatestBlocks.pw.tsx_default_with-long-block-height-1.png differ diff --git a/ui/home/__screenshots__/LatestBlocks.pw.tsx_mobile_default-view-mobile-dark-mode-1.png b/ui/home/__screenshots__/LatestBlocks.pw.tsx_mobile_default-view-mobile-dark-mode-1.png index c0a9bc8dbe..231bf3dee4 100644 Binary files a/ui/home/__screenshots__/LatestBlocks.pw.tsx_mobile_default-view-mobile-dark-mode-1.png and b/ui/home/__screenshots__/LatestBlocks.pw.tsx_mobile_default-view-mobile-dark-mode-1.png differ diff --git a/ui/home/__screenshots__/LatestTxs.pw.tsx_dark-color-mode_default-view-dark-mode-1.png b/ui/home/__screenshots__/LatestTxs.pw.tsx_dark-color-mode_default-view-dark-mode-1.png index d1f03023e8..de595491ed 100644 Binary files a/ui/home/__screenshots__/LatestTxs.pw.tsx_dark-color-mode_default-view-dark-mode-1.png and b/ui/home/__screenshots__/LatestTxs.pw.tsx_dark-color-mode_default-view-dark-mode-1.png differ diff --git a/ui/home/__screenshots__/LatestTxs.pw.tsx_default_default-view-dark-mode-1.png b/ui/home/__screenshots__/LatestTxs.pw.tsx_default_default-view-dark-mode-1.png index e1704af270..87a5d0ae09 100644 Binary files a/ui/home/__screenshots__/LatestTxs.pw.tsx_default_default-view-dark-mode-1.png and b/ui/home/__screenshots__/LatestTxs.pw.tsx_default_default-view-dark-mode-1.png differ diff --git a/ui/home/__screenshots__/LatestTxs.pw.tsx_default_mobile-default-view-1.png b/ui/home/__screenshots__/LatestTxs.pw.tsx_default_mobile-default-view-1.png index bf31071943..05da4d4a1c 100644 Binary files a/ui/home/__screenshots__/LatestTxs.pw.tsx_default_mobile-default-view-1.png and b/ui/home/__screenshots__/LatestTxs.pw.tsx_default_mobile-default-view-1.png differ diff --git a/ui/home/__screenshots__/LatestTxs.pw.tsx_default_socket-new-item-1.png b/ui/home/__screenshots__/LatestTxs.pw.tsx_default_socket-new-item-1.png index a1adf26f3d..2a13ae6673 100644 Binary files a/ui/home/__screenshots__/LatestTxs.pw.tsx_default_socket-new-item-1.png and b/ui/home/__screenshots__/LatestTxs.pw.tsx_default_socket-new-item-1.png differ diff --git a/ui/home/__screenshots__/Stats.pw.tsx_dark-color-mode_all-items-mobile-dark-mode-1.png b/ui/home/__screenshots__/Stats.pw.tsx_dark-color-mode_all-items-mobile-dark-mode-1.png index 464ccaa58e..412f09805a 100644 Binary files a/ui/home/__screenshots__/Stats.pw.tsx_dark-color-mode_all-items-mobile-dark-mode-1.png and b/ui/home/__screenshots__/Stats.pw.tsx_dark-color-mode_all-items-mobile-dark-mode-1.png differ diff --git a/ui/home/__screenshots__/Stats.pw.tsx_default_all-items-mobile-dark-mode-1.png b/ui/home/__screenshots__/Stats.pw.tsx_default_all-items-mobile-dark-mode-1.png index 10eb185b93..aa958cd05f 100644 Binary files a/ui/home/__screenshots__/Stats.pw.tsx_default_all-items-mobile-dark-mode-1.png and b/ui/home/__screenshots__/Stats.pw.tsx_default_all-items-mobile-dark-mode-1.png differ diff --git a/ui/home/__screenshots__/Stats.pw.tsx_default_no-gas-info-1.png b/ui/home/__screenshots__/Stats.pw.tsx_default_no-gas-info-1.png index 416a5c300f..d6700b5454 100644 Binary files a/ui/home/__screenshots__/Stats.pw.tsx_default_no-gas-info-1.png and b/ui/home/__screenshots__/Stats.pw.tsx_default_no-gas-info-1.png differ diff --git a/ui/home/__screenshots__/Stats.pw.tsx_mobile_3-items-default-view-mobile---default-1.png b/ui/home/__screenshots__/Stats.pw.tsx_mobile_3-items-default-view-mobile---default-1.png index 7e00bc0f16..47c9ba61df 100644 Binary files a/ui/home/__screenshots__/Stats.pw.tsx_mobile_3-items-default-view-mobile---default-1.png and b/ui/home/__screenshots__/Stats.pw.tsx_mobile_3-items-default-view-mobile---default-1.png differ diff --git a/ui/home/__screenshots__/Stats.pw.tsx_mobile_4-items-default-view-mobile---default-1.png b/ui/home/__screenshots__/Stats.pw.tsx_mobile_4-items-default-view-mobile---default-1.png index 82e4e121e2..2fc03395d6 100644 Binary files a/ui/home/__screenshots__/Stats.pw.tsx_mobile_4-items-default-view-mobile---default-1.png and b/ui/home/__screenshots__/Stats.pw.tsx_mobile_4-items-default-view-mobile---default-1.png differ diff --git a/ui/home/__screenshots__/Stats.pw.tsx_mobile_all-items-mobile-dark-mode-1.png b/ui/home/__screenshots__/Stats.pw.tsx_mobile_all-items-mobile-dark-mode-1.png index e05fee1820..6fddb04f17 100644 Binary files a/ui/home/__screenshots__/Stats.pw.tsx_mobile_all-items-mobile-dark-mode-1.png and b/ui/home/__screenshots__/Stats.pw.tsx_mobile_all-items-mobile-dark-mode-1.png differ diff --git a/ui/home/indicators/ChainIndicatorChartContainer.tsx b/ui/home/indicators/ChainIndicatorChartContainer.tsx index a435aa16f0..3b58af2ccd 100644 --- a/ui/home/indicators/ChainIndicatorChartContainer.tsx +++ b/ui/home/indicators/ChainIndicatorChartContainer.tsx @@ -21,7 +21,7 @@ const ChainIndicatorChartContainer = ({ data, isError, isPending }: Props) => { } if (isError) { - return ; + return ; } if (data[0].items.length === 0) { diff --git a/ui/home/indicators/ChainIndicatorChartContent.tsx b/ui/home/indicators/ChainIndicatorChartContent.tsx index a167099170..40793dd949 100644 --- a/ui/home/indicators/ChainIndicatorChartContent.tsx +++ b/ui/home/indicators/ChainIndicatorChartContent.tsx @@ -45,7 +45,7 @@ const ChainIndicatorChartContent = ({ data }: Props) => { data={ data[0].items } xScale={ axes.x.scale } yScale={ axes.y.scale } - stroke={ lineColor } + stroke={ lineColor[0] } animation="left" strokeWidth={ 3 } /> diff --git a/ui/home/indicators/ChainIndicatorItem.tsx b/ui/home/indicators/ChainIndicatorItem.tsx index 13e0da03e9..184841f001 100644 --- a/ui/home/indicators/ChainIndicatorItem.tsx +++ b/ui/home/indicators/ChainIndicatorItem.tsx @@ -1,9 +1,9 @@ -import { Text, Flex, Box, useColorModeValue } from '@chakra-ui/react'; +import { Text, Flex, Box } from '@chakra-ui/react'; import React from 'react'; import type { ChainIndicatorId } from 'types/homepage'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Skeleton } from 'toolkit/chakra/skeleton'; interface Props { id: ChainIndicatorId; @@ -18,20 +18,17 @@ interface Props { } const ChainIndicatorItem = ({ id, title, value, valueDiff, icon, isSelected, onClick, isLoading, hasData }: Props) => { - const activeColor = useColorModeValue('gray.500', 'gray.400'); - const activeBgColor = useColorModeValue('white', 'black'); - const handleClick = React.useCallback(() => { onClick(id); }, [ id, onClick ]); const valueContent = (() => { if (!hasData) { - return no data; + return no data; } return ( - + { value } ); @@ -45,7 +42,7 @@ const ChainIndicatorItem = ({ id, title, value, valueDiff, icon, isSelected, onC const diffColor = valueDiff >= 0 ? 'green.500' : 'red.500'; return ( - + { valueDiff >= 0 ? '+' : '-' } { Math.abs(valueDiff) }% @@ -62,14 +59,14 @@ const ChainIndicatorItem = ({ id, title, value, valueDiff, icon, isSelected, onC as="li" borderRadius="base" cursor="pointer" - color={ isSelected ? activeColor : 'link' } - bgColor={ isSelected ? activeBgColor : undefined } + color={ isSelected ? { _light: 'gray.500', _dark: 'gray.400' } : 'link' } + bgColor={ isSelected ? { _light: 'white', _dark: 'black' } : undefined } onClick={ handleClick } fontSize="xs" fontWeight={ 500 } _hover={{ - bgColor: activeBgColor, - color: isSelected ? activeColor : 'link_hovered', + bgColor: { _light: 'white', _dark: 'black' }, + color: isSelected ? { _light: 'gray.500', _dark: 'gray.400' } : 'link.primary.hover', zIndex: 1, }} > diff --git a/ui/home/indicators/ChainIndicators.tsx b/ui/home/indicators/ChainIndicators.tsx index c9be478dc7..84949333d7 100644 --- a/ui/home/indicators/ChainIndicators.tsx +++ b/ui/home/indicators/ChainIndicators.tsx @@ -1,4 +1,4 @@ -import { Flex, Text, useColorModeValue } from '@chakra-ui/react'; +import { Flex, Text } from '@chakra-ui/react'; import React from 'react'; import type { TChainIndicator } from './types'; @@ -7,7 +7,7 @@ import type { ChainIndicatorId } from 'types/homepage'; import config from 'configs/app'; import useApiQuery from 'lib/api/useApiQuery'; import { HOMEPAGE_STATS, HOMEPAGE_STATS_MICROSERVICE } from 'stubs/stats'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import Hint from 'ui/shared/Hint'; import IconSvg from 'ui/shared/IconSvg'; @@ -54,8 +54,6 @@ const ChainIndicators = () => { }, }); - const bgColor = useColorModeValue('gray.50', 'whiteAlpha.100'); - if (indicators.length === 0) { return null; } @@ -86,7 +84,7 @@ const ChainIndicators = () => { const valueTitle = (() => { if (isPlaceholderData) { - return ; + return ; } if (!hasData) { @@ -108,7 +106,7 @@ const ChainIndicators = () => { const diffColor = indicatorValueDiff >= 0 ? 'green.500' : 'red.500'; return ( - + { indicatorValueDiff }% @@ -120,7 +118,7 @@ const ChainIndicators = () => { px={{ base: 3, lg: 4 }} py={ 3 } borderRadius="base" - bgColor={ bgColor } + bgColor={{ _light: 'gray.50', _dark: 'whiteAlpha.100' }} columnGap={{ base: 3, lg: 4 }} rowGap={ 0 } flexBasis="50%" diff --git a/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_daily-txs-chart-dark-mode-mobile-1.png b/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_daily-txs-chart-dark-mode-mobile-1.png index 18681ae3ab..b29120f39e 100644 Binary files a/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_daily-txs-chart-dark-mode-mobile-1.png and b/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_daily-txs-chart-dark-mode-mobile-1.png differ diff --git a/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_daily-txs-chart-mobile-1.png b/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_daily-txs-chart-mobile-1.png index 8f5be09bda..026683c166 100644 Binary files a/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_daily-txs-chart-mobile-1.png and b/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_daily-txs-chart-mobile-1.png differ diff --git a/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_no-data-1.png b/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_no-data-1.png index c05ea45530..a5169b8cc3 100644 Binary files a/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_no-data-1.png and b/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_no-data-1.png differ diff --git a/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_partial-data-1.png b/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_partial-data-1.png index 35e0cb1e6a..2419fd7970 100644 Binary files a/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_partial-data-1.png and b/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_partial-data-1.png differ diff --git a/ui/home/latestBatches/LatestArbitrumL2Batches.tsx b/ui/home/latestBatches/LatestArbitrumL2Batches.tsx index 46e8495acf..44014b5865 100644 --- a/ui/home/latestBatches/LatestArbitrumL2Batches.tsx +++ b/ui/home/latestBatches/LatestArbitrumL2Batches.tsx @@ -1,6 +1,6 @@ -import { Box, Heading, Flex, Text, VStack } from '@chakra-ui/react'; +import { Box, Flex, Text, VStack } from '@chakra-ui/react'; import { useQueryClient } from '@tanstack/react-query'; -import { AnimatePresence } from 'framer-motion'; +// import { AnimatePresence } from 'framer-motion'; import React from 'react'; import type { SocketMessage } from 'lib/socket/types'; @@ -9,11 +9,13 @@ import type { ArbitrumL2TxnBatchesItem } from 'types/api/arbitrumL2'; import { route } from 'nextjs-routes'; import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery'; +import useInitialList from 'lib/hooks/useInitialList'; import useIsMobile from 'lib/hooks/useIsMobile'; import useSocketChannel from 'lib/socket/useSocketChannel'; import useSocketMessage from 'lib/socket/useSocketMessage'; import { ARBITRUM_L2_TXN_BATCHES_ITEM } from 'stubs/arbitrumL2'; -import LinkInternal from 'ui/shared/links/LinkInternal'; +import { Heading } from 'toolkit/chakra/heading'; +import { Link } from 'toolkit/chakra/link'; import LatestBatchItem from './LatestBatchItem'; @@ -28,6 +30,12 @@ const LatestArbitrumL2Batches = () => { }, }); + const initialList = useInitialList({ + data: data?.items ?? [], + idFn: (batch) => batch.number, + enabled: !isPlaceholderData, + }); + const handleNewBatchMessage: SocketMessage.NewArbitrumL2Batch['handler'] = React.useCallback((payload) => { queryClient.setQueryData(getResourceKey('homepage_arbitrum_l2_batches'), (prevData: { items: Array } | undefined) => { const newItems = prevData?.items ? [ ...prevData.items ] : []; @@ -61,21 +69,20 @@ const LatestArbitrumL2Batches = () => { content = ( <> - - - { dataToShow.map(((batch, index) => ( - - ))) } - + + { dataToShow.map(((batch, index) => ( + + ))) } - View all batches + View all batches ); @@ -83,7 +90,7 @@ const LatestArbitrumL2Batches = () => { return ( - Latest batches + Latest batches { content } ); diff --git a/ui/home/latestBatches/LatestBatchItem.tsx b/ui/home/latestBatches/LatestBatchItem.tsx index b0b15922ca..baea846c8e 100644 --- a/ui/home/latestBatches/LatestBatchItem.tsx +++ b/ui/home/latestBatches/LatestBatchItem.tsx @@ -1,15 +1,11 @@ -import { - Box, - Flex, -} from '@chakra-ui/react'; -import { motion } from 'framer-motion'; +import { Box, Flex } from '@chakra-ui/react'; import React from 'react'; import { route } from 'nextjs-routes'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Link } from 'toolkit/chakra/link'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import BatchEntityL2 from 'ui/shared/entities/block/BatchEntityL2'; -import LinkInternal from 'ui/shared/links/LinkInternal'; import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip'; type Props = { @@ -18,20 +14,16 @@ type Props = { txCount: number; status?: React.ReactNode; isLoading: boolean; + animation?: string; }; -const LatestBatchItem = ({ number, timestamp, txCount, status, isLoading }: Props) => { +const LatestBatchItem = ({ number, timestamp, txCount, status, isLoading, animation }: Props) => { return ( @@ -39,8 +31,7 @@ const LatestBatchItem = ({ number, timestamp, txCount, status, isLoading }: Prop isLoading={ isLoading } number={ number } tailLength={ 2 } - fontSize="xl" - lineHeight={ 7 } + textStyle="xl" fontWeight={ 500 } mr="auto" /> @@ -48,25 +39,22 @@ const LatestBatchItem = ({ number, timestamp, txCount, status, isLoading }: Prop timestamp={ timestamp } enableIncrement={ !isLoading } isLoading={ isLoading } - color="text_secondary" - fontWeight={ 400 } + color="text.secondary" display="inline-block" - fontSize="sm" + textStyle="sm" flexShrink={ 0 } ml={ 2 } /> - Txn - Txn + - - { txCount } - - + { txCount } + { status } diff --git a/ui/home/latestBatches/LatestZkEvmL2Batches.tsx b/ui/home/latestBatches/LatestZkEvmL2Batches.tsx index 7297194dd2..4afa894827 100644 --- a/ui/home/latestBatches/LatestZkEvmL2Batches.tsx +++ b/ui/home/latestBatches/LatestZkEvmL2Batches.tsx @@ -1,6 +1,5 @@ -import { Box, Heading, Flex, Text, VStack } from '@chakra-ui/react'; +import { Box, Flex, Text, VStack } from '@chakra-ui/react'; import { useQueryClient } from '@tanstack/react-query'; -import { AnimatePresence } from 'framer-motion'; import React from 'react'; import type { SocketMessage } from 'lib/socket/types'; @@ -9,11 +8,13 @@ import type { ZkEvmL2TxnBatchesItem } from 'types/api/zkEvmL2'; import { route } from 'nextjs-routes'; import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery'; +import useInitialList from 'lib/hooks/useInitialList'; import useIsMobile from 'lib/hooks/useIsMobile'; import useSocketChannel from 'lib/socket/useSocketChannel'; import useSocketMessage from 'lib/socket/useSocketMessage'; import { ZKEVM_L2_TXN_BATCHES_ITEM } from 'stubs/zkEvmL2'; -import LinkInternal from 'ui/shared/links/LinkInternal'; +import { Heading } from 'toolkit/chakra/heading'; +import { Link } from 'toolkit/chakra/link'; import ZkEvmL2TxnBatchStatus from 'ui/shared/statusTag/ZkEvmL2TxnBatchStatus'; import LatestBatchItem from './LatestBatchItem'; @@ -29,6 +30,12 @@ const LatestZkEvmL2Batches = () => { }, }); + const initialList = useInitialList({ + data: data?.items ?? [], + idFn: (batch) => batch.number, + enabled: !isPlaceholderData, + }); + const handleNewBatchMessage: SocketMessage.NewZkEvmL2Batch['handler'] = React.useCallback((payload) => { queryClient.setQueryData(getResourceKey('homepage_zkevm_l2_batches'), (prevData: { items: Array } | undefined) => { const newItems = prevData?.items ? [ ...prevData.items ] : []; @@ -62,25 +69,24 @@ const LatestZkEvmL2Batches = () => { content = ( <> - - - { dataToShow.map(((batch, index) => { - const status = ; - return ( - - ); - })) } - + + { dataToShow.map(((batch, index) => { + const status = ; + return ( + + ); + })) } - View all batches + View all batches ); @@ -88,7 +94,7 @@ const LatestZkEvmL2Batches = () => { return ( - Latest batches + Latest batches { content } ); diff --git a/ui/home/latestBatches/__screenshots__/LatestArbitrumL2Batches.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png b/ui/home/latestBatches/__screenshots__/LatestArbitrumL2Batches.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png index 6c14b1a75e..b1c5d1a93c 100644 Binary files a/ui/home/latestBatches/__screenshots__/LatestArbitrumL2Batches.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png and b/ui/home/latestBatches/__screenshots__/LatestArbitrumL2Batches.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png differ diff --git a/ui/home/latestBatches/__screenshots__/LatestArbitrumL2Batches.pw.tsx_default_default-view-mobile-dark-mode-1.png b/ui/home/latestBatches/__screenshots__/LatestArbitrumL2Batches.pw.tsx_default_default-view-mobile-dark-mode-1.png index e9356f9f9b..ae139a47db 100644 Binary files a/ui/home/latestBatches/__screenshots__/LatestArbitrumL2Batches.pw.tsx_default_default-view-mobile-dark-mode-1.png and b/ui/home/latestBatches/__screenshots__/LatestArbitrumL2Batches.pw.tsx_default_default-view-mobile-dark-mode-1.png differ diff --git a/ui/home/latestBatches/__screenshots__/LatestArbitrumL2Batches.pw.tsx_mobile_default-view-mobile-dark-mode-1.png b/ui/home/latestBatches/__screenshots__/LatestArbitrumL2Batches.pw.tsx_mobile_default-view-mobile-dark-mode-1.png index 10933d4d87..712875aece 100644 Binary files a/ui/home/latestBatches/__screenshots__/LatestArbitrumL2Batches.pw.tsx_mobile_default-view-mobile-dark-mode-1.png and b/ui/home/latestBatches/__screenshots__/LatestArbitrumL2Batches.pw.tsx_mobile_default-view-mobile-dark-mode-1.png differ diff --git a/ui/home/latestBatches/__screenshots__/LatestZkEvmL2Batches.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png b/ui/home/latestBatches/__screenshots__/LatestZkEvmL2Batches.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png index 50a9ff5683..5989029fc2 100644 Binary files a/ui/home/latestBatches/__screenshots__/LatestZkEvmL2Batches.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png and b/ui/home/latestBatches/__screenshots__/LatestZkEvmL2Batches.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png differ diff --git a/ui/home/latestBatches/__screenshots__/LatestZkEvmL2Batches.pw.tsx_default_default-view-mobile-dark-mode-1.png b/ui/home/latestBatches/__screenshots__/LatestZkEvmL2Batches.pw.tsx_default_default-view-mobile-dark-mode-1.png index 29f95e9727..4ada20a031 100644 Binary files a/ui/home/latestBatches/__screenshots__/LatestZkEvmL2Batches.pw.tsx_default_default-view-mobile-dark-mode-1.png and b/ui/home/latestBatches/__screenshots__/LatestZkEvmL2Batches.pw.tsx_default_default-view-mobile-dark-mode-1.png differ diff --git a/ui/home/latestBatches/__screenshots__/LatestZkEvmL2Batches.pw.tsx_mobile_default-view-mobile-dark-mode-1.png b/ui/home/latestBatches/__screenshots__/LatestZkEvmL2Batches.pw.tsx_mobile_default-view-mobile-dark-mode-1.png index f56ca0ab53..b5037263ac 100644 Binary files a/ui/home/latestBatches/__screenshots__/LatestZkEvmL2Batches.pw.tsx_mobile_default-view-mobile-dark-mode-1.png and b/ui/home/latestBatches/__screenshots__/LatestZkEvmL2Batches.pw.tsx_mobile_default-view-mobile-dark-mode-1.png differ diff --git a/ui/home/latestDeposits/LatestDeposits.tsx b/ui/home/latestDeposits/LatestDeposits.tsx index e6ba00bc95..bdaf979b6d 100644 --- a/ui/home/latestDeposits/LatestDeposits.tsx +++ b/ui/home/latestDeposits/LatestDeposits.tsx @@ -9,11 +9,11 @@ import React from 'react'; import { route } from 'nextjs-routes'; import useIsMobile from 'lib/hooks/useIsMobile'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Link } from 'toolkit/chakra/link'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1'; -import LinkInternal from 'ui/shared/links/LinkInternal'; import SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice'; import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip'; @@ -43,16 +43,14 @@ const LatestDepositsItem = ({ item, isLoading }: ItemProps) => { ) : ( @@ -62,16 +60,14 @@ const LatestDepositsItem = ({ item, isLoading }: ItemProps) => { ) : ( @@ -81,8 +77,7 @@ const LatestDepositsItem = ({ item, isLoading }: ItemProps) => { ); @@ -97,16 +92,16 @@ const LatestDepositsItem = ({ item, isLoading }: ItemProps) => { ) : } - + L1 txn { l1TxLink } - + L2 txn { l2TxLink } @@ -118,7 +113,7 @@ const LatestDepositsItem = ({ item, isLoading }: ItemProps) => { return ( { l1BlockLink } - + L1 txn { l1TxLink } @@ -126,13 +121,13 @@ const LatestDepositsItem = ({ item, isLoading }: ItemProps) => { ) : } - + L2 txn { l2TxLink } @@ -143,13 +138,11 @@ const LatestDepositsItem = ({ item, isLoading }: ItemProps) => { return ( { content } @@ -171,7 +164,7 @@ const LatestDeposits = ({ isLoading, items, socketAlert, socketItemsNum }: Props ))) } - View all deposits + View all deposits ); diff --git a/ui/home/latestDeposits/LatestDepositsItem.tsx b/ui/home/latestDeposits/LatestDepositsItem.tsx index eeef1bfd31..8913df8da1 100644 --- a/ui/home/latestDeposits/LatestDepositsItem.tsx +++ b/ui/home/latestDeposits/LatestDepositsItem.tsx @@ -6,7 +6,7 @@ import { import React from 'react'; import useIsMobile from 'lib/hooks/useIsMobile'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1'; @@ -62,15 +62,15 @@ const LatestDepositsItem = ({ l1BlockNumber, l1TxHash, l2TxHash, timestamp, isLo - + L1 txn { l1TxLink } - + L2 txn { l2TxLink } @@ -82,19 +82,19 @@ const LatestDepositsItem = ({ l1BlockNumber, l1TxHash, l2TxHash, timestamp, isLo return ( { l1BlockLink } - + L1 txn { l1TxLink } - + L2 txn { l2TxLink } @@ -106,10 +106,10 @@ const LatestDepositsItem = ({ l1BlockNumber, l1TxHash, l2TxHash, timestamp, isLo diff --git a/ui/home/latestDeposits/LatestOptimisticDeposits.tsx b/ui/home/latestDeposits/LatestOptimisticDeposits.tsx index 89ffb53796..68396618a8 100644 --- a/ui/home/latestDeposits/LatestOptimisticDeposits.tsx +++ b/ui/home/latestDeposits/LatestOptimisticDeposits.tsx @@ -37,7 +37,7 @@ const LatestOptimisticDeposits = () => { }, [ setNum ]); const channel = useSocketChannel({ - topic: 'optimism_deposits:new_deposits', + topic: 'optimism:new_deposits', onSocketClose: handleSocketClose, onSocketError: handleSocketError, isDisabled: false, @@ -45,7 +45,7 @@ const LatestOptimisticDeposits = () => { useSocketMessage({ channel, - event: 'deposits', + event: 'new_optimism_deposits', handler: handleNewDepositMessage, }); diff --git a/ui/home/latestDeposits/__screenshots__/LatestArbitrumDeposits.pw.tsx_default_default-view-mobile-1.png b/ui/home/latestDeposits/__screenshots__/LatestArbitrumDeposits.pw.tsx_default_default-view-mobile-1.png index 02e8a3b6b0..88b0eb80bb 100644 Binary files a/ui/home/latestDeposits/__screenshots__/LatestArbitrumDeposits.pw.tsx_default_default-view-mobile-1.png and b/ui/home/latestDeposits/__screenshots__/LatestArbitrumDeposits.pw.tsx_default_default-view-mobile-1.png differ diff --git a/ui/home/latestDeposits/__screenshots__/LatestArbitrumDeposits.pw.tsx_mobile_default-view-mobile-1.png b/ui/home/latestDeposits/__screenshots__/LatestArbitrumDeposits.pw.tsx_mobile_default-view-mobile-1.png index 950b31fc77..d278807054 100644 Binary files a/ui/home/latestDeposits/__screenshots__/LatestArbitrumDeposits.pw.tsx_mobile_default-view-mobile-1.png and b/ui/home/latestDeposits/__screenshots__/LatestArbitrumDeposits.pw.tsx_mobile_default-view-mobile-1.png differ diff --git a/ui/home/latestDeposits/__screenshots__/LatestOptimisticDeposits.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png b/ui/home/latestDeposits/__screenshots__/LatestOptimisticDeposits.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png index e5dc4af0d5..6bb2e604f0 100644 Binary files a/ui/home/latestDeposits/__screenshots__/LatestOptimisticDeposits.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png and b/ui/home/latestDeposits/__screenshots__/LatestOptimisticDeposits.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png differ diff --git a/ui/home/latestDeposits/__screenshots__/LatestOptimisticDeposits.pw.tsx_default_default-view-mobile-dark-mode-1.png b/ui/home/latestDeposits/__screenshots__/LatestOptimisticDeposits.pw.tsx_default_default-view-mobile-dark-mode-1.png index 5e69e9d56d..706b3811dd 100644 Binary files a/ui/home/latestDeposits/__screenshots__/LatestOptimisticDeposits.pw.tsx_default_default-view-mobile-dark-mode-1.png and b/ui/home/latestDeposits/__screenshots__/LatestOptimisticDeposits.pw.tsx_default_default-view-mobile-dark-mode-1.png differ diff --git a/ui/home/latestDeposits/__screenshots__/LatestOptimisticDeposits.pw.tsx_mobile_default-view-mobile-dark-mode-1.png b/ui/home/latestDeposits/__screenshots__/LatestOptimisticDeposits.pw.tsx_mobile_default-view-mobile-dark-mode-1.png index 89c27ec008..6b5dca283f 100644 Binary files a/ui/home/latestDeposits/__screenshots__/LatestOptimisticDeposits.pw.tsx_mobile_default-view-mobile-dark-mode-1.png and b/ui/home/latestDeposits/__screenshots__/LatestOptimisticDeposits.pw.tsx_mobile_default-view-mobile-dark-mode-1.png differ diff --git a/ui/internalTxs/InternalTxsListItem.tsx b/ui/internalTxs/InternalTxsListItem.tsx index 258b5a9292..50f22c8688 100644 --- a/ui/internalTxs/InternalTxsListItem.tsx +++ b/ui/internalTxs/InternalTxsListItem.tsx @@ -6,9 +6,9 @@ import type { InternalTransaction } from 'types/api/internalTransaction'; import config from 'configs/app'; import { currencyUnits } from 'lib/units'; +import { Badge } from 'toolkit/chakra/badge'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import AddressFromTo from 'ui/shared/address/AddressFromTo'; -import Skeleton from 'ui/shared/chakra/Skeleton'; -import Tag from 'ui/shared/chakra/Tag'; import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; @@ -38,7 +38,7 @@ const InternalTxsListItem = ({ return ( - { typeTitle && { typeTitle } } + { typeTitle && { typeTitle } } @@ -51,19 +51,18 @@ const InternalTxsListItem = ({ - - Block + + Block - - Value { currencyUnits.ether } - + + Value { currencyUnits.ether } + { BigNumber(value).div(BigNumber(10 ** config.chain.currency.decimals)).toFormat() } diff --git a/ui/internalTxs/InternalTxsTable.tsx b/ui/internalTxs/InternalTxsTable.tsx index b833d683b0..c96143d164 100644 --- a/ui/internalTxs/InternalTxsTable.tsx +++ b/ui/internalTxs/InternalTxsTable.tsx @@ -1,11 +1,10 @@ -import { Table, Tbody, Tr, Th } from '@chakra-ui/react'; import React from 'react'; import type { InternalTransaction } from 'types/api/internalTransaction'; import { AddressHighlightProvider } from 'lib/contexts/addressHighlight'; import { currencyUnits } from 'lib/units'; -import { default as Thead } from 'ui/shared/TheadSticky'; +import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table'; import InternalTxsTableItem from './InternalTxsTableItem'; @@ -18,19 +17,19 @@ interface Props { const InternalTxsTable = ({ data, currentAddress, isLoading }: Props) => { return ( - - - - - - - - - - - + + + + { data.map((item, index) => ( { isLoading={ isLoading } /> )) } - -
Parent txn hashTypeBlockFrom/To + + + + Parent txn hash + Type + Block + From/To + Value { currencyUnits.ether } -
+ +
); diff --git a/ui/internalTxs/InternalTxsTableItem.tsx b/ui/internalTxs/InternalTxsTableItem.tsx index d4ff472ede..807e699bb6 100644 --- a/ui/internalTxs/InternalTxsTableItem.tsx +++ b/ui/internalTxs/InternalTxsTableItem.tsx @@ -1,13 +1,14 @@ -import { Tr, Td, Box, Flex } from '@chakra-ui/react'; +import { Flex } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; import React from 'react'; import type { InternalTransaction } from 'types/api/internalTransaction'; import config from 'configs/app'; +import { Badge } from 'toolkit/chakra/badge'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import { TableCell, TableRow } from 'toolkit/chakra/table'; import AddressFromTo from 'ui/shared/address/AddressFromTo'; -import Skeleton from 'ui/shared/chakra/Skeleton'; -import Tag from 'ui/shared/chakra/Tag'; import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxStatus from 'ui/shared/statusTag/TxStatus'; @@ -34,8 +35,8 @@ const InternalTxsTableItem = ({ const toData = to ? to : createdContract; return ( - - + + - - - + + + { typeTitle && ( - - { typeTitle } - + { typeTitle } ) } - - + + - - + + - - - + + + { BigNumber(value).div(BigNumber(10 ** config.chain.currency.decimals)).toFormat() } - - + + ); }; diff --git a/ui/interopMessages/InteropMessageAdditionalInfo.tsx b/ui/interopMessages/InteropMessageAdditionalInfo.tsx new file mode 100644 index 0000000000..a93097e2c4 --- /dev/null +++ b/ui/interopMessages/InteropMessageAdditionalInfo.tsx @@ -0,0 +1,39 @@ +import { chakra, Text, Flex } from '@chakra-ui/react'; +import React from 'react'; + +import type { InteropMessage } from 'types/api/interop'; + +import { PopoverBody, PopoverCloseTriggerWrapper, PopoverContent, PopoverRoot, PopoverTrigger } from 'toolkit/chakra/popover'; +import AdditionalInfoButton from 'ui/shared/AdditionalInfoButton'; +import CopyToClipboard from 'ui/shared/CopyToClipboard'; + +type Props = { + payload: InteropMessage['payload']; + isLoading?: boolean; + className?: string; +}; + +const InteropMessageAdditionalInfo = ({ payload, isLoading, className }: Props) => { + return ( + + + + + + + + Message payload + + + + + + { payload } + + + + + ); +}; + +export default React.memo(chakra(InteropMessageAdditionalInfo)); diff --git a/ui/interopMessages/InteropMessageDestinationTx.tsx b/ui/interopMessages/InteropMessageDestinationTx.tsx new file mode 100644 index 0000000000..5bd5509918 --- /dev/null +++ b/ui/interopMessages/InteropMessageDestinationTx.tsx @@ -0,0 +1,42 @@ +import React from 'react'; + +import type { ChainInfo } from 'types/api/interop'; + +import TxEntity from 'ui/shared/entities/tx/TxEntity'; +import type { EntityProps } from 'ui/shared/entities/tx/TxEntity'; +import TxEntityInterop from 'ui/shared/entities/tx/TxEntityInterop'; + +type Props = { + relay_transaction_hash?: string | null; + relay_chain?: ChainInfo | null; + truncation?: EntityProps['truncation']; + isLoading?: boolean; +}; + +const InteropMessageDestinationTx = (props: Props) => { + if (props.relay_chain !== undefined) { + return ( + + ); + } + + if (!props.relay_transaction_hash) { + return 'N/A'; + } + + return ( + + ); +}; + +export default InteropMessageDestinationTx; diff --git a/ui/interopMessages/InteropMessageSourceTx.tsx b/ui/interopMessages/InteropMessageSourceTx.tsx new file mode 100644 index 0000000000..b349bd0bf5 --- /dev/null +++ b/ui/interopMessages/InteropMessageSourceTx.tsx @@ -0,0 +1,41 @@ +import React from 'react'; + +import type { ChainInfo } from 'types/api/interop'; + +import type { EntityProps } from 'ui/shared/entities/tx/TxEntity'; +import TxEntity from 'ui/shared/entities/tx/TxEntity'; +import TxEntityInterop from 'ui/shared/entities/tx/TxEntityInterop'; + +type Props = { + init_transaction_hash?: string | null; + init_chain?: ChainInfo | null; + isLoading?: boolean; + truncation?: EntityProps['truncation']; +}; +const InteropMessageSourceTx = (props: Props) => { + if (props.init_chain !== undefined) { + return ( + + ); + } + + if (!props.init_transaction_hash) { + return 'N/A'; + } + + return ( + + ); +}; + +export default InteropMessageSourceTx; diff --git a/ui/interopMessages/InteropMessagesListItem.tsx b/ui/interopMessages/InteropMessagesListItem.tsx new file mode 100644 index 0000000000..6c6afc3ac2 --- /dev/null +++ b/ui/interopMessages/InteropMessagesListItem.tsx @@ -0,0 +1,71 @@ +import { Flex, HStack, Text, Grid } from '@chakra-ui/react'; +import React from 'react'; + +import type { InteropMessage } from 'types/api/interop'; + +import AddressFromToIcon from 'ui/shared/address/AddressFromToIcon'; +import AddressEntity from 'ui/shared/entities/address/AddressEntity'; +import AddressEntityInterop from 'ui/shared/entities/address/AddressEntityInterop'; +import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; +import InteropMessageStatus from 'ui/shared/statusTag/InteropMessageStatus'; +import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip'; + +import InteropMessageAdditionalInfo from './InteropMessageAdditionalInfo'; +import InteropMessageDestinationTx from './InteropMessageDestinationTx'; +import InteropMessageSourceTx from './InteropMessageSourceTx'; + +interface Props { + item: InteropMessage; + isLoading?: boolean; +} + +const InteropMessagesListItem = ({ item, isLoading }: Props) => { + return ( + + + + + + + + #{ item.nonce } + + + + Source tx + + Destination tx + + + + { item.init_chain !== undefined ? ( + + ) : ( + + ) } + + { item.relay_chain !== undefined ? ( + + ) : ( + + ) } + + + + ); +}; + +export default React.memo(InteropMessagesListItem); diff --git a/ui/interopMessages/InteropMessagesTable.tsx b/ui/interopMessages/InteropMessagesTable.tsx new file mode 100644 index 0000000000..4c49000dab --- /dev/null +++ b/ui/interopMessages/InteropMessagesTable.tsx @@ -0,0 +1,44 @@ +import React from 'react'; + +import type { InteropMessage } from 'types/api/interop'; + +import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table'; + +import InteropMessagesTableItem from './InteropMessagesTableItem'; + +interface Props { + items?: Array; + top: number; + isLoading?: boolean; +} + +const InteropMessagesTable = ({ items, top, isLoading }: Props) => { + return ( + + + + + Message + Age + Status + Source tx + Destination tx + Sender + In/Out + Target + + + + { items?.map((item, index) => ( + + )) } + + + ); +}; + +export default React.memo(InteropMessagesTable); diff --git a/ui/interopMessages/InteropMessagesTableItem.tsx b/ui/interopMessages/InteropMessagesTableItem.tsx new file mode 100644 index 0000000000..225507cb90 --- /dev/null +++ b/ui/interopMessages/InteropMessagesTableItem.tsx @@ -0,0 +1,67 @@ +import React from 'react'; + +import type { InteropMessage } from 'types/api/interop'; + +import { Skeleton } from 'toolkit/chakra/skeleton'; +import { TableCell, TableRow } from 'toolkit/chakra/table'; +import AddressFromToIcon from 'ui/shared/address/AddressFromToIcon'; +import AddressEntity from 'ui/shared/entities/address/AddressEntity'; +import AddressEntityInterop from 'ui/shared/entities/address/AddressEntityInterop'; +import InteropMessageStatus from 'ui/shared/statusTag/InteropMessageStatus'; +import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip'; + +import InteropMessageAdditionalInfo from './InteropMessageAdditionalInfo'; +import InteropMessageDestinationTx from './InteropMessageDestinationTx'; +import InteropMessageSourceTx from './InteropMessageSourceTx'; + +interface Props { + item: InteropMessage; + isLoading?: boolean; +} + +const InteropMessagesTableItem = ({ item, isLoading }: Props) => { + return ( + + + + + + + { item.nonce } + + + + + + + + + + + + + + + + { item.init_chain !== undefined ? + : + + } + + + + + + { item.relay_chain !== undefined ? + : + + } + + + ); +}; + +export default React.memo(InteropMessagesTableItem); diff --git a/ui/marketplace/AppSecurityReport.tsx b/ui/marketplace/AppSecurityReport.tsx index 33da12daa2..896b8bac20 100644 --- a/ui/marketplace/AppSecurityReport.tsx +++ b/ui/marketplace/AppSecurityReport.tsx @@ -1,4 +1,5 @@ -import { Box, Text, Link, PopoverTrigger, PopoverBody, PopoverContent, useDisclosure, chakra, Flex, Divider, Icon } from '@chakra-ui/react'; +import type { BoxProps, ButtonProps } from '@chakra-ui/react'; +import { Box, Text, Flex, Separator, Icon } from '@chakra-ui/react'; import React from 'react'; import type { MarketplaceAppSecurityReport } from 'types/client/marketplace'; @@ -11,7 +12,9 @@ import config from 'configs/app'; import solidityScanIcon from 'icons/brands/solidity_scan.svg'; import { apos } from 'lib/html-entities'; import * as mixpanel from 'lib/mixpanel/index'; -import Popover from 'ui/shared/chakra/Popover'; +import { Link } from 'toolkit/chakra/link'; +import { PopoverBody, PopoverContent, PopoverRoot } from 'toolkit/chakra/popover'; +import { useDisclosure } from 'toolkit/hooks/useDisclosure'; import IconSvg from 'ui/shared/IconSvg'; import SolidityscanReportButton from 'ui/shared/solidityscanReport/SolidityscanReportButton'; import SolidityscanReportDetails from 'ui/shared/solidityscanReport/SolidityscanReportDetails'; @@ -24,29 +27,32 @@ type Props = { isLoading?: boolean; onlyIcon?: boolean; source: 'Discovery view' | 'App modal' | 'App page'; - className?: string; popoverPlacement?: 'bottom-start' | 'bottom-end' | 'left'; + buttonProps?: ButtonProps; + triggerWrapperProps?: BoxProps; }; const AppSecurityReport = ({ - id, securityReport, showContractList, isLoading, onlyIcon, source, className, popoverPlacement = 'bottom-start', + id, securityReport, showContractList, isLoading, onlyIcon, source, triggerWrapperProps, buttonProps, popoverPlacement = 'bottom-start', }: Props) => { - const { isOpen, onToggle, onClose } = useDisclosure(); + + const { open, onOpenChange } = useDisclosure(); const handleButtonClick = React.useCallback(() => { mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'Security score', Info: id, Source: source }); - onToggle(); - }, [ id, source, onToggle ]); + }, [ id, source ]); const showAnalyzedContracts = React.useCallback(() => { mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'Analyzed contracts', Info: id, Source: 'Security score popup' }); showContractList(id, ContractListTypes.ANALYZED); - }, [ showContractList, id ]); + onOpenChange({ open: false }); + }, [ showContractList, id, onOpenChange ]); const showAllContracts = React.useCallback(() => { mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'Total contracts', Info: id, Source: 'Security score popup' }); showContractList(id, ContractListTypes.ALL); - }, [ showContractList, id ]); + onOpenChange({ open: false }); + }, [ showContractList, id, onOpenChange ]); const { securityScore = 0, @@ -60,31 +66,29 @@ const AppSecurityReport = ({ } return ( - - - The security score is based on analysis
of a DApp{ apos }s smart contracts. } - className={ className } - /> -
- - - Smart contracts info + + The security score is based on analysis
of a DApp{ apos }s smart contracts. } + wrapperProps={ triggerWrapperProps } + { ...buttonProps } + /> + + + Smart contracts info Verified contracts - + { securityReport?.overallInfo.verifiedNumber ?? 0 } of { securityReport?.overallInfo.totalContractsNumber ?? 0 } - + { solidityScanContractsNumber } smart contract{ solidityScanContractsNumber === 1 ? ' was' : 's were' } evaluated to determine this protocol{ apos }s overall security score on the { config.chain.name } network by { ' ' } @@ -96,7 +100,7 @@ const AppSecurityReport = ({ { issueSeverityDistribution && totalIssues > 0 && ( - Threat score & vulnerabilities + Threat score & vulnerabilities ) } @@ -105,8 +109,8 @@ const AppSecurityReport = ({ -
+ ); }; -export default chakra(AppSecurityReport); +export default AppSecurityReport; diff --git a/ui/marketplace/Banner/FeaturedApp.tsx b/ui/marketplace/Banner/FeaturedApp.tsx index 7c5d66ecb0..e7a062cab4 100644 --- a/ui/marketplace/Banner/FeaturedApp.tsx +++ b/ui/marketplace/Banner/FeaturedApp.tsx @@ -1,13 +1,18 @@ -import { Link, useColorModeValue, LinkBox, Flex, Image, LinkOverlay, IconButton } from '@chakra-ui/react'; +import { Flex, Text } from '@chakra-ui/react'; import type { MouseEvent } from 'react'; import React, { useCallback } from 'react'; import type { MarketplaceAppPreview } from 'types/client/marketplace'; +import { route } from 'nextjs-routes'; + import useIsMobile from 'lib/hooks/useIsMobile'; import * as mixpanel from 'lib/mixpanel/index'; -import Skeleton from 'ui/shared/chakra/Skeleton'; -import NextLink from 'ui/shared/NextLink'; +import { useColorModeValue } from 'toolkit/chakra/color-mode'; +import { IconButton } from 'toolkit/chakra/icon-button'; +import { Image } from 'toolkit/chakra/image'; +import { Link, LinkBox, LinkOverlay } from 'toolkit/chakra/link'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import FavoriteIcon from '../FavoriteIcon'; import MarketplaceAppIntegrationIcon from '../MarketplaceAppIntegrationIcon'; @@ -32,8 +37,6 @@ const FeaturedApp = ({ const logoUrl = useColorModeValue(logo, logoDarkMode || logo); const categoriesLabel = categories.join(', '); - const backgroundColor = useColorModeValue('purple.50', 'whiteAlpha.100'); - const handleInfoClick = useCallback((event: MouseEvent) => { event.preventDefault(); mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'More button', Info: id, Source: 'Banner' }); @@ -58,18 +61,18 @@ const FeaturedApp = ({ } return ( - + - { external ? ( - - { title } - - ) : ( - - - { title } - - - ) } + + { title } + @@ -128,27 +127,25 @@ const FeaturedApp = ({ { !isLoading && ( } - /> + selected={ isFavorite } + > + + ) } - { shortDescription } + + { shortDescription } + diff --git a/ui/marketplace/Banner/FeaturedAppMobile.tsx b/ui/marketplace/Banner/FeaturedAppMobile.tsx index 34c3acd855..872ccbe66a 100644 --- a/ui/marketplace/Banner/FeaturedAppMobile.tsx +++ b/ui/marketplace/Banner/FeaturedAppMobile.tsx @@ -1,10 +1,14 @@ -import { IconButton, Image, Link, LinkBox, useColorModeValue, Flex } from '@chakra-ui/react'; +import { Flex, Text } from '@chakra-ui/react'; import type { MouseEvent } from 'react'; import React from 'react'; import type { MarketplaceAppPreview } from 'types/client/marketplace'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { useColorModeValue } from 'toolkit/chakra/color-mode'; +import { IconButton } from 'toolkit/chakra/icon-button'; +import { Image } from 'toolkit/chakra/image'; +import { Link, LinkBox } from 'toolkit/chakra/link'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import FavoriteIcon from '../FavoriteIcon'; import MarketplaceAppCardLink from '../MarketplaceAppCardLink'; @@ -43,7 +47,7 @@ const FeaturedAppMobile = ({ borderRadius="md" padding={{ base: 3, sm: '20px' }} role="group" - background={ useColorModeValue('purple.50', 'whiteAlpha.100') } + background={{ base: 'purple.50', sm: 'whiteAlpha.100' }} mt={ 6 } > { categoriesLabel } - { shortDescription } + + { shortDescription } + @@ -140,13 +143,13 @@ const FeaturedAppMobile = ({ top={{ base: 1, sm: '18px' }} aria-label="Mark as favorite" title="Mark as favorite" - variant="ghost" - colorScheme="gray" - w={ 9 } - h={ 8 } + variant="icon_secondary" + size="md" onClick={ onFavoriteClick } - icon={ } - /> + selected={ isFavorite } + > + + ) } diff --git a/ui/marketplace/Banner/IframeBanner.tsx b/ui/marketplace/Banner/IframeBanner.tsx index 29327ca8bf..fc2ec4e70f 100644 --- a/ui/marketplace/Banner/IframeBanner.tsx +++ b/ui/marketplace/Banner/IframeBanner.tsx @@ -1,8 +1,9 @@ -import { Link, Box } from '@chakra-ui/react'; +import { chakra } from '@chakra-ui/react'; import React, { useCallback, useState } from 'react'; import * as mixpanel from 'lib/mixpanel/index'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Link } from 'toolkit/chakra/link'; +import { Skeleton } from 'toolkit/chakra/skeleton'; const IframeBanner = ({ contentUrl, linkUrl }: { contentUrl: string; linkUrl: string }) => { const [ isFrameLoading, setIsFrameLoading ] = useState(true); @@ -17,7 +18,7 @@ const IframeBanner = ({ contentUrl, linkUrl }: { contentUrl: string; linkUrl: st return ( - { - const isMobile = useIsMobile(); + const handleOpenChange = React.useCallback(({ open }: { open: boolean }) => { + if (!open) { + onClose(); + } + }, [ onClose ]); const displayedContracts = React.useMemo(() => { if (!contracts) { @@ -54,35 +54,18 @@ const ContractListModal = ({ onClose, onBack, type, contracts }: Props) => { } return ( - - - - - { onBack && ( - - ) } - - { titles[type] } - - - - + + { titles[type] } + + { /> )) } - - - + + + ); }; diff --git a/ui/marketplace/ContractSecurityReport.tsx b/ui/marketplace/ContractSecurityReport.tsx index a2983342d2..5cf2ecce98 100644 --- a/ui/marketplace/ContractSecurityReport.tsx +++ b/ui/marketplace/ContractSecurityReport.tsx @@ -1,4 +1,4 @@ -import { Box, Text, PopoverTrigger, PopoverBody, PopoverContent, useDisclosure, Icon } from '@chakra-ui/react'; +import { Box, Text, Icon } from '@chakra-ui/react'; import React from 'react'; import config from 'configs/app'; @@ -8,8 +8,8 @@ import config from 'configs/app'; import solidityScanIcon from 'icons/brands/solidity_scan.svg'; import * as mixpanel from 'lib/mixpanel/index'; import type { SolidityScanReport } from 'lib/solidityScan/schema'; -import Popover from 'ui/shared/chakra/Popover'; -import LinkExternal from 'ui/shared/links/LinkExternal'; +import { Link } from 'toolkit/chakra/link'; +import { PopoverBody, PopoverContent, PopoverRoot } from 'toolkit/chakra/popover'; import SolidityscanReportButton from 'ui/shared/solidityscanReport/SolidityscanReportButton'; import SolidityscanReportDetails from 'ui/shared/solidityscanReport/SolidityscanReportDetails'; import SolidityscanReportScore from 'ui/shared/solidityscanReport/SolidityscanReportScore'; @@ -19,12 +19,9 @@ type Props = { }; const ContractSecurityReport = ({ securityReport }: Props) => { - const { isOpen, onToggle, onClose } = useDisclosure(); - const handleClick = React.useCallback(() => { mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'Security score', Source: 'Analyzed contracts popup' }); - onToggle(); - }, [ onToggle ]); + }, [ ]); if (!securityReport) { return null; @@ -39,16 +36,13 @@ const ContractSecurityReport = ({ securityReport }: Props) => { const totalIssues = Object.values(issueSeverityDistribution as Record).reduce((acc, val) => acc + val, 0); return ( - - - - - - + + + + The security score was derived from evaluating the smart contracts of a protocol on the { config.chain.name } network by { ' ' } @@ -59,14 +53,14 @@ const ContractSecurityReport = ({ securityReport }: Props) => { { issueSeverityDistribution && totalIssues > 0 && ( - Threat score & vulnerabilities + Threat score & vulnerabilities ) } - View full report + View full report - + ); }; diff --git a/ui/marketplace/EmptySearchResult.tsx b/ui/marketplace/EmptySearchResult.tsx index b6a263fbcf..30af27bc58 100644 --- a/ui/marketplace/EmptySearchResult.tsx +++ b/ui/marketplace/EmptySearchResult.tsx @@ -4,9 +4,9 @@ import { MarketplaceCategory } from 'types/client/marketplace'; import config from 'configs/app'; import { apos } from 'lib/html-entities'; +import { Link } from 'toolkit/chakra/link'; import EmptySearchResultDefault from 'ui/shared/EmptySearchResult'; import IconSvg from 'ui/shared/IconSvg'; -import LinkExternal from 'ui/shared/links/LinkExternal'; const feature = config.features.marketplace; @@ -29,7 +29,7 @@ const EmptySearchResult = ({ favoriteApps, selectedCategoryId }: Props) => ( { 'suggestIdeasFormUrl' in feature && ( <> { ' ' }Have a groundbreaking idea or app suggestion?
- Share it with us + Share it with us ) } diff --git a/ui/marketplace/FavoriteIcon.tsx b/ui/marketplace/FavoriteIcon.tsx index f48c44f748..82d4068b60 100644 --- a/ui/marketplace/FavoriteIcon.tsx +++ b/ui/marketplace/FavoriteIcon.tsx @@ -1,22 +1,17 @@ -import { useColorModeValue } from '@chakra-ui/react'; +import type { HTMLChakraProps } from '@chakra-ui/react'; import React from 'react'; import IconSvg from 'ui/shared/IconSvg'; -type Props = { +interface Props extends HTMLChakraProps<'div'> { isFavorite: boolean; - color?: string; }; -const FavoriteIcon = ({ isFavorite, color }: Props) => { - const heartFilledColor = useColorModeValue('blue.600', 'blue.300'); - const defaultColor = isFavorite ? heartFilledColor : (color || 'gray.400'); - +const FavoriteIcon = ({ isFavorite, ...rest }: Props) => { return ( ); }; diff --git a/ui/marketplace/MarketplaceAppCard.tsx b/ui/marketplace/MarketplaceAppCard.tsx index 976903c519..7c9f922aef 100644 --- a/ui/marketplace/MarketplaceAppCard.tsx +++ b/ui/marketplace/MarketplaceAppCard.tsx @@ -1,4 +1,4 @@ -import { IconButton, Image, Link, LinkBox, useColorModeValue, chakra, Flex } from '@chakra-ui/react'; +import { chakra, Flex, Text } from '@chakra-ui/react'; import type { MouseEvent } from 'react'; import React, { useCallback } from 'react'; @@ -6,7 +6,11 @@ import type { MarketplaceAppWithSecurityReport, ContractListTypes, AppRating } f import useIsMobile from 'lib/hooks/useIsMobile'; import isBrowser from 'lib/isBrowser'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { useColorModeValue } from 'toolkit/chakra/color-mode'; +import { IconButton } from 'toolkit/chakra/icon-button'; +import { Image } from 'toolkit/chakra/image'; +import { Link, LinkBox } from 'toolkit/chakra/link'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import CopyToClipboard from 'ui/shared/CopyToClipboard'; import AppSecurityReport from './AppSecurityReport'; @@ -30,7 +34,7 @@ interface Props extends MarketplaceAppWithSecurityReport { isRatingSending: boolean; isRatingLoading: boolean; canRate: boolean | undefined; - graphLinks: Array<{ text: string; url: string }>; + graphLinks?: Array<{ title: string; url: string }>; } const MarketplaceAppCard = ({ @@ -84,9 +88,8 @@ const MarketplaceAppCard = ({ }} borderRadius="md" padding={{ base: 3, md: '20px' }} - border="1px" - borderColor={ useColorModeValue('gray.200', 'gray.600') } - role="group" + borderWidth="1px" + borderColor={{ _light: 'gray.200', _dark: 'gray.600' }} > @@ -157,12 +160,12 @@ const MarketplaceAppCard = ({ - { shortDescription } + + { shortDescription } + { !isLoading && ( @@ -172,7 +175,7 @@ const MarketplaceAppCard = ({ marginTop="auto" > More info - + } - ml={ 2 } - /> + selected={ isFavorite } + > + + @@ -228,11 +225,15 @@ const MarketplaceAppCard = ({ isLoading={ isLoading } source="Discovery view" popoverPlacement={ isMobile ? 'bottom-end' : 'left' } - position="absolute" - right={{ base: 3, md: 5 }} - top={{ base: '10px', md: 5 }} - border={ 0 } - padding={ 0 } + triggerWrapperProps={{ + position: 'absolute', + right: { base: 3, md: 5 }, + top: { base: '10px', md: 5 }, + }} + buttonProps={{ + border: 0, + padding: 0, + }} /> ) } diff --git a/ui/marketplace/MarketplaceAppCardLink.tsx b/ui/marketplace/MarketplaceAppCardLink.tsx index 61c1b18aad..96265398a4 100644 --- a/ui/marketplace/MarketplaceAppCardLink.tsx +++ b/ui/marketplace/MarketplaceAppCardLink.tsx @@ -1,8 +1,10 @@ -import { LinkOverlay, chakra } from '@chakra-ui/react'; +import { chakra } from '@chakra-ui/react'; import React from 'react'; import type { MouseEvent } from 'react'; -import NextLink from 'ui/shared/NextLink'; +import { route } from 'nextjs-routes'; + +import { LinkOverlay } from 'toolkit/chakra/link'; type Props = { id: string; @@ -18,16 +20,16 @@ const MarketplaceAppCardLink = ({ url, external, id, title, onClick, className } onClick?.(event, id); }, [ onClick, id ]); - return external ? ( - + return ( + { title } - ) : ( - - - { title } - - ); }; diff --git a/ui/marketplace/MarketplaceAppGraphLinks.tsx b/ui/marketplace/MarketplaceAppGraphLinks.tsx index 3b2a98d595..574e94c15f 100644 --- a/ui/marketplace/MarketplaceAppGraphLinks.tsx +++ b/ui/marketplace/MarketplaceAppGraphLinks.tsx @@ -1,8 +1,5 @@ import { Text, - PopoverTrigger, - PopoverBody, - PopoverContent, chakra, Box, VStack, @@ -10,9 +7,9 @@ import { import React from 'react'; import useIsMobile from 'lib/hooks/useIsMobile'; -import Popover from 'ui/shared/chakra/Popover'; +import { Link } from 'toolkit/chakra/link'; +import { Tooltip } from 'toolkit/chakra/tooltip'; import IconSvg from 'ui/shared/IconSvg'; -import LinkExternal from 'ui/shared/links/LinkExternal'; interface Props { className?: string; @@ -30,27 +27,25 @@ const MarketplaceAppGraphLinks = ({ className, links }: Props) => { return null; } + const content = ( + + { `This dapp uses ${ links.length > 1 ? 'several subgraphs' : 'a subgraph' } powered by The Graph` } + { links.map(link => ( + { link.title } + )) } + + ); + return ( - - + - - - - - - - { `This dapp uses ${ links.length > 1 ? 'several subgraphs' : 'a subgraph' } powered by The Graph` } - { links.map(link => ( - { link.title } - )) } - - - - + + ); }; diff --git a/ui/marketplace/MarketplaceAppInfo.tsx b/ui/marketplace/MarketplaceAppInfo.tsx index 107186017c..64af06e1e8 100644 --- a/ui/marketplace/MarketplaceAppInfo.tsx +++ b/ui/marketplace/MarketplaceAppInfo.tsx @@ -8,11 +8,12 @@ import Content from './MarketplaceAppInfo/Content'; interface Props { data: MarketplaceAppOverview | undefined; + isLoading?: boolean; } -const MarketplaceAppInfo = ({ data }: Props) => { +const MarketplaceAppInfo = ({ data, isLoading }: Props) => { return ( - + ); diff --git a/ui/marketplace/MarketplaceAppInfo/Content.tsx b/ui/marketplace/MarketplaceAppInfo/Content.tsx index 778d421237..8f2962b1d3 100644 --- a/ui/marketplace/MarketplaceAppInfo/Content.tsx +++ b/ui/marketplace/MarketplaceAppInfo/Content.tsx @@ -34,13 +34,13 @@ const Content = ({ data }: Props) => { return (
- Project info + Project info { data?.shortDescription }
{ socialLinks.length > 0 && (
- Links + Links { socialLinks.map((link, index) => ) } diff --git a/ui/marketplace/MarketplaceAppInfo/SocialLink.tsx b/ui/marketplace/MarketplaceAppInfo/SocialLink.tsx index da3464f6d6..465f576a8b 100644 --- a/ui/marketplace/MarketplaceAppInfo/SocialLink.tsx +++ b/ui/marketplace/MarketplaceAppInfo/SocialLink.tsx @@ -1,8 +1,8 @@ -import { Link } from '@chakra-ui/react'; import React from 'react'; import type { MarketplaceAppSocialInfo } from 'types/client/marketplace'; +import { Link } from 'toolkit/chakra/link'; import type { IconName } from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg'; @@ -23,7 +23,7 @@ const SocialLink = ({ href, icon, title }: Props) => { display="inline-flex" alignItems="center" > - + { title } ); diff --git a/ui/marketplace/MarketplaceAppInfo/WebsiteLink.tsx b/ui/marketplace/MarketplaceAppInfo/WebsiteLink.tsx index 2a3dea9cca..6e7b7e8a8d 100644 --- a/ui/marketplace/MarketplaceAppInfo/WebsiteLink.tsx +++ b/ui/marketplace/MarketplaceAppInfo/WebsiteLink.tsx @@ -1,6 +1,7 @@ -import { Link } from '@chakra-ui/react'; import React from 'react'; +import makePrettyLink from 'lib/makePrettyLink'; +import { Link } from 'toolkit/chakra/link'; import IconSvg from 'ui/shared/IconSvg'; interface Props { @@ -12,23 +13,18 @@ const WebsiteLink = ({ url }: Props) => { return null; } - function getHostname(url: string) { - try { - return new URL(url).hostname; - } catch (err) {} - } - return ( - - { getHostname(url) } + + { makePrettyLink(url)?.domain } ); }; diff --git a/ui/marketplace/MarketplaceAppIntegrationIcon.tsx b/ui/marketplace/MarketplaceAppIntegrationIcon.tsx index 6011eec96f..936d412181 100644 --- a/ui/marketplace/MarketplaceAppIntegrationIcon.tsx +++ b/ui/marketplace/MarketplaceAppIntegrationIcon.tsx @@ -1,6 +1,6 @@ -import { Tooltip } from '@chakra-ui/react'; import React from 'react'; +import { Tooltip } from 'toolkit/chakra/tooltip'; import type { IconName } from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg'; @@ -18,7 +18,7 @@ const MarketplaceAppIntegrationIcon = ({ external, internalWallet }: Props) => { if (external) { icon = 'link_external'; - color = 'icon_link_external'; + color = 'icon.externalLink'; text = 'This app opens in a separate tab'; boxSize = 4; } else if (internalWallet) { @@ -32,11 +32,9 @@ const MarketplaceAppIntegrationIcon = ({ external, internalWallet }: Props) => { return ( { position="relative" cursor="pointer" verticalAlign="middle" - mb={{ base: 0, md: 1 }} /> ); diff --git a/ui/marketplace/MarketplaceAppModal.tsx b/ui/marketplace/MarketplaceAppModal.tsx index e5f31a576e..70a54ea4a0 100644 --- a/ui/marketplace/MarketplaceAppModal.tsx +++ b/ui/marketplace/MarketplaceAppModal.tsx @@ -1,17 +1,25 @@ -import { - Box, Flex, Heading, IconButton, Image, Link, Modal, ModalBody, - ModalCloseButton, ModalContent, ModalFooter, ModalOverlay, Tag, Text, useColorModeValue, -} from '@chakra-ui/react'; +import { Box, Flex, Text } from '@chakra-ui/react'; import React, { useCallback } from 'react'; import type { MarketplaceAppWithSecurityReport, AppRating } from 'types/client/marketplace'; import { ContractListTypes } from 'types/client/marketplace'; +import { route } from 'nextjs-routes'; + import config from 'configs/app'; import useIsMobile from 'lib/hooks/useIsMobile'; import { nbsp } from 'lib/html-entities'; import isBrowser from 'lib/isBrowser'; +import makePrettyLink from 'lib/makePrettyLink'; import * as mixpanel from 'lib/mixpanel/index'; +import { Badge } from 'toolkit/chakra/badge'; +import { Button } from 'toolkit/chakra/button'; +import { useColorModeValue } from 'toolkit/chakra/color-mode'; +import { DialogBody, DialogCloseTrigger, DialogContent, DialogFooter, DialogRoot } from 'toolkit/chakra/dialog'; +import { Heading } from 'toolkit/chakra/heading'; +import { IconButton } from 'toolkit/chakra/icon-button'; +import { Image } from 'toolkit/chakra/image'; +import { Link } from 'toolkit/chakra/link'; import CopyToClipboard from 'ui/shared/CopyToClipboard'; import type { IconName } from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg'; @@ -20,7 +28,6 @@ import AppSecurityReport from './AppSecurityReport'; import FavoriteIcon from './FavoriteIcon'; import MarketplaceAppGraphLinks from './MarketplaceAppGraphLinks'; import MarketplaceAppIntegrationIcon from './MarketplaceAppIntegrationIcon'; -import MarketplaceAppModalLink from './MarketplaceAppModalLink'; import Rating from './Rating/Rating'; import type { RateFunction } from './Rating/useRatings'; @@ -38,7 +45,7 @@ type Props = { isRatingSending: boolean; isRatingLoading: boolean; canRate: boolean | undefined; - graphLinks?: Array<{ text: string; url: string }>; + graphLinks?: Array<{ title: string; url: string }>; }; const MarketplaceAppModal = ({ @@ -97,14 +104,23 @@ const MarketplaceAppModal = ({ } } + const handleOpenChange = React.useCallback(({ open }: { open: boolean }) => { + if (!open) { + onClose(); + } + }, [ onClose ]); + const handleFavoriteClick = useCallback(() => { onFavoriteClick(id, isFavorite, 'App modal'); }, [ onFavoriteClick, id, isFavorite ]); const showContractList = useCallback((id: string, type: ContractListTypes) => { onClose(); - showContractListProp(id, type, true); - }, [ onClose, showContractListProp ]); + // FIXME: This is a workaround to avoid the dialog closing before the modal is opened + window.setTimeout(() => { + showContractListProp(id, type, true); + }, 100); + }, [ showContractListProp, onClose ]); const showAllContracts = React.useCallback(() => { mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'Total contracts', Info: id, Source: 'App modal' }); @@ -114,28 +130,17 @@ const MarketplaceAppModal = ({ const isMobile = useIsMobile(); const logoUrl = useColorModeValue(logo, logoDarkMode || logo); - function getHostname(url: string | undefined) { - try { - return new URL(url || '').hostname; - } catch (err) {} - } - - const iconColor = useColorModeValue('blue.600', 'gray.400'); - return ( - - - - + { title } + By{ nbsp }{ author } @@ -204,46 +207,38 @@ const MarketplaceAppModal = ({ > - + + + } - /> + selected={ isFavorite } + > + + - - - + { securityReport && ( ) } { description } - + - - + { categories.map((category) => ( - { category } - + )) } - + { site && ( - { getHostname(site) } + { makePrettyLink(site)?.domain } ) } @@ -332,23 +327,21 @@ const MarketplaceAppModal = ({ display="flex" alignItems="center" justifyContent="center" - isExternal - w={ 5 } - h={ 5 } + external + noIcon + flexShrink={ 0 } > )) } - - - + + + ); }; diff --git a/ui/marketplace/MarketplaceAppModalLink.tsx b/ui/marketplace/MarketplaceAppModalLink.tsx deleted file mode 100644 index a8bc411cf1..0000000000 --- a/ui/marketplace/MarketplaceAppModalLink.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { Button } from '@chakra-ui/react'; -import React from 'react'; - -import NextLink from 'ui/shared/NextLink'; - -type Props = { - id: string; - url: string; - external?: boolean; - title: string; -}; - -const MarketplaceAppModalLink = ({ url, external, id }: Props) => { - const buttonProps = { - size: 'sm', - marginRight: 2, - width: { base: '100%', sm: 'auto' }, - ...(external ? { - target: '_blank', - rel: 'noopener noreferrer', - } : {}), - }; - - return external ? ( - - ) : ( - - - - ); -}; - -export default MarketplaceAppModalLink; diff --git a/ui/marketplace/MarketplaceAppTopBar.tsx b/ui/marketplace/MarketplaceAppTopBar.tsx index 3aa7977f5e..2d6dfc68e0 100644 --- a/ui/marketplace/MarketplaceAppTopBar.tsx +++ b/ui/marketplace/MarketplaceAppTopBar.tsx @@ -1,4 +1,4 @@ -import { chakra, Flex, Tooltip } from '@chakra-ui/react'; +import { chakra, Flex } from '@chakra-ui/react'; import React from 'react'; import type { MarketplaceAppOverview, MarketplaceAppSecurityReport, ContractListTypes } from 'types/client/marketplace'; @@ -8,11 +8,10 @@ import { route } from 'nextjs-routes'; import config from 'configs/app'; import { useAppContext } from 'lib/contexts/app'; import useIsMobile from 'lib/hooks/useIsMobile'; +import makePrettyLink from 'lib/makePrettyLink'; +import { Link } from 'toolkit/chakra/link'; import RewardsButton from 'ui/rewards/RewardsButton'; -import Skeleton from 'ui/shared/chakra/Skeleton'; -import IconSvg from 'ui/shared/IconSvg'; -import LinkExternal from 'ui/shared/links/LinkExternal'; -import LinkInternal from 'ui/shared/links/LinkInternal'; +import ButtonBackTo from 'ui/shared/buttons/ButtonBackTo'; import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo'; import UserProfileDesktop from 'ui/snippets/user/profile/UserProfileDesktop'; import UserWalletDesktop from 'ui/snippets/user/wallet/UserWalletDesktop'; @@ -44,41 +43,33 @@ const MarketplaceAppTopBar = ({ appId, data, isLoading, securityReport }: Props) return route({ pathname: '/apps' }); }, [ appProps.referrer ]); - function getHostname(url: string | undefined) { - try { - return new URL(url || '').hostname; - } catch (err) {} - } - const showContractList = React.useCallback((id: string, type: ContractListTypes) => setContractListType(type), []); const hideContractList = React.useCallback(() => setContractListType(undefined), []); return ( <> - { !isMobile && } - - - - - - } + + - - { getHostname(data?.url) } + + { makePrettyLink(data?.url)?.domain } - - - - + + { (securityReport || isLoading) && ( { }, [ ]); return ( - - - - - - - Disclaimer - - + + + Disclaimer + - - + + You are now accessing a third-party app. Blockscout does not own, control, maintain, or audit 3rd party apps,{ ' ' } and is not liable for any losses associated with these interactions. Please do so at your own risk.

By clicking continue, you agree that you understand the risks and have read the Disclaimer.
-
+ - - - - + - -
-
+ + + ); }; diff --git a/ui/marketplace/MarketplaceList.tsx b/ui/marketplace/MarketplaceList.tsx index 7b8edd8bf2..6e9faacfff 100644 --- a/ui/marketplace/MarketplaceList.tsx +++ b/ui/marketplace/MarketplaceList.tsx @@ -26,7 +26,7 @@ type Props = { isRatingSending: boolean; isRatingLoading: boolean; canRate: boolean | undefined; - graphLinksQuery: UseQueryResult>, unknown>; + graphLinksQuery: UseQueryResult>, unknown>; }; const MarketplaceList = ({ @@ -61,6 +61,8 @@ const MarketplaceList = ({ external={ app.external } url={ app.url } title={ app.title } + description={ app.description } + author={ app.author } logo={ app.logo } logoDarkMode={ app.logoDarkMode } shortDescription={ app.shortDescription } diff --git a/ui/marketplace/Rating/PopoverContent.tsx b/ui/marketplace/Rating/PopoverContent.tsx index d86d31179d..d5a02997ab 100644 --- a/ui/marketplace/Rating/PopoverContent.tsx +++ b/ui/marketplace/Rating/PopoverContent.tsx @@ -4,9 +4,9 @@ import React from 'react'; import type { AppRating } from 'types/client/marketplace'; import type { EventTypes, EventPayload } from 'lib/mixpanel/index'; +import { Rating } from 'toolkit/chakra/rating'; import IconSvg from 'ui/shared/IconSvg'; -import Stars from './Stars'; import type { RateFunction } from './useRatings'; const ratingDescriptions = [ 'Very bad', 'Bad', 'Average', 'Good', 'Excellent' ]; @@ -21,25 +21,8 @@ type Props = { }; const PopoverContent = ({ appId, rating, userRating, rate, isSending, source }: Props) => { - const [ hovered, setHovered ] = React.useState(-1); - - const filledIndex = React.useMemo(() => { - if (hovered >= 0) { - return hovered; - } - return userRating?.value ? userRating?.value - 1 : -1; - }, [ userRating, hovered ]); - - const handleMouseOverFactory = React.useCallback((index: number) => () => { - setHovered(index); - }, []); - - const handleMouseOut = React.useCallback(() => { - setHovered(-1); - }, []); - - const handleRateFactory = React.useCallback((index: number) => () => { - rate(appId, rating?.recordId, userRating?.recordId, index + 1, source); + const handleValueChange = React.useCallback(({ value }: { value: number }) => { + rate(appId, rating?.recordId, userRating?.recordId, value, source); }, [ appId, rating, rate, userRating, source ]); if (isSending) { @@ -57,23 +40,16 @@ const PopoverContent = ({ appId, rating, userRating, rate, isSending, source }: { userRating && ( ) } - + { userRating ? 'App is already rated by you' : 'How was your experience?' }
- - - { (filledIndex >= 0) && ( - - { ratingDescriptions[filledIndex] } - - ) } - + ); }; diff --git a/ui/marketplace/Rating/Rating.tsx b/ui/marketplace/Rating/Rating.tsx index b6c06a509b..3549c4aea6 100644 --- a/ui/marketplace/Rating/Rating.tsx +++ b/ui/marketplace/Rating/Rating.tsx @@ -1,15 +1,15 @@ -import { Text, PopoverTrigger, PopoverBody, PopoverContent, useDisclosure, useOutsideClick, Box } from '@chakra-ui/react'; +import { Text } from '@chakra-ui/react'; import React from 'react'; import type { AppRating } from 'types/client/marketplace'; import config from 'configs/app'; import type { EventTypes, EventPayload } from 'lib/mixpanel/index'; -import Popover from 'ui/shared/chakra/Popover'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { PopoverBody, PopoverContent, PopoverRoot } from 'toolkit/chakra/popover'; +import { Rating } from 'toolkit/chakra/rating'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import Content from './PopoverContent'; -import Stars from './Stars'; import TriggerButton from './TriggerButton'; import type { RateFunction } from './useRatings'; @@ -28,14 +28,10 @@ type Props = { source: EventPayload['Source']; }; -const Rating = ({ +const MarketplaceRating = ({ appId, rating, userRating, rate, isSending, isLoading, fullView, canRate, source, }: Props) => { - const { isOpen, onToggle, onClose } = useDisclosure(); - // have to implement this solution because popover loses focus on button click inside it (issue: https://github.com/chakra-ui/chakra-ui/issues/7359) - const popoverRef = React.useRef(null); - useOutsideClick({ ref: popoverRef, handler: onClose }); if (!isEnabled) { return null; @@ -45,30 +41,27 @@ const Rating = ({ { fullView && ( <> - + { rating?.value } - { rating?.count && ({ rating?.count }) } + { rating?.count && ({ rating?.count }) } ) } - - - - - - - + + + + { canRate ? ( + + - - + ) : } + ); }; -export default Rating; +export default MarketplaceRating; diff --git a/ui/marketplace/Rating/Stars.tsx b/ui/marketplace/Rating/Stars.tsx deleted file mode 100644 index ca2e8d0be4..0000000000 --- a/ui/marketplace/Rating/Stars.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Flex, useColorModeValue } from '@chakra-ui/react'; -import React from 'react'; -import type { MouseEventHandler } from 'react'; - -import IconSvg from 'ui/shared/IconSvg'; - -type Props = { - filledIndex: number; - onMouseOverFactory?: (index: number) => MouseEventHandler; - onMouseOut?: () => void; - onClickFactory?: (index: number) => MouseEventHandler; -}; - -const Stars = ({ filledIndex, onMouseOverFactory, onMouseOut, onClickFactory }: Props) => { - const disabledStarColor = useColorModeValue('gray.200', 'gray.700'); - const outlineStartColor = onMouseOverFactory ? 'gray.400' : disabledStarColor; - return ( - - { Array(5).fill(null).map((_, index) => ( - = index ? 'star_filled' : 'star_outline' } - color={ filledIndex >= index ? 'yellow.400' : outlineStartColor } - w={ 6 } // 5 + 1 padding - h={ 5 } - pr={ 1 } // use padding intead of margin so that there are no empty spaces between stars without hover effect - _last={{ w: 5, pr: 0 }} - cursor={ onMouseOverFactory ? 'pointer' : 'default' } - onMouseOver={ onMouseOverFactory?.(index) } - onMouseOut={ onMouseOut } - onClick={ onClickFactory?.(index) } - /> - )) } - - ); -}; - -export default Stars; diff --git a/ui/marketplace/Rating/TriggerButton.tsx b/ui/marketplace/Rating/TriggerButton.tsx index c94647efb8..4b600bdca1 100644 --- a/ui/marketplace/Rating/TriggerButton.tsx +++ b/ui/marketplace/Rating/TriggerButton.tsx @@ -1,16 +1,18 @@ -import { Button, chakra, useColorModeValue, Tooltip, useDisclosure, Text } from '@chakra-ui/react'; +import { chakra, Text } from '@chakra-ui/react'; import React from 'react'; import useIsMobile from 'lib/hooks/useIsMobile'; import usePreventFocusAfterModalClosing from 'lib/hooks/usePreventFocusAfterModalClosing'; +import type { ButtonProps } from 'toolkit/chakra/button'; +import { Button } from 'toolkit/chakra/button'; +import { PopoverTrigger } from 'toolkit/chakra/popover'; +import { Tooltip } from 'toolkit/chakra/tooltip'; import IconSvg from 'ui/shared/IconSvg'; -type Props = { +interface Props extends ButtonProps { rating?: number; count?: number; fullView?: boolean; - isActive: boolean; - onClick: () => void; canRate: boolean | undefined; }; @@ -25,65 +27,53 @@ const getTooltipText = (canRate: boolean | undefined) => { }; const TriggerButton = ( - { rating, count, fullView, isActive, onClick, canRate }: Props, + { rating, count, fullView, canRate, onClick, ...rest }: Props, ref: React.ForwardedRef, ) => { - const textColor = useColorModeValue('blackAlpha.800', 'whiteAlpha.800'); const onFocusCapture = usePreventFocusAfterModalClosing(); - // have to implement controlled tooltip on mobile because of the issue - https://github.com/chakra-ui/chakra-ui/issues/7107 - const { isOpen, onToggle, onClose } = useDisclosure(); const isMobile = useIsMobile(); - const handleClick = React.useCallback(() => { - if (canRate) { - onClick(); - } else if (isMobile) { - onToggle(); - } - }, [ canRate, isMobile, onToggle, onClick ]); - return ( - +
+ + + +
); }; diff --git a/ui/marketplace/Rating/useRatings.test.tsx b/ui/marketplace/Rating/useRatings.test.tsx index ee8d71695d..6b1f8196b7 100644 --- a/ui/marketplace/Rating/useRatings.test.tsx +++ b/ui/marketplace/Rating/useRatings.test.tsx @@ -1,3 +1,5 @@ +import { act } from 'react'; + import { renderHook, wrapper } from 'jest/lib'; import useRatings from './useRatings'; @@ -5,7 +7,11 @@ import useRatings from './useRatings'; const useAccount = jest.fn(); const useApiQuery = jest.fn(); -jest.mock('lib/hooks/useToast', () => jest.fn()); +jest.mock('toolkit/chakra/toaster', () => ({ + toaster: { + error: jest.fn(), + }, +})); jest.mock('wagmi', () => ({ useAccount: () => useAccount() })); jest.mock('lib/api/useApiQuery', () => () => useApiQuery()); @@ -19,7 +25,12 @@ it('should set canRate to true if address is defined and transactions_count is 5 isPlaceholderData: false, data: { transactions_count: 5 }, }); + const { result } = renderHook(() => useRatings(), { wrapper }); + await act(async() => { + await Promise.resolve(); + }); + expect(result.current.canRate).toBe(true); }); @@ -29,7 +40,12 @@ it('should set canRate to undefined if address is undefined', async() => { isPlaceholderData: false, data: { transactions_count: 5 }, }); + const { result } = renderHook(() => useRatings(), { wrapper }); + await act(async() => { + await Promise.resolve(); + }); + expect(result.current.canRate).toBe(undefined); }); @@ -39,7 +55,12 @@ it('should set canRate to false if transactions_count is less than 5', async() = isPlaceholderData: false, data: { transactions_count: 4 }, }); + const { result } = renderHook(() => useRatings(), { wrapper }); + await act(async() => { + await Promise.resolve(); + }); + expect(result.current.canRate).toBe(false); }); @@ -49,7 +70,12 @@ it('should set canRate to false if isPlaceholderData is true', async() => { isPlaceholderData: true, data: { transactions_count: 5 }, }); + const { result } = renderHook(() => useRatings(), { wrapper }); + await act(async() => { + await Promise.resolve(); + }); + expect(result.current.canRate).toBe(false); }); @@ -59,7 +85,12 @@ it('should set canRate to false if data is undefined', async() => { isPlaceholderData: false, data: undefined, }); - const { result } = renderHook(() => useRatings()); + + const { result } = renderHook(() => useRatings(), { wrapper }); + await act(async() => { + await Promise.resolve(); + }); + expect(result.current.canRate).toBe(false); }); @@ -69,6 +100,11 @@ it('should set canRate to false if transactions_count is undefined', async() => isPlaceholderData: false, data: {}, }); - const { result } = renderHook(() => useRatings()); + + const { result } = renderHook(() => useRatings(), { wrapper }); + await act(async() => { + await Promise.resolve(); + }); + expect(result.current.canRate).toBe(false); }); diff --git a/ui/marketplace/Rating/useRatings.tsx b/ui/marketplace/Rating/useRatings.tsx index 4c070c46bc..8c0d2c856e 100644 --- a/ui/marketplace/Rating/useRatings.tsx +++ b/ui/marketplace/Rating/useRatings.tsx @@ -6,10 +6,10 @@ import type { AppRating } from 'types/client/marketplace'; import config from 'configs/app'; import useApiQuery from 'lib/api/useApiQuery'; -import useToast from 'lib/hooks/useToast'; import type { EventTypes, EventPayload } from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index'; import { ADDRESS_COUNTERS } from 'stubs/address'; +import { toaster } from 'toolkit/chakra/toaster'; const MIN_TRANSACTION_COUNT = 5; @@ -41,7 +41,6 @@ function formatRatings(data: Airtable.Records) { export default function useRatings() { const { address } = useAccount(); - const toast = useToast(); const addressCountersQuery = useApiQuery<'address_counters', { status: number }>('address_counters', { pathParams: { hash: address }, @@ -68,13 +67,12 @@ export default function useRatings() { const ratings = formatRatings(data); setRatings(ratings); } catch (error) { - toast({ - status: 'error', + toaster.error({ title: 'Error loading ratings', description: 'Please try again later', }); } - }, [ toast ]); + }, [ ]); useEffect(() => { async function fetch() { @@ -97,8 +95,7 @@ export default function useRatings() { }).all(); userRatings = formatRatings(data); } catch (error) { - toast({ - status: 'error', + toaster.error({ title: 'Error loading user ratings', description: 'Please try again later', }); @@ -108,7 +105,7 @@ export default function useRatings() { setIsUserRatingLoading(false); } fetchUserRatings(); - }, [ address, toast ]); + }, [ address ]); useEffect(() => { const isPlaceholderData = addressCountersQuery?.isPlaceholderData; @@ -163,8 +160,7 @@ export default function useRatings() { }); fetchRatings(); - toast({ - status: 'success', + toaster.success({ title: 'Awesome! Thank you 💜', description: 'Your rating improves the service', }); @@ -173,15 +169,14 @@ export default function useRatings() { { Action: 'Rating', Source: source, AppId: appId, Score: rating }, ); } catch (error) { - toast({ - status: 'error', + toaster.error({ title: 'Ooops! Something went wrong', description: 'Please try again later', }); } setIsSending(false); - }, [ address, userRatings, fetchRatings, toast ]); + }, [ address, userRatings, fetchRatings ]); return { ratings, diff --git a/ui/marketplace/__screenshots__/MarketplaceAppInfo.pw.tsx_dark-color-mode_base-view-dark-mode-1.png b/ui/marketplace/__screenshots__/MarketplaceAppInfo.pw.tsx_dark-color-mode_base-view-dark-mode-1.png index 6c912d627e..9ab83b61d3 100644 Binary files a/ui/marketplace/__screenshots__/MarketplaceAppInfo.pw.tsx_dark-color-mode_base-view-dark-mode-1.png and b/ui/marketplace/__screenshots__/MarketplaceAppInfo.pw.tsx_dark-color-mode_base-view-dark-mode-1.png differ diff --git a/ui/marketplace/__screenshots__/MarketplaceAppInfo.pw.tsx_default_base-view-dark-mode-1.png b/ui/marketplace/__screenshots__/MarketplaceAppInfo.pw.tsx_default_base-view-dark-mode-1.png index d6e441a77e..84186c82da 100644 Binary files a/ui/marketplace/__screenshots__/MarketplaceAppInfo.pw.tsx_default_base-view-dark-mode-1.png and b/ui/marketplace/__screenshots__/MarketplaceAppInfo.pw.tsx_default_base-view-dark-mode-1.png differ diff --git a/ui/marketplace/__screenshots__/MarketplaceAppInfo.pw.tsx_default_mobile-base-view-1.png b/ui/marketplace/__screenshots__/MarketplaceAppInfo.pw.tsx_default_mobile-base-view-1.png index 7bb7f5597c..a8c5aaf181 100644 Binary files a/ui/marketplace/__screenshots__/MarketplaceAppInfo.pw.tsx_default_mobile-base-view-1.png and b/ui/marketplace/__screenshots__/MarketplaceAppInfo.pw.tsx_default_mobile-base-view-1.png differ diff --git a/ui/marketplace/__screenshots__/MarketplaceAppModal.pw.tsx_dark-color-mode_base-view-dark-mode-1.png b/ui/marketplace/__screenshots__/MarketplaceAppModal.pw.tsx_dark-color-mode_base-view-dark-mode-1.png index 0d7e74cac7..84385bd4ab 100644 Binary files a/ui/marketplace/__screenshots__/MarketplaceAppModal.pw.tsx_dark-color-mode_base-view-dark-mode-1.png and b/ui/marketplace/__screenshots__/MarketplaceAppModal.pw.tsx_dark-color-mode_base-view-dark-mode-1.png differ diff --git a/ui/marketplace/__screenshots__/MarketplaceAppModal.pw.tsx_default_base-view-dark-mode-1.png b/ui/marketplace/__screenshots__/MarketplaceAppModal.pw.tsx_default_base-view-dark-mode-1.png index 196f4dfaa1..a9f726d481 100644 Binary files a/ui/marketplace/__screenshots__/MarketplaceAppModal.pw.tsx_default_base-view-dark-mode-1.png and b/ui/marketplace/__screenshots__/MarketplaceAppModal.pw.tsx_default_base-view-dark-mode-1.png differ diff --git a/ui/marketplace/__screenshots__/MarketplaceAppModal.pw.tsx_default_mobile-base-view-1.png b/ui/marketplace/__screenshots__/MarketplaceAppModal.pw.tsx_default_mobile-base-view-1.png index 04509513b0..18e605595d 100644 Binary files a/ui/marketplace/__screenshots__/MarketplaceAppModal.pw.tsx_default_mobile-base-view-1.png and b/ui/marketplace/__screenshots__/MarketplaceAppModal.pw.tsx_default_mobile-base-view-1.png differ diff --git a/ui/marketplace/utils.ts b/ui/marketplace/utils.ts index 010a09d9b5..42a5e8a587 100644 --- a/ui/marketplace/utils.ts +++ b/ui/marketplace/utils.ts @@ -1,17 +1,16 @@ import type { NextRouter } from 'next/router'; -import type { SelectOption } from 'ui/shared/select/types'; - import config from 'configs/app'; import getQueryParamString from 'lib/router/getQueryParamString'; import removeQueryParam from 'lib/router/removeQueryParam'; +import type { SelectOption } from 'toolkit/chakra/select'; const feature = config.features.marketplace; -export type SortValue = 'rating_score' | 'rating_count' | 'security_score'; +export type SortValue = 'default' | 'rating_score' | 'rating_count' | 'security_score'; export const SORT_OPTIONS: Array> = [ - { label: 'Default', value: undefined }, + { label: 'Default', value: 'default' }, (feature.isEnabled && feature.rating) && { label: 'Top rated', value: 'rating_score' }, (feature.isEnabled && feature.rating) && { label: 'Most rated', value: 'rating_count' }, (feature.isEnabled && feature.securityReportsUrl) && { label: 'Security score', value: 'security_score' }, diff --git a/ui/messages/ArbitrumL2Messages.tsx b/ui/messages/ArbitrumL2Messages.tsx index 895e69b410..a027da12c7 100644 --- a/ui/messages/ArbitrumL2Messages.tsx +++ b/ui/messages/ArbitrumL2Messages.tsx @@ -1,14 +1,14 @@ -import { Hide, Show } from '@chakra-ui/react'; +import { Box } from '@chakra-ui/react'; import React from 'react'; import useApiQuery from 'lib/api/useApiQuery'; import { rightLineArrow, nbsp } from 'lib/html-entities'; import { ARBITRUM_MESSAGES_ITEM } from 'stubs/arbitrumL2'; import { generateListStub } from 'stubs/utils'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import ArbitrumL2MessagesListItem from 'ui/messages/ArbitrumL2MessagesListItem'; import ArbitrumL2MessagesTable from 'ui/messages/ArbitrumL2MessagesTable'; import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; -import Skeleton from 'ui/shared/chakra/Skeleton'; import DataListDisplay from 'ui/shared/DataListDisplay'; import PageTitle from 'ui/shared/Page/PageTitle'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; @@ -43,7 +43,7 @@ const ArbitrumL2Messages = ({ direction }: Props) => { const content = data?.items ? ( <> - + { data.items.map(((item, index) => ( { direction={ direction } /> ))) } - - + + - + ) : null; @@ -71,7 +71,7 @@ const ArbitrumL2Messages = ({ direction }: Props) => { return ( A total of { countersQuery.data?.toLocaleString() } { type } found @@ -91,11 +91,12 @@ const ArbitrumL2Messages = ({ direction }: Props) => { /> + > + { content } + ); }; diff --git a/ui/messages/ArbitrumL2MessagesListItem.tsx b/ui/messages/ArbitrumL2MessagesListItem.tsx index ef0e1832d0..75fe086765 100644 --- a/ui/messages/ArbitrumL2MessagesListItem.tsx +++ b/ui/messages/ArbitrumL2MessagesListItem.tsx @@ -6,12 +6,12 @@ import type { ArbitrumL2MessagesItem } from 'types/api/arbitrumL2'; import { route } from 'nextjs-routes'; import config from 'configs/app'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Link } from 'toolkit/chakra/link'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1'; -import LinkInternal from 'ui/shared/links/LinkInternal'; import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid'; import ArbitrumL2MessageStatus from 'ui/shared/statusTag/ArbitrumL2MessageStatus'; import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip'; @@ -41,8 +41,6 @@ const ArbitrumL2MessagesListItem = ({ item, isLoading, direction }: Props) => { ) : N/A } @@ -58,8 +56,6 @@ const ArbitrumL2MessagesListItem = ({ item, isLoading, direction }: Props) => { address={{ hash: item.origination_address }} truncation="constant" isLoading={ isLoading } - fontSize="sm" - lineHeight={ 5 } fontWeight={ 600 } /> @@ -68,7 +64,7 @@ const ArbitrumL2MessagesListItem = ({ item, isLoading, direction }: Props) => { Message # - + { item.id } @@ -79,8 +75,6 @@ const ArbitrumL2MessagesListItem = ({ item, isLoading, direction }: Props) => { ) : ( @@ -106,7 +100,7 @@ const ArbitrumL2MessagesListItem = ({ item, isLoading, direction }: Props) => { Status { item.status === 'confirmed' && direction === 'from-rollup' ? - Ready for relay : + Ready for relay : } @@ -116,8 +110,6 @@ const ArbitrumL2MessagesListItem = ({ item, isLoading, direction }: Props) => { ) : ( diff --git a/ui/messages/ArbitrumL2MessagesTable.tsx b/ui/messages/ArbitrumL2MessagesTable.tsx index 4fa8678f23..2aed9ac018 100644 --- a/ui/messages/ArbitrumL2MessagesTable.tsx +++ b/ui/messages/ArbitrumL2MessagesTable.tsx @@ -1,9 +1,8 @@ -import { Table, Tbody, Th, Tr } from '@chakra-ui/react'; import React from 'react'; import type { ArbitrumL2MessagesItem } from 'types/api/arbitrumL2'; -import { default as Thead } from 'ui/shared/TheadSticky'; +import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table'; import type { MessagesDirection } from './ArbitrumL2Messages'; import ArbitrumL2MessagesTableItem from './ArbitrumL2MessagesTableItem'; @@ -17,19 +16,19 @@ import ArbitrumL2MessagesTableItem from './ArbitrumL2MessagesTableItem'; const ArbitrumL2MessagesTable = ({ items, direction, top, isLoading }: Props) => { return ( - - - - { direction === 'to-rollup' && } - { direction === 'from-rollup' && } - - - - - - - - + + + + { direction === 'to-rollup' && L1 block } + { direction === 'from-rollup' && From } + Message # + L2 transaction + Age + Status + L1 transaction + + + { items.map((item, index) => ( isLoading={ isLoading } /> )) } - -
L1 blockFromMessage #L2 transactionAgeStatusL1 transaction
+ + ); }; diff --git a/ui/messages/ArbitrumL2MessagesTableItem.tsx b/ui/messages/ArbitrumL2MessagesTableItem.tsx index 12cb59fd4d..73af5feb54 100644 --- a/ui/messages/ArbitrumL2MessagesTableItem.tsx +++ b/ui/messages/ArbitrumL2MessagesTableItem.tsx @@ -1,4 +1,4 @@ -import { Td, Tr, chakra } from '@chakra-ui/react'; +import { chakra } from '@chakra-ui/react'; import React from 'react'; import type { ArbitrumL2MessagesItem } from 'types/api/arbitrumL2'; @@ -6,12 +6,13 @@ import type { ArbitrumL2MessagesItem } from 'types/api/arbitrumL2'; import { route } from 'nextjs-routes'; import config from 'configs/app'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Link } from 'toolkit/chakra/link'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import { TableCell, TableRow } from 'toolkit/chakra/table'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1'; -import LinkInternal from 'ui/shared/links/LinkInternal'; import ArbitrumL2MessageStatus from 'ui/shared/statusTag/ArbitrumL2MessageStatus'; import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip'; @@ -30,83 +31,75 @@ const ArbitrumL2MessagesTableItem = ({ item, direction, isLoading }: Props) => { const l2TxHash = direction === 'from-rollup' ? item.origination_transaction_hash : item.completion_transaction_hash; return ( - + { direction === 'to-rollup' && ( - + { item.origination_transaction_block_number ? ( - ) : N/A } - + ) : N/A } + ) } { direction === 'from-rollup' && ( - + - + ) } - - + + { item.id } - - + + { l2TxHash ? ( ) : ( - + N/A ) } - - + + - - + + { item.status === 'confirmed' && direction === 'from-rollup' ? - Ready for relay : + Ready for relay : } - - + + { l1TxHash ? ( ) : ( - + N/A ) } - - + + ); }; diff --git a/ui/mudWorlds/MudWorldsListItem.tsx b/ui/mudWorlds/MudWorldsListItem.tsx index 6d66cf1043..b330b289bf 100644 --- a/ui/mudWorlds/MudWorldsListItem.tsx +++ b/ui/mudWorlds/MudWorldsListItem.tsx @@ -6,7 +6,7 @@ import type { MudWorldItem } from 'types/api/mudWorlds'; import config from 'configs/app'; import { currencyUnits } from 'lib/units'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; @@ -31,15 +31,15 @@ const MudWorldsListItem = ({ mr={ 2 } truncation="constant_long" /> - - { `Balance ${ currencyUnits.ether }` } - + + { `Balance ${ currencyUnits.ether }` } + { addressBalance.dp(8).toFormat() } - - Txn count - + + Txn count + { Number(item.transaction_count).toLocaleString() } diff --git a/ui/mudWorlds/MudWorldsTable.tsx b/ui/mudWorlds/MudWorldsTable.tsx index e825757abc..4b4d0367d4 100644 --- a/ui/mudWorlds/MudWorldsTable.tsx +++ b/ui/mudWorlds/MudWorldsTable.tsx @@ -1,10 +1,9 @@ -import { Table, Tbody, Th, Tr } from '@chakra-ui/react'; import React from 'react'; import type { MudWorldItem } from 'types/api/mudWorlds'; import { currencyUnits } from 'lib/units'; -import { default as Thead } from 'ui/shared/TheadSticky'; +import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table'; import MudWorldsTableItem from './MudWorldsTableItem'; @@ -16,15 +15,15 @@ type Props = { const MudWorldsTable = ({ items, top, isLoading }: Props) => { return ( - - - - - - - - - + + + + Address + { `Balance ${ currencyUnits.ether }` } + Txn count + + + { items.map((item, index) => ( { isLoading={ isLoading } /> )) } - -
Address{ `Balance ${ currencyUnits.ether }` }Txn count
+ + ); }; diff --git a/ui/mudWorlds/MudWorldsTableItem.tsx b/ui/mudWorlds/MudWorldsTableItem.tsx index 857d442453..53ff0260c1 100644 --- a/ui/mudWorlds/MudWorldsTableItem.tsx +++ b/ui/mudWorlds/MudWorldsTableItem.tsx @@ -1,11 +1,12 @@ -import { Text, Td, Tr } from '@chakra-ui/react'; +import { Text } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; import React from 'react'; import type { MudWorldItem } from 'types/api/mudWorlds'; import config from 'configs/app'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import { TableCell, TableRow } from 'toolkit/chakra/table'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; type Props = { item: MudWorldItem; isLoading?: boolean }; @@ -15,22 +16,22 @@ const MudWorldsTableItem = ({ item, isLoading }: Props) => { const addressBalanceChunks = addressBalance.dp(8).toFormat().split('.'); return ( - - + + - - - + + + { addressBalanceChunks[0] + (addressBalanceChunks[1] ? '.' : '') } - { addressBalanceChunks[1] } + { addressBalanceChunks[1] } - - - + + + { Number(item.transaction_count).toLocaleString() } - - + + ); }; diff --git a/ui/myProfile/MyProfileEmail.tsx b/ui/myProfile/MyProfileEmail.tsx index 345dd42d70..07748d33d2 100644 --- a/ui/myProfile/MyProfileEmail.tsx +++ b/ui/myProfile/MyProfileEmail.tsx @@ -1,4 +1,4 @@ -import { Button, chakra, Heading, useDisclosure } from '@chakra-ui/react'; +import { chakra } from '@chakra-ui/react'; import type { UseQueryResult } from '@tanstack/react-query'; import React from 'react'; import type { SubmitHandler } from 'react-hook-form'; @@ -11,8 +11,11 @@ import config from 'configs/app'; import useApiFetch from 'lib/api/useApiFetch'; import getErrorMessage from 'lib/errors/getErrorMessage'; import getErrorObjPayload from 'lib/errors/getErrorObjPayload'; -import useToast from 'lib/hooks/useToast'; import * as mixpanel from 'lib/mixpanel'; +import { Button } from 'toolkit/chakra/button'; +import { Heading } from 'toolkit/chakra/heading'; +import { toaster } from 'toolkit/chakra/toaster'; +import { useDisclosure } from 'toolkit/hooks/useDisclosure'; import FormFieldText from 'ui/shared/forms/fields/FormFieldText'; import ReCaptcha from 'ui/shared/reCaptcha/ReCaptcha'; import useReCaptcha from 'ui/shared/reCaptcha/useReCaptcha'; @@ -33,7 +36,6 @@ interface Props { const MyProfileEmail = ({ profileQuery }: Props) => { const authModal = useDisclosure(); const apiFetch = useApiFetch(); - const toast = useToast(); const recaptcha = useReCaptcha(); const formApi = useForm({ @@ -65,25 +67,24 @@ const MyProfileEmail = ({ profileQuery }: Props) => { authModal.onOpen(); } catch (error) { const apiError = getErrorObjPayload<{ message: string }>(error); - toast({ - status: 'error', + toaster.error({ title: 'Error', description: apiError?.message || getErrorMessage(error) || 'Something went wrong', }); } - }, [ apiFetch, authModal, toast, recaptcha ]); + }, [ apiFetch, authModal, recaptcha ]); const hasDirtyFields = Object.keys(formApi.formState.dirtyFields).length > 0; return (
- Notifications + Notifications - name="name" placeholder="Name" isReadOnly mb={ 3 }/> + name="name" placeholder="Name" readOnly mb={ 3 }/> { size="sm" variant="outline" type="submit" - isDisabled={ formApi.formState.isSubmitting || !hasDirtyFields } - isLoading={ formApi.formState.isSubmitting } + disabled={ formApi.formState.isSubmitting || !hasDirtyFields } + loading={ formApi.formState.isSubmitting } loadingText="Save changes" > Save changes @@ -104,7 +105,7 @@ const MyProfileEmail = ({ profileQuery }: Props) => { ) } - { authModal.isOpen && ( + { authModal.open && ( ; @@ -14,24 +16,23 @@ interface Props { } const MyProfileWallet = ({ profileQuery, onAddWallet }: Props) => { - const bgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50'); return (
- My linked wallet + My linked wallet This wallet address is used for login{ ' ' } { config.features.rewards.isEnabled && ( <> and participation in the Merits Program. - + Learn more - + ) } { profileQuery.data?.address_hash ? ( - + { - const { control } = useFormContext(); - const { field, fieldState, formState } = useController({ - control, - name: 'email', - rules: { required: true, pattern: EMAIL_REGEXP }, - }); - - const isDisabled = formState.isSubmitting; - const isVerified = defaultValue && field.value === defaultValue; return ( - - - - - { isVerified && ( - - - - ) } - - Email for watch list notifications and private tags - + + name="email" + placeholder="Email" + required + readOnly={ isReadOnly } + helperText="Email for watch list notifications and private tags" + group={{ + endElement: ({ field }) => { + const isVerified = defaultValue && field.value === defaultValue; + return isVerified ? : null; + }, + }} + /> ); }; diff --git a/ui/nameDomain/NameDomainDetails.tsx b/ui/nameDomain/NameDomainDetails.tsx index 851b7dbd4b..fabf0f1237 100644 --- a/ui/nameDomain/NameDomainDetails.tsx +++ b/ui/nameDomain/NameDomainDetails.tsx @@ -1,4 +1,4 @@ -import { Grid, Tooltip, Flex } from '@chakra-ui/react'; +import { Grid, Flex } from '@chakra-ui/react'; import type { UseQueryResult } from '@tanstack/react-query'; import React from 'react'; @@ -10,12 +10,13 @@ import config from 'configs/app'; import type { ResourceError } from 'lib/api/resources'; import dayjs from 'lib/date/dayjs'; import stripTrailingSlash from 'lib/stripTrailingSlash'; -import Skeleton from 'ui/shared/chakra/Skeleton'; -import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem'; +import { Link } from 'toolkit/chakra/link'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import { Tooltip } from 'toolkit/chakra/tooltip'; +import * as DetailedInfo from 'ui/shared/DetailedInfo/DetailedInfo'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import NftEntity from 'ui/shared/entities/nft/NftEntity'; import IconSvg from 'ui/shared/IconSvg'; -import LinkInternal from 'ui/shared/links/LinkInternal'; import TextSeparator from 'ui/shared/TextSeparator'; import NameDomainDetailsAlert from './details/NameDomainDetailsAlert'; @@ -37,78 +38,78 @@ const NameDomainDetails = ({ query }: Props) => { { query.data?.registration_date && ( <> - Registration date - - + + - + { dayjs(query.data.registration_date).format('llll') } - + ) } { query.data?.expiry_date && ( <> - Expiration date - - + + { hasExpired && ( <> - + { dayjs(query.data.expiry_date).fromNow() } ) } - + { dayjs(query.data.expiry_date).format('llll') } - + - + ) } { query.data?.resolver_address && ( <> - Resolver - - + - + ) } { query.data?.registrant && ( <> - Registrant - - + @@ -116,28 +117,28 @@ const NameDomainDetails = ({ query }: Props) => { address={ query.data.registrant } isLoading={ isLoading } /> - - + - + - + ) } { query.data?.owner && ( <> - Owner - - + @@ -145,28 +146,28 @@ const NameDomainDetails = ({ query }: Props) => { address={ query.data.owner } isLoading={ isLoading } /> - - + - + - + ) } { query.data?.wrapped_owner && ( <> - Manager - - + @@ -174,16 +175,16 @@ const NameDomainDetails = ({ query }: Props) => { address={ query.data.wrapped_owner } isLoading={ isLoading } /> - - + - + - + ) } @@ -199,37 +200,37 @@ const NameDomainDetails = ({ query }: Props) => { return ( - { token.type === bens.TokenType.WRAPPED_DOMAIN_TOKEN ? 'Wrapped token ID' : 'Token ID' } - - + - + ); }) } { otherAddresses.length > 0 && ( <> - Other addresses - - + { otherAddresses.map(([ type, address ]) => ( - { type } + { type } { /> )) } - + ) } diff --git a/ui/nameDomain/NameDomainExpiryStatus.tsx b/ui/nameDomain/NameDomainExpiryStatus.tsx index 4a6460d068..6ee6318998 100644 --- a/ui/nameDomain/NameDomainExpiryStatus.tsx +++ b/ui/nameDomain/NameDomainExpiryStatus.tsx @@ -23,7 +23,7 @@ const NameDomainExpiryStatus = ({ date }: Props) => { return { diff } days left; } - return Expires { dayjs(date).fromNow() }; + return Expires { dayjs(date).fromNow() }; }; export default React.memo(NameDomainExpiryStatus); diff --git a/ui/nameDomain/NameDomainHistory.tsx b/ui/nameDomain/NameDomainHistory.tsx index 40780267f6..6fd83002de 100644 --- a/ui/nameDomain/NameDomainHistory.tsx +++ b/ui/nameDomain/NameDomainHistory.tsx @@ -1,4 +1,4 @@ -import { Box, Hide, Show } from '@chakra-ui/react'; +import { Box } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import React from 'react'; @@ -22,7 +22,7 @@ const NameDomainHistory = ({ domain }: Props) => { const router = useRouter(); const domainName = getQueryParamString(router.query.name); - const [ sort, setSort ] = React.useState(); + const [ sort, setSort ] = React.useState('default'); const { isPlaceholderData, isError, data } = useApiQuery('domain_events', { pathParams: { name: domainName, chainId: config.chain.id }, @@ -31,11 +31,10 @@ const NameDomainHistory = ({ domain }: Props) => { }, }); - const handleSortToggle = React.useCallback((event: React.MouseEvent) => { + const handleSortToggle = React.useCallback((field: SortField) => { if (isPlaceholderData) { return; } - const field = (event.currentTarget as HTMLDivElement).getAttribute('data-field') as SortField | undefined; if (field) { setSort(getNextSortValue(field)); @@ -44,19 +43,17 @@ const NameDomainHistory = ({ domain }: Props) => { const content = ( <> - - - { data?.items.map((item, index) => ( - - )) } - - - + + { data?.items.map((item, index) => ( + + )) } + + { sort={ sort } onSortToggle={ handleSortToggle } /> - + ); return ( + > + { content } + ); }; diff --git a/ui/nameDomain/details/NameDomainDetailsAlert.tsx b/ui/nameDomain/details/NameDomainDetailsAlert.tsx index 02b74ec93f..101763127b 100644 --- a/ui/nameDomain/details/NameDomainDetailsAlert.tsx +++ b/ui/nameDomain/details/NameDomainDetailsAlert.tsx @@ -1,9 +1,9 @@ -import { Alert } from '@chakra-ui/react'; import React from 'react'; import type * as bens from '@blockscout/bens-types'; -import LinkExternal from 'ui/shared/links/LinkExternal'; +import { Alert } from 'toolkit/chakra/alert'; +import { Link } from 'toolkit/chakra/link'; interface Props { data: bens.DetailedDomain | undefined; @@ -16,11 +16,11 @@ const NameDomainDetailsAlert = ({ data }: Props) => { } return ( - + The domain name is resolved offchain using - { data.stored_offchain && EIP-3668: CCIP Read } + { data.stored_offchain && EIP-3668: CCIP Read } { data.stored_offchain && data.resolved_with_wildcard && & } - { data.resolved_with_wildcard && EIP-2544: Wildcard Resolution } + { data.resolved_with_wildcard && EIP-2544: Wildcard Resolution } ); }; diff --git a/ui/nameDomain/history/NameDomainHistoryListItem.tsx b/ui/nameDomain/history/NameDomainHistoryListItem.tsx index ebc43e607e..843f46326f 100644 --- a/ui/nameDomain/history/NameDomainHistoryListItem.tsx +++ b/ui/nameDomain/history/NameDomainHistoryListItem.tsx @@ -6,7 +6,7 @@ import { route } from 'nextjs-routes'; import config from 'configs/app'; import stripTrailingSlash from 'lib/stripTrailingSlash'; -import Tag from 'ui/shared/chakra/Tag'; +import { Badge } from 'toolkit/chakra/badge'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid'; @@ -40,7 +40,7 @@ const NameDomainHistoryListItem = ({ isLoading, domain, event }: Props) => { @@ -58,7 +58,7 @@ const NameDomainHistoryListItem = ({ isLoading, domain, event }: Props) => { <> Method - { event.action } + { event.action } ) } diff --git a/ui/nameDomain/history/NameDomainHistoryTable.tsx b/ui/nameDomain/history/NameDomainHistoryTable.tsx index 90fc301915..7b115d076d 100644 --- a/ui/nameDomain/history/NameDomainHistoryTable.tsx +++ b/ui/nameDomain/history/NameDomainHistoryTable.tsx @@ -1,60 +1,49 @@ -import { Table, Tbody, Tr, Th, Link } from '@chakra-ui/react'; import React from 'react'; import type * as bens from '@blockscout/bens-types'; -import IconSvg from 'ui/shared/IconSvg'; -import { default as Thead } from 'ui/shared/TheadSticky'; +import { TableBody, TableColumnHeader, TableColumnHeaderSortable, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table'; import NameDomainHistoryTableItem from './NameDomainHistoryTableItem'; -import type { Sort } from './utils'; +import type { SortField, Sort } from './utils'; import { sortFn } from './utils'; interface Props { history: bens.ListDomainEventsResponse | undefined; domain: bens.DetailedDomain | undefined; isLoading?: boolean; - sort: Sort | undefined; - onSortToggle: (event: React.MouseEvent) => void; + sort: Sort; + onSortToggle: (field: SortField) => void; } const NameDomainHistoryTable = ({ history, domain, isLoading, sort, onSortToggle }: Props) => { - const sortIconTransform = sort?.includes('asc') ? 'rotate(-90deg)' : 'rotate(90deg)'; - return ( - - - - - - - - - - + + + + Txn hash + + Age + + From + Method + + + { history?.items .slice() .sort(sortFn(sort)) .map((item, index) => ) } - -
Txn hash - - { sort?.includes('timestamp') && ( - - ) } - Age - - FromMethod
+ + ); }; diff --git a/ui/nameDomain/history/NameDomainHistoryTableItem.tsx b/ui/nameDomain/history/NameDomainHistoryTableItem.tsx index cd287cc98d..db47390388 100644 --- a/ui/nameDomain/history/NameDomainHistoryTableItem.tsx +++ b/ui/nameDomain/history/NameDomainHistoryTableItem.tsx @@ -1,4 +1,3 @@ -import { Tr, Td } from '@chakra-ui/react'; import React from 'react'; import type * as bens from '@blockscout/bens-types'; @@ -7,7 +6,8 @@ import { route } from 'nextjs-routes'; import config from 'configs/app'; import stripTrailingSlash from 'lib/stripTrailingSlash'; -import Tag from 'ui/shared/chakra/Tag'; +import { Badge } from 'toolkit/chakra/badge'; +import { TableCell, TableRow } from 'toolkit/chakra/table'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip'; @@ -29,8 +29,8 @@ const NameDomainHistoryTableItem = ({ isLoading, event, domain }: Props) => { }; return ( - - + + { noIcon truncation="constant_long" /> - - + + - - + + { event.from_address && } - - - { event.action && { event.action } } - - + + + { event.action && { event.action } } + + ); }; diff --git a/ui/nameDomain/history/utils.ts b/ui/nameDomain/history/utils.ts index 585856901e..97e33b7b3e 100644 --- a/ui/nameDomain/history/utils.ts +++ b/ui/nameDomain/history/utils.ts @@ -3,10 +3,10 @@ import type * as bens from '@blockscout/bens-types'; import getNextSortValueShared from 'ui/shared/sort/getNextSortValue'; export type SortField = 'timestamp'; -export type Sort = `${ SortField }-asc` | `${ SortField }-desc`; +export type Sort = `${ SortField }-asc` | `${ SortField }-desc` | 'default'; -const SORT_SEQUENCE: Record> = { - timestamp: [ 'timestamp-desc', 'timestamp-asc', undefined ], +const SORT_SEQUENCE: Record> = { + timestamp: [ 'timestamp-desc', 'timestamp-asc', 'default' ], }; export const getNextSortValue = (getNextSortValueShared).bind(undefined, SORT_SEQUENCE); diff --git a/ui/nameDomains/NameDomainsActionBar.tsx b/ui/nameDomains/NameDomainsActionBar.tsx index 8518abceb2..bee0614241 100644 --- a/ui/nameDomains/NameDomainsActionBar.tsx +++ b/ui/nameDomains/NameDomainsActionBar.tsx @@ -1,4 +1,4 @@ -import { Box, Checkbox, CheckboxGroup, Flex, HStack, Image, Link, Text, VStack, chakra } from '@chakra-ui/react'; +import { Box, Fieldset, Flex, HStack, Text, chakra, createListCollection } from '@chakra-ui/react'; import React from 'react'; import type * as bens from '@blockscout/bens-types'; @@ -6,6 +6,9 @@ import type { EnsDomainLookupFiltersOptions } from 'types/api/ens'; import type { PaginationParams } from 'ui/shared/pagination/types'; import useIsInitialLoading from 'lib/hooks/useIsInitialLoading'; +import { Button } from 'toolkit/chakra/button'; +import { Checkbox, CheckboxGroup } from 'toolkit/chakra/checkbox'; +import { Image } from 'toolkit/chakra/image'; import ActionBar from 'ui/shared/ActionBar'; import FilterInput from 'ui/shared/filters/FilterInput'; import PopoverFilter from 'ui/shared/filters/PopoverFilter'; @@ -16,6 +19,8 @@ import Sort from 'ui/shared/sort/Sort'; import type { Sort as TSort } from './utils'; import { SORT_OPTIONS } from './utils'; +const sortCollection = createListCollection({ items: SORT_OPTIONS }); + interface Props { pagination: PaginationParams; searchTerm: string | undefined; @@ -25,8 +30,8 @@ interface Props { protocolsData: Array | undefined; protocolsFilterValue: Array; onProtocolsFilterChange: (nextValue: Array) => void; - sort: TSort | undefined; - onSortChange: (nextValue: TSort | undefined) => void; + sort: TSort; + onSortChange: (nextValue: TSort) => void; isLoading: boolean; isAddressSearch: boolean; } @@ -51,11 +56,11 @@ const NameDomainsActionBar = ({ ); @@ -63,7 +68,15 @@ const NameDomainsActionBar = ({ onProtocolsFilterChange([]); }, [ onProtocolsFilterChange ]); - const filterGroupDivider = ; + const handleSortChange = React.useCallback(({ value }: { value: Array }) => { + onSortChange(value[0] as TSort); + }, [ onSortChange ]); + + const handleFilterValueChange = React.useCallback((value: Array) => { + onFilterValueChange(value as EnsDomainLookupFiltersOptions); + }, [ onFilterValueChange ]); + + const filterGroupDivider = ; const appliedFiltersNum = filterValue.length + (protocolsData && protocolsData.length > 1 ? protocolsFilterValue.length : 0); @@ -72,64 +85,62 @@ const NameDomainsActionBar = ({
{ protocolsData && protocolsData.length > 1 && ( <> - - Protocol - + Protocol + - - - { protocolsData.map((protocol) => { - const topLevelDomains = protocol.tld_list.map((domain) => `.${ domain }`).join(' '); - return ( - - - { } - fallbackStrategy={ protocol.icon_url ? 'onError' : 'beforeLoadOrError' } - /> - { protocol.short_name } - { topLevelDomains } - - - ); - }) } - + + { protocolsData.map((protocol) => { + const topLevelDomains = protocol.tld_list.map((domain) => `.${ domain }`).join(' '); + return ( + + + { } + /> + { protocol.short_name } + { topLevelDomains } + + + ); + }) } { filterGroupDivider } ) } - - Address - - Owned by - - - Resolved to address - - { filterGroupDivider } - Status - - Include expired - - + + + + Address + + Owned by + + + Resolved to address + + { filterGroupDivider } + Status + + Include expired + + + +
); @@ -137,16 +148,16 @@ const NameDomainsActionBar = ({ const sortButton = ( ); return ( <> - + { filter } { sortButton } { searchInput } @@ -155,7 +166,7 @@ const NameDomainsActionBar = ({ mt={ -6 } display={{ base: pagination.isVisible ? 'flex' : 'none', lg: 'flex' }} > - + { filter } { searchInput } diff --git a/ui/nameDomains/NameDomainsListItem.tsx b/ui/nameDomains/NameDomainsListItem.tsx index d7e5500b24..9295a9827b 100644 --- a/ui/nameDomains/NameDomainsListItem.tsx +++ b/ui/nameDomains/NameDomainsListItem.tsx @@ -3,8 +3,8 @@ import React from 'react'; import type * as bens from '@blockscout/bens-types'; import dayjs from 'lib/date/dayjs'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import NameDomainExpiryStatus from 'ui/nameDomain/NameDomainExpiryStatus'; -import Skeleton from 'ui/shared/chakra/Skeleton'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import EnsEntity from 'ui/shared/entities/ens/EnsEntity'; import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid'; @@ -41,7 +41,7 @@ const NameDomainsListItem = ({ <> Registered on - +
{ dayjs(registrationDate).format('lll') }
{ dayjs(registrationDate).fromNow() }
@@ -53,7 +53,7 @@ const NameDomainsListItem = ({ <> Expiration date - +
{ dayjs(expiryDate).format('lll') }
diff --git a/ui/nameDomains/NameDomainsTable.tsx b/ui/nameDomains/NameDomainsTable.tsx index 45f20d4078..05ebd46abc 100644 --- a/ui/nameDomains/NameDomainsTable.tsx +++ b/ui/nameDomains/NameDomainsTable.tsx @@ -1,54 +1,43 @@ -import { Table, Tbody, Tr, Th, Link } from '@chakra-ui/react'; import React from 'react'; import type * as bens from '@blockscout/bens-types'; +import { TableBody, TableColumnHeader, TableColumnHeaderSortable, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table'; import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; -import IconSvg from 'ui/shared/IconSvg'; -import { default as Thead } from 'ui/shared/TheadSticky'; import NameDomainsTableItem from './NameDomainsTableItem'; -import { type Sort } from './utils'; +import type { SortField, Sort } from './utils'; interface Props { data: bens.LookupDomainNameResponse | undefined; isLoading?: boolean; - sort: Sort | undefined; - onSortToggle: (event: React.MouseEvent) => void; + sort: Sort; + onSortToggle: (field: SortField) => void; } const NameDomainsTable = ({ data, isLoading, sort, onSortToggle }: Props) => { - const sortIconTransform = sort?.toLowerCase().includes('asc') ? 'rotate(-90deg)' : 'rotate(90deg)'; - return ( - - - - - - - - - - + + + + Domain + Address + + Registered on + + Expiration date + + + { data?.items.map((item, index) => ) } - -
DomainAddress - - { sort?.includes('registration_date') && ( - - ) } - Registered on - - Expiration date
+ + ); }; diff --git a/ui/nameDomains/NameDomainsTableItem.tsx b/ui/nameDomains/NameDomainsTableItem.tsx index 5ba1f2f3e4..1868526bf6 100644 --- a/ui/nameDomains/NameDomainsTableItem.tsx +++ b/ui/nameDomains/NameDomainsTableItem.tsx @@ -1,11 +1,12 @@ -import { chakra, Tr, Td } from '@chakra-ui/react'; +import { chakra } from '@chakra-ui/react'; import React from 'react'; import type * as bens from '@blockscout/bens-types'; import dayjs from 'lib/date/dayjs'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import { TableCell, TableRow } from 'toolkit/chakra/table'; import NameDomainExpiryStatus from 'ui/nameDomain/NameDomainExpiryStatus'; -import Skeleton from 'ui/shared/chakra/Skeleton'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import EnsEntity from 'ui/shared/entities/ens/EnsEntity'; @@ -23,30 +24,30 @@ const NameDomainsTableItem = ({ }: Props) => { return ( - - + + - - + + { resolvedAddress && } - - + + { registrationDate && ( - + { dayjs(registrationDate).format('lll') } - { dayjs(registrationDate).fromNow() } + { dayjs(registrationDate).fromNow() } ) } - - + + { expiryDate && ( - + { dayjs(expiryDate).format('lll') } ) } - - + + ); }; diff --git a/ui/nameDomains/utils.ts b/ui/nameDomains/utils.ts index 38f9f2c91d..7e6cb77235 100644 --- a/ui/nameDomains/utils.ts +++ b/ui/nameDomains/utils.ts @@ -1,19 +1,19 @@ import type { EnsLookupSorting } from 'types/api/ens'; -import type { SelectOption } from 'ui/shared/select/types'; +import type { SelectOption } from 'toolkit/chakra/select'; import getNextSortValueShared from 'ui/shared/sort/getNextSortValue'; export type SortField = EnsLookupSorting['sort']; -export type Sort = `${ EnsLookupSorting['sort'] }-${ EnsLookupSorting['order'] }`; +export type Sort = `${ EnsLookupSorting['sort'] }-${ EnsLookupSorting['order'] }` | 'default'; export const SORT_OPTIONS: Array> = [ - { label: 'Default', value: undefined }, + { label: 'Default', value: 'default' }, { label: 'Registered on descending', value: 'registration_date-DESC' }, { label: 'Registered on ascending', value: 'registration_date-ASC' }, ]; -const SORT_SEQUENCE: Record> = { - registration_date: [ 'registration_date-DESC', 'registration_date-ASC', undefined ], +const SORT_SEQUENCE: Record> = { + registration_date: [ 'registration_date-DESC', 'registration_date-ASC', 'default' ], }; export const getNextSortValue = (getNextSortValueShared).bind(undefined, SORT_SEQUENCE); diff --git a/ui/outputRoots/optimisticL2/OptimisticL2OutputRootsListItem.tsx b/ui/outputRoots/optimisticL2/OptimisticL2OutputRootsListItem.tsx index d4a9eebecd..d3511e31a7 100644 --- a/ui/outputRoots/optimisticL2/OptimisticL2OutputRootsListItem.tsx +++ b/ui/outputRoots/optimisticL2/OptimisticL2OutputRootsListItem.tsx @@ -4,7 +4,7 @@ import React from 'react'; import type { OptimisticL2OutputRootsItem } from 'types/api/optimisticL2'; import config from 'configs/app'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import CopyToClipboard from 'ui/shared/CopyToClipboard'; import BlockEntityL2 from 'ui/shared/entities/block/BlockEntityL2'; import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1'; @@ -26,7 +26,7 @@ const OptimisticL2OutputRootsListItem = ({ item, isLoading }: Props) => { L2 output index - { item.l2_output_index } + { item.l2_output_index } Age @@ -43,8 +43,6 @@ const OptimisticL2OutputRootsListItem = ({ item, isLoading }: Props) => {
@@ -54,8 +52,6 @@ const OptimisticL2OutputRootsListItem = ({ item, isLoading }: Props) => {
@@ -63,7 +59,7 @@ const OptimisticL2OutputRootsListItem = ({ item, isLoading }: Props) => { Output root - + diff --git a/ui/outputRoots/optimisticL2/OptimisticL2OutputRootsTable.tsx b/ui/outputRoots/optimisticL2/OptimisticL2OutputRootsTable.tsx index e96d22a01f..16136bffb0 100644 --- a/ui/outputRoots/optimisticL2/OptimisticL2OutputRootsTable.tsx +++ b/ui/outputRoots/optimisticL2/OptimisticL2OutputRootsTable.tsx @@ -1,9 +1,8 @@ -import { Table, Tbody, Th, Tr } from '@chakra-ui/react'; import React from 'react'; import type { OptimisticL2OutputRootsItem } from 'types/api/optimisticL2'; -import { default as Thead } from 'ui/shared/TheadSticky'; +import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table'; import OptimisticL2OutputRootsTableItem from './OptimisticL2OutputRootsTableItem'; @@ -15,17 +14,17 @@ type Props = { const OptimisticL2OutputRootsTable = ({ items, top, isLoading }: Props) => { return ( - - - - - - - - - - - + + + + L2 output index + Age + L2 block # + L1 txn hash + Output root + + + { items.map((item, index) => ( { isLoading={ isLoading } /> )) } - -
L2 output indexAgeL2 block #L1 txn hashOutput root
+ + ); }; diff --git a/ui/outputRoots/optimisticL2/OptimisticL2OutputRootsTableItem.tsx b/ui/outputRoots/optimisticL2/OptimisticL2OutputRootsTableItem.tsx index ae7b3a8cd6..8bfd94026f 100644 --- a/ui/outputRoots/optimisticL2/OptimisticL2OutputRootsTableItem.tsx +++ b/ui/outputRoots/optimisticL2/OptimisticL2OutputRootsTableItem.tsx @@ -1,10 +1,11 @@ -import { Flex, Td, Tr } from '@chakra-ui/react'; +import { Flex } from '@chakra-ui/react'; import React from 'react'; import type { OptimisticL2OutputRootsItem } from 'types/api/optimisticL2'; import config from 'configs/app'; -import Skeleton from 'ui/shared/chakra/Skeleton'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import { TableCell, TableRow } from 'toolkit/chakra/table'; import CopyToClipboard from 'ui/shared/CopyToClipboard'; import BlockEntityL2 from 'ui/shared/entities/block/BlockEntityL2'; import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1'; @@ -21,47 +22,43 @@ const OptimisticL2OutputRootsTableItem = ({ item, isLoading }: Props) => { } return ( - - - { item.l2_output_index } - - + + + { item.l2_output_index } + + - - + + - - + + - - + + - + - - + + ); }; diff --git a/ui/pages/Accounts.tsx b/ui/pages/Accounts.tsx index 602cb676b0..d86a04f72b 100644 --- a/ui/pages/Accounts.tsx +++ b/ui/pages/Accounts.tsx @@ -1,4 +1,4 @@ -import { Hide, Show } from '@chakra-ui/react'; +import { Box } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; import React from 'react'; @@ -45,7 +45,7 @@ const Accounts = () => { const content = data?.items ? ( <> - + { pageStartIndex={ pageStartIndex } isLoading={ isPlaceholderData } /> - - +
+ { data.items.map((item, index) => { return ( { /> ); }) } - + ) : null; @@ -75,11 +75,12 @@ const Accounts = () => { + > + { content } + ); }; diff --git a/ui/pages/AccountsLabelSearch.tsx b/ui/pages/AccountsLabelSearch.tsx index 51e7382b68..dd4ac9621d 100644 --- a/ui/pages/AccountsLabelSearch.tsx +++ b/ui/pages/AccountsLabelSearch.tsx @@ -1,4 +1,4 @@ -import { chakra, Flex, Hide, Show } from '@chakra-ui/react'; +import { Box, chakra, Flex } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import React from 'react'; @@ -7,10 +7,10 @@ import type { EntityTag as TEntityTag, EntityTagType } from 'ui/shared/EntityTag import getQueryParamString from 'lib/router/getQueryParamString'; import { TOP_ADDRESS } from 'stubs/address'; import { generateListStub } from 'stubs/utils'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import AddressesLabelSearchListItem from 'ui/addressesLabelSearch/AddressesLabelSearchListItem'; import AddressesLabelSearchTable from 'ui/addressesLabelSearch/AddressesLabelSearchTable'; import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; -import Skeleton from 'ui/shared/chakra/Skeleton'; import DataListDisplay from 'ui/shared/DataListDisplay'; import EntityTag from 'ui/shared/EntityTags/EntityTag'; import PageTitle from 'ui/shared/Page/PageTitle'; @@ -43,14 +43,14 @@ const AccountsLabelSearch = () => { const content = data?.items ? ( <> - + - - +
+ { data.items.map((item, index) => { return ( { /> ); }) } - + ) : null; @@ -80,10 +80,7 @@ const AccountsLabelSearch = () => { return ( - + Found{ ' ' } { num }{ data?.next_page_params || pagination.page > 1 ? '+' : '' } @@ -102,11 +99,12 @@ const AccountsLabelSearch = () => { + > + { content } + ); }; diff --git a/ui/pages/Address.tsx b/ui/pages/Address.tsx index 4a56abad29..3d52d9bd3e 100644 --- a/ui/pages/Address.tsx +++ b/ui/pages/Address.tsx @@ -1,9 +1,9 @@ -import { Box, Flex, HStack, useColorModeValue } from '@chakra-ui/react'; +import { Box, Flex, HStack } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import React from 'react'; +import type { TabItemRegular } from 'toolkit/components/AdaptiveTabs/types'; import type { EntityTag } from 'ui/shared/EntityTags/types'; -import type { RoutedTab } from 'ui/shared/Tabs/types'; import config from 'configs/app'; import getCheckedSummedAddress from 'lib/address/getCheckedSummedAddress'; @@ -19,6 +19,7 @@ import useSocketMessage from 'lib/socket/useSocketMessage'; import useFetchXStarScore from 'lib/xStarScore/useFetchXStarScore'; import { ADDRESS_TABS_COUNTERS } from 'stubs/address'; import { USER_OPS_ACCOUNT } from 'stubs/userOps'; +import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs'; import AddressAccountHistory from 'ui/address/AddressAccountHistory'; import AddressBlocksValidated from 'ui/address/AddressBlocksValidated'; import AddressCoinBalance from 'ui/address/AddressCoinBalance'; @@ -54,7 +55,6 @@ import sortEntityTags from 'ui/shared/EntityTags/sortEntityTags'; import IconSvg from 'ui/shared/IconSvg'; import NetworkExplorers from 'ui/shared/NetworkExplorers'; import PageTitle from 'ui/shared/Page/PageTitle'; -import RoutedTabs from 'ui/shared/Tabs/RoutedTabs'; const TOKEN_TABS = [ 'tokens_erc20', 'tokens_nfts', 'tokens_nfts_collection', 'tokens_nfts_list' ]; const PREDEFINED_TAG_PRIORITY = 100; @@ -67,7 +67,6 @@ const AddressPageContent = () => { const router = useRouter(); const appProps = useAppContext(); - const tabsScrollRef = React.useRef(null); const hash = getQueryParamString(router.query.hash); const checkDomainName = useCheckDomainNameParam(hash); @@ -142,7 +141,6 @@ const AddressPageContent = () => { }); const isSafeAddress = useIsSafeAddress(!addressQuery.isPlaceholderData && addressQuery.data?.is_contract ? hash : undefined); - const safeIconColor = useColorModeValue('black', 'white'); const xStarQuery = useFetchXStarScore({ hash }); @@ -152,25 +150,55 @@ const AddressPageContent = () => { Boolean(config.features.mudFramework.isEnabled && mudTablesCountQuery.data && mudTablesCountQuery.data > 0), ); - const tabs: Array = React.useMemo(() => { + const tabs: Array = React.useMemo(() => { return [ + { + id: 'index', + title: 'Details', + component: , + }, + addressQuery.data?.is_contract ? { + id: 'contract', + title: () => { + const tabName = addressQuery.data.proxy_type === 'eip7702' ? 'Code' : 'Contract'; + + if (addressQuery.data.is_verified) { + return ( + <> + { tabName } + + + ); + } + + return tabName; + }, + component: ( + + ), + subTabs: CONTRACT_TAB_IDS, + } : undefined, config.features.mudFramework.isEnabled && mudTablesCountQuery.data && mudTablesCountQuery.data > 0 && { id: 'mud', title: 'MUD', count: mudTablesCountQuery.data, - component: , + component: , }, { id: 'txs', title: 'Transactions', count: addressTabsCountersQuery.data?.transactions_count, - component: , + component: , }, txInterpretation.isEnabled && txInterpretation.provider === 'noves' ? { id: 'account_history', title: 'Account history', - component: , + component: , } : undefined, config.features.userOps.isEnabled && Boolean(userOpsAccountQuery.data?.total_ops) ? @@ -186,14 +214,14 @@ const AddressPageContent = () => { id: 'withdrawals', title: 'Withdrawals', count: addressTabsCountersQuery.data?.withdrawals_count, - component: , + component: , } : undefined, { id: 'token_transfers', title: 'Token transfers', count: addressTabsCountersQuery.data?.token_transfers_count, - component: , + component: , }, { id: 'tokens', @@ -206,13 +234,13 @@ const AddressPageContent = () => { id: 'internal_txns', title: 'Internal txns', count: addressTabsCountersQuery.data?.internal_transactions_count, - component: , + component: , }, addressTabsCountersQuery.data?.celo_election_rewards_count ? { id: 'epoch_rewards', title: 'Epoch rewards', count: addressTabsCountersQuery.data?.celo_election_rewards_count, - component: , + component: , } : undefined, { id: 'coin_balance_history', @@ -224,7 +252,7 @@ const AddressPageContent = () => { id: 'blocks_validated', title: `Blocks ${ getNetworkValidationActionText() }`, count: addressTabsCountersQuery.data?.validations_count, - component: , + component: , } : undefined, addressTabsCountersQuery.data?.logs_count ? @@ -232,38 +260,12 @@ const AddressPageContent = () => { id: 'logs', title: 'Logs', count: addressTabsCountersQuery.data?.logs_count, - component: , + component: , } : undefined, - - addressQuery.data?.is_contract ? { - id: 'contract', - title: () => { - const tabName = addressQuery.data.proxy_type === 'eip7702' ? 'Code' : 'Contract'; - - if (addressQuery.data.is_verified) { - return ( - <> - { tabName } - - - ); - } - - return tabName; - }, - component: ( - - ), - subTabs: CONTRACT_TAB_IDS, - } : undefined, ].filter(Boolean); }, [ - addressQuery.data, + addressQuery, contractTabs, addressTabsCountersQuery.data, userOpsAccountQuery.data, @@ -348,10 +350,6 @@ const AddressPageContent = () => { /> ); - const content = (addressQuery.isError || addressQuery.isDegradedData) ? - null : - ; - const backLink = React.useMemo(() => { if (appProps.referrer && appProps.referrer.includes('/accounts')) { return { @@ -380,9 +378,7 @@ const AddressPageContent = () => { @@ -396,20 +392,17 @@ const AddressPageContent = () => { implementations: null, }} isLoading={ isLoading } - fontFamily="heading" - fontSize="lg" - fontWeight={ 500 } + variant="subheading" noLink isSafeAddress={ isSafeAddress } - icon={{ color: isSafeAddress ? safeIconColor : undefined }} - mr={ 4 } + icon={{ color: isSafeAddress ? { _light: 'black', _dark: 'white' } : undefined }} /> { !isLoading && addressQuery.data?.is_contract && addressQuery.data.token && } { !isLoading && !addressQuery.data?.is_contract && config.features.account.isEnabled && ( ) } - + { !isLoading && addressQuery.data?.is_contract && addressQuery.data?.is_verified && config.UI.views.address.solidityscanEnabled && @@ -433,10 +426,7 @@ const AddressPageContent = () => { { !addressMetadataQuery.isPending && } { config.features.metasuites.isEnabled && } - - { /* should stay before tabs to scroll up with pagination */ } - - { content } + ); }; diff --git a/ui/pages/AdvancedFilter.tsx b/ui/pages/AdvancedFilter.tsx index 034a03c04f..76a26aa41e 100644 --- a/ui/pages/AdvancedFilter.tsx +++ b/ui/pages/AdvancedFilter.tsx @@ -1,19 +1,9 @@ import { - Table, - Tbody, - Tr, - Th, - Td, - Thead, Box, Text, - Tag, - TagCloseButton, chakra, Flex, - TagLabel, HStack, - Link, } from '@chakra-ui/react'; import { omit } from 'es-toolkit'; import { useRouter } from 'next/router'; @@ -31,6 +21,9 @@ import getValuesArrayFromQuery from 'lib/getValuesArrayFromQuery'; import getQueryParamString from 'lib/router/getQueryParamString'; import { ADVANCED_FILTER_ITEM } from 'stubs/advancedFilter'; import { generateListStub } from 'stubs/utils'; +import { Link } from 'toolkit/chakra/link'; +import { TableBody, TableCell, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table'; +import { Tag } from 'toolkit/chakra/tag'; import ColumnsButton from 'ui/advancedFilter/ColumnsButton'; import type { ColumnsIds } from 'ui/advancedFilter/constants'; import { TABLE_COLUMNS } from 'ui/advancedFilter/constants'; @@ -145,12 +138,12 @@ const AdvancedFilter = () => { const content = ( - - - + + + { columnsToShow.map(column => { return ( - + ); }) } - - - + + + { data?.items.map((item, index) => ( - - { columnsToShow.map(column => ( - - )) } - + + { columnsToShow.map(column => { + const textAlign = (() => { + if (column.id === 'or_and') { + return 'center'; + } + if (column.isNumeric) { + return 'right'; + } + return 'start'; + })(); + + return ( + + + + ); + }) } + )) } - -
{ searchParams={ data?.search_params } isLoading={ isPlaceholderData } /> -
- -
+ +
); @@ -223,42 +228,33 @@ const AdvancedFilter = () => {
{ filterTags.map(t => ( - - - { t.name }: - { t.value } - - + + { t.value } )) } { filterTags.length === 0 && ( <> - - - Type: - All - + + All - - - Age: - 7d - + + 7d ) } + > + { content } + ); }; diff --git a/ui/pages/ApiKeys.tsx b/ui/pages/ApiKeys.tsx index 25666eba9c..9d2d7d01ac 100644 --- a/ui/pages/ApiKeys.tsx +++ b/ui/pages/ApiKeys.tsx @@ -1,4 +1,4 @@ -import { Box, Button, Link, Text, useDisclosure } from '@chakra-ui/react'; +import { Box, Text } from '@chakra-ui/react'; import React, { useCallback, useState } from 'react'; import type { ApiKey } from 'types/api/account'; @@ -6,12 +6,15 @@ import type { ApiKey } from 'types/api/account'; import useApiQuery from 'lib/api/useApiQuery'; import { space } from 'lib/html-entities'; import { API_KEY } from 'stubs/account'; +import { Button } from 'toolkit/chakra/button'; +import { Link } from 'toolkit/chakra/link'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import { useDisclosure } from 'toolkit/hooks/useDisclosure'; import ApiKeyModal from 'ui/apiKey/ApiKeyModal/ApiKeyModal'; import ApiKeyListItem from 'ui/apiKey/ApiKeyTable/ApiKeyListItem'; import ApiKeyTable from 'ui/apiKey/ApiKeyTable/ApiKeyTable'; import DeleteApiKeyModal from 'ui/apiKey/DeleteApiKeyModal'; import AccountPageDescription from 'ui/shared/AccountPageDescription'; -import Skeleton from 'ui/shared/chakra/Skeleton'; import DataFetchAlert from 'ui/shared/DataFetchAlert'; import PageTitle from 'ui/shared/Page/PageTitle'; import useRedirectForInvalidAuthToken from 'ui/snippets/auth/useRedirectForInvalidAuthToken'; @@ -37,9 +40,9 @@ const ApiKeysPage: React.FC = () => { apiKeyModalProps.onOpen(); }, [ apiKeyModalProps ]); - const onApiKeyModalClose = useCallback(() => { - setApiKeyModalData(undefined); - apiKeyModalProps.onClose(); + const onApiKeyModalOpenChange = useCallback(({ open }: { open: boolean }) => { + !open && setApiKeyModalData(undefined); + apiKeyModalProps.onOpenChange({ open }); }, [ apiKeyModalProps ]); const onDeleteClick = useCallback((data: ApiKey) => { @@ -47,9 +50,9 @@ const ApiKeysPage: React.FC = () => { deleteModalProps.onOpen(); }, [ deleteModalProps ]); - const onDeleteModalClose = useCallback(() => { - setDeleteModalData(undefined); - deleteModalProps.onClose(); + const onDeleteModalOpenChange = useCallback(({ open }: { open: boolean }) => { + !open && setDeleteModalData(undefined); + deleteModalProps.onOpenChange({ open }); }, [ deleteModalProps ]); const description = ( @@ -99,26 +102,25 @@ const ApiKeysPage: React.FC = () => { marginTop={ 8 } flexDir={{ base: 'column', lg: 'row' }} alignItems={{ base: 'start', lg: 'center' }} - isLoaded={ !isPlaceholderData } + loading={ isPlaceholderData } display="inline-flex" columnGap={ 5 } rowGap={ 5 } > { !canAdd && ( - + { `You have added the maximum number of API keys (${ DATA_LIMIT }). Contact us to request additional keys.` } ) } - - { deleteModalData && } + + { deleteModalData && } ); })(); diff --git a/ui/pages/ArbitrumL2Deposits.pw.tsx b/ui/pages/ArbitrumL2Deposits.pw.tsx index af26008a4f..f0ceecacaf 100644 --- a/ui/pages/ArbitrumL2Deposits.pw.tsx +++ b/ui/pages/ArbitrumL2Deposits.pw.tsx @@ -7,6 +7,7 @@ import { test, expect } from 'playwright/lib'; import ArbitrumL2Deposits from './ArbitrumL2Deposits'; test('base view +@mobile', async({ render, mockApiResponse, mockEnvs, mockTextAd }) => { + test.slow(); await mockTextAd(); await mockEnvs(ENVS_MAP.arbitrumRollup); await mockApiResponse('arbitrum_l2_messages', depositsMock.baseResponse, { pathParams: { direction: 'to-rollup' } }); @@ -14,5 +15,5 @@ test('base view +@mobile', async({ render, mockApiResponse, mockEnvs, mockTextAd const component = await render(); - await expect(component).toHaveScreenshot(); + await expect(component).toHaveScreenshot({ timeout: 10_000 }); }); diff --git a/ui/pages/ArbitrumL2TxnBatch.tsx b/ui/pages/ArbitrumL2TxnBatch.tsx index 0babe5ac4d..85bbd88577 100644 --- a/ui/pages/ArbitrumL2TxnBatch.tsx +++ b/ui/pages/ArbitrumL2TxnBatch.tsx @@ -1,7 +1,7 @@ import { useRouter } from 'next/router'; import React from 'react'; -import type { RoutedTab } from 'ui/shared/Tabs/types'; +import type { TabItemRegular } from 'toolkit/components/AdaptiveTabs/types'; import { useAppContext } from 'lib/contexts/app'; import throwOnAbsentParamError from 'lib/errors/throwOnAbsentParamError'; @@ -11,13 +11,13 @@ import getQueryParamString from 'lib/router/getQueryParamString'; import { BLOCK } from 'stubs/block'; import { TX } from 'stubs/tx'; import { generateListStub } from 'stubs/utils'; +import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs'; +import RoutedTabsSkeleton from 'toolkit/components/RoutedTabs/RoutedTabsSkeleton'; import BlocksContent from 'ui/blocks/BlocksContent'; import TextAd from 'ui/shared/ad/TextAd'; import PageTitle from 'ui/shared/Page/PageTitle'; import Pagination from 'ui/shared/pagination/Pagination'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; -import RoutedTabs from 'ui/shared/Tabs/RoutedTabs'; -import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton'; import ArbitrumL2TxnBatchDetails from 'ui/txnBatches/arbitrumL2/ArbitrumL2TxnBatchDetails'; import useBatchQuery from 'ui/txnBatches/arbitrumL2/useBatchQuery'; import TxsWithFrontendSorting from 'ui/txs/TxsWithFrontendSorting'; @@ -81,7 +81,7 @@ const ArbitrumL2TxnBatch = () => { const hasPagination = !isMobile && pagination?.isVisible; - const tabs: Array = React.useMemo(() => ([ + const tabs: Array = React.useMemo(() => ([ { id: 'index', title: 'Details', component: }, { id: 'txs', @@ -117,10 +117,10 @@ const ArbitrumL2TxnBatch = () => { isLoading={ batchQuery.isPlaceholderData } /> { batchQuery.isPlaceholderData ? - : ( + : ( : null } stickyEnabled={ hasPagination } /> diff --git a/ui/pages/ArbitrumL2TxnBatches.tsx b/ui/pages/ArbitrumL2TxnBatches.tsx index 941f348e90..996051a071 100644 --- a/ui/pages/ArbitrumL2TxnBatches.tsx +++ b/ui/pages/ArbitrumL2TxnBatches.tsx @@ -1,11 +1,11 @@ -import { Hide, Show, Text } from '@chakra-ui/react'; +import { Box, Text } from '@chakra-ui/react'; import React from 'react'; import useApiQuery from 'lib/api/useApiQuery'; import { ARBITRUM_L2_TXN_BATCHES_ITEM } from 'stubs/arbitrumL2'; import { generateListStub } from 'stubs/utils'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; -import Skeleton from 'ui/shared/chakra/Skeleton'; import DataListDisplay from 'ui/shared/DataListDisplay'; import PageTitle from 'ui/shared/Page/PageTitle'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; @@ -38,7 +38,7 @@ const ArbitrumL2TxnBatches = () => { const content = data?.items ? ( <> - + { data.items.map(((item, index) => ( { isLoading={ isPlaceholderData } /> ))) } - - +
+ - + ) : null; @@ -59,7 +59,7 @@ const ArbitrumL2TxnBatches = () => { } return ( - + Txn batch #{ data.items[0].number } to #{ data.items[data.items.length - 1].number } @@ -75,11 +75,12 @@ const ArbitrumL2TxnBatches = () => { + > + { content } + ); }; diff --git a/ui/pages/ArbitrumL2TxnWithdrawals.tsx b/ui/pages/ArbitrumL2TxnWithdrawals.tsx index 05fac9a433..02e4dd1a31 100644 --- a/ui/pages/ArbitrumL2TxnWithdrawals.tsx +++ b/ui/pages/ArbitrumL2TxnWithdrawals.tsx @@ -82,7 +82,7 @@ const ArbitrumL2TxnWithdrawals = () => { name="tx_hash" w={{ base: '100%', lg: '700px' }} mt={ 6 } - size="xs" + size="sm" placeholder="Search by transaction hash" initialValue={ searchTerm } onChange={ handleSearchTermChange } @@ -94,13 +94,14 @@ const ArbitrumL2TxnWithdrawals = () => { + > + { content } + ); }; diff --git a/ui/pages/ArbitrumL2Withdrawals.pw.tsx b/ui/pages/ArbitrumL2Withdrawals.pw.tsx index 47873b7efe..d49f869b7e 100644 --- a/ui/pages/ArbitrumL2Withdrawals.pw.tsx +++ b/ui/pages/ArbitrumL2Withdrawals.pw.tsx @@ -7,6 +7,7 @@ import { test, expect } from 'playwright/lib'; import ArbitrumL2Withdrawals from './ArbitrumL2Withdrawals'; test('base view +@mobile', async({ render, mockApiResponse, mockEnvs, mockTextAd }) => { + test.slow(); await mockTextAd(); await mockEnvs(ENVS_MAP.arbitrumRollup); await mockApiResponse('arbitrum_l2_messages', depositsMock.baseResponse, { pathParams: { direction: 'from-rollup' } }); @@ -14,5 +15,5 @@ test('base view +@mobile', async({ render, mockApiResponse, mockEnvs, mockTextAd const component = await render(); - await expect(component).toHaveScreenshot(); + await expect(component).toHaveScreenshot({ timeout: 10_000 }); }); diff --git a/ui/pages/BeaconChainWithdrawals.tsx b/ui/pages/BeaconChainWithdrawals.tsx index e7b8a0cd84..72e25e24c1 100644 --- a/ui/pages/BeaconChainWithdrawals.tsx +++ b/ui/pages/BeaconChainWithdrawals.tsx @@ -1,4 +1,4 @@ -import { Hide, Show, Text } from '@chakra-ui/react'; +import { Box, Text } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; import React from 'react'; @@ -8,8 +8,8 @@ import getCurrencyValue from 'lib/getCurrencyValue'; import { currencyUnits } from 'lib/units'; import { generateListStub } from 'stubs/utils'; import { WITHDRAWAL } from 'stubs/withdrawals'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; -import Skeleton from 'ui/shared/chakra/Skeleton'; import DataListDisplay from 'ui/shared/DataListDisplay'; import PageTitle from 'ui/shared/Page/PageTitle'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; @@ -41,7 +41,7 @@ const Withdrawals = () => { const content = data?.items ? ( <> - + { data.items.map(((item, index) => ( { isLoading={ isPlaceholderData } /> ))) } - - + + - + ) : null; @@ -68,7 +68,7 @@ const Withdrawals = () => { } return ( - + { countersQuery.data && ( { BigNumber(countersQuery.data.withdrawal_count).toFormat() } withdrawals processed @@ -89,11 +89,12 @@ const Withdrawals = () => { /> + > + { content } + ); }; diff --git a/ui/pages/Blob.tsx b/ui/pages/Blob.tsx index 4df70d1ba9..841dd60131 100644 --- a/ui/pages/Blob.tsx +++ b/ui/pages/Blob.tsx @@ -41,7 +41,7 @@ const BlobPageContent = () => { })(); const titleSecondRow = ( - + ); return ( diff --git a/ui/pages/Block.tsx b/ui/pages/Block.tsx index 5a45762d54..7d7024e775 100644 --- a/ui/pages/Block.tsx +++ b/ui/pages/Block.tsx @@ -3,8 +3,8 @@ import { capitalize } from 'es-toolkit'; import { useRouter } from 'next/router'; import React from 'react'; +import type { TabItemRegular } from 'toolkit/components/AdaptiveTabs/types'; import type { PaginationParams } from 'ui/shared/pagination/types'; -import type { RoutedTab } from 'ui/shared/Tabs/types'; import config from 'configs/app'; import { useAppContext } from 'lib/contexts/app'; @@ -13,6 +13,9 @@ import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError'; import useIsMobile from 'lib/hooks/useIsMobile'; import getNetworkValidationActionText from 'lib/networks/getNetworkValidationActionText'; import getQueryParamString from 'lib/router/getQueryParamString'; +import { Skeleton } from 'toolkit/chakra/skeleton'; +import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs'; +import RoutedTabsSkeleton from 'toolkit/components/RoutedTabs/RoutedTabsSkeleton'; import BlockCeloEpochTag from 'ui/block/BlockCeloEpochTag'; import BlockDetails from 'ui/block/BlockDetails'; import BlockEpochRewards from 'ui/block/BlockEpochRewards'; @@ -23,13 +26,10 @@ import useBlockTxsQuery from 'ui/block/useBlockTxsQuery'; import useBlockWithdrawalsQuery from 'ui/block/useBlockWithdrawalsQuery'; import TextAd from 'ui/shared/ad/TextAd'; import ServiceDegradationWarning from 'ui/shared/alerts/ServiceDegradationWarning'; -import Skeleton from 'ui/shared/chakra/Skeleton'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import NetworkExplorers from 'ui/shared/NetworkExplorers'; import PageTitle from 'ui/shared/Page/PageTitle'; import Pagination from 'ui/shared/pagination/Pagination'; -import RoutedTabs from 'ui/shared/Tabs/RoutedTabs'; -import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton'; import TxsWithFrontendSorting from 'ui/txs/TxsWithFrontendSorting'; const TAB_LIST_PROPS = { @@ -57,7 +57,7 @@ const BlockPageContent = () => { (tab === 'withdrawals' && blockWithdrawalsQuery.pagination.isVisible) ); - const tabs: Array = React.useMemo(() => ([ + const tabs: Array = React.useMemo(() => ([ { id: 'index', title: 'Details', @@ -152,7 +152,7 @@ const BlockPageContent = () => { <> { !config.UI.views.block.hiddenFields?.miner && blockQuery.data?.miner && ( { ) } - + ); @@ -179,10 +183,10 @@ const BlockPageContent = () => { secondRow={ titleSecondRow } isLoading={ blockQuery.isPlaceholderData } /> - { blockQuery.isPlaceholderData ? : ( + { blockQuery.isPlaceholderData ? : ( : null } stickyEnabled={ hasPagination } /> diff --git a/ui/pages/BlockCountdown.tsx b/ui/pages/BlockCountdown.tsx index cf16980b9a..baccd7ac8d 100644 --- a/ui/pages/BlockCountdown.tsx +++ b/ui/pages/BlockCountdown.tsx @@ -1,4 +1,4 @@ -import { Box, Center, Flex, Heading, Image, useColorModeValue, Grid, Button } from '@chakra-ui/react'; +import { Box, Center, Flex, Grid } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import React from 'react'; @@ -9,12 +9,15 @@ import dayjs from 'lib/date/dayjs'; import downloadBlob from 'lib/downloadBlob'; import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError'; import getQueryParamString from 'lib/router/getQueryParamString'; +import { Button } from 'toolkit/chakra/button'; +import { Heading } from 'toolkit/chakra/heading'; +import { Image } from 'toolkit/chakra/image'; +import { Link } from 'toolkit/chakra/link'; import BlockCountdownTimer from 'ui/blockCountdown/BlockCountdownTimer'; import createGoogleCalendarLink from 'ui/blockCountdown/createGoogleCalendarLink'; import createIcsFileBlob from 'ui/blockCountdown/createIcsFileBlob'; import ContentLoader from 'ui/shared/ContentLoader'; import IconSvg from 'ui/shared/IconSvg'; -import LinkExternal from 'ui/shared/links/LinkExternal'; import StatsWidget from 'ui/shared/stats/StatsWidget'; import TruncatedValue from 'ui/shared/TruncatedValue'; @@ -27,8 +30,6 @@ type Props = { const BlockCountdown = ({ hideCapybaraRunner }: Props) => { const router = useRouter(); const height = getQueryParamString(router.query.height); - const iconColor = useColorModeValue('gray.300', 'gray.600'); - const buttonBgColor = useColorModeValue('gray.100', 'gray.700'); const { data, isPending, isError, error } = useApiQuery('block_countdown', { queryParams: { @@ -70,43 +71,49 @@ const BlockCountdown = ({ hideCapybaraRunner }: Props) => { - + Estimated target date { dayjs().add(Number(data.result.EstimateTimeInSec), 's').format('llll') } - Google calendar logo Google - + - + { data.result.EstimateTimeInSec && ( { const router = useRouter(); - const iconColor = useColorModeValue('gray.300', 'gray.600'); const handleFormSubmit = React.useCallback((event: React.FormEvent) => { event.preventDefault(); @@ -21,11 +21,14 @@ const BlockCountdownIndex = () => { return (
- + Block countdown @@ -41,7 +44,7 @@ const BlockCountdownIndex = () => { > diff --git a/ui/pages/Blocks.tsx b/ui/pages/Blocks.tsx index 56934c7bfd..dac25f2f46 100644 --- a/ui/pages/Blocks.tsx +++ b/ui/pages/Blocks.tsx @@ -1,17 +1,17 @@ import { useRouter } from 'next/router'; import React from 'react'; -import type { RoutedTab } from 'ui/shared/Tabs/types'; +import type { TabItemRegular } from 'toolkit/components/AdaptiveTabs/types'; import useIsMobile from 'lib/hooks/useIsMobile'; import getQueryParamString from 'lib/router/getQueryParamString'; import { BLOCK } from 'stubs/block'; import { generateListStub } from 'stubs/utils'; +import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs'; import BlocksContent from 'ui/blocks/BlocksContent'; import BlocksTabSlot from 'ui/blocks/BlocksTabSlot'; import PageTitle from 'ui/shared/Page/PageTitle'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; -import RoutedTabs from 'ui/shared/Tabs/RoutedTabs'; const TAB_LIST_PROPS = { marginBottom: 0, @@ -69,7 +69,7 @@ const BlocksPageContent = () => { return blocksQuery.pagination; })(); - const tabs: Array = [ + const tabs: Array = [ { id: 'blocks', title: 'All', component: }, { id: 'reorgs', title: 'Forked', component: }, { id: 'uncles', title: 'Uncles', component: }, @@ -80,7 +80,7 @@ const BlocksPageContent = () => { } stickyEnabled={ !isMobile } /> diff --git a/ui/pages/Chakra.tsx b/ui/pages/Chakra.tsx new file mode 100644 index 0000000000..7680ace9db --- /dev/null +++ b/ui/pages/Chakra.tsx @@ -0,0 +1,97 @@ +import React from 'react'; + +import useIsMobile from 'lib/hooks/useIsMobile'; +import { useColorMode } from 'toolkit/chakra/color-mode'; +import { Switch } from 'toolkit/chakra/switch'; +import { TabsList, TabsRoot, TabsTrigger } from 'toolkit/chakra/tabs'; +import PageTitle from 'ui/shared/Page/PageTitle'; +import AccordionsShowcase from 'ui/showcases/Accordion'; +import AlertShowcase from 'ui/showcases/Alert'; +import BadgeShowcase from 'ui/showcases/Badge'; +import ButtonShowcase from 'ui/showcases/Button'; +import CheckboxShowcase from 'ui/showcases/Checkbox'; +import ClipboardShowcase from 'ui/showcases/Clipboard'; +import CloseButtonShowcase from 'ui/showcases/CloseButton'; +import CollapsibleShowcase from 'ui/showcases/Collapsible'; +import ContentLoaderShowcase from 'ui/showcases/ContentLoader'; +import DialogShowcase from 'ui/showcases/Dialog'; +import FieldShowcase from 'ui/showcases/Field'; +import IconButtonShowcase from 'ui/showcases/IconButton'; +import InputShowcase from 'ui/showcases/Input'; +import LinkShowcase from 'ui/showcases/Link'; +import MenuShowcase from 'ui/showcases/Menu'; +import PaginationShowcase from 'ui/showcases/Pagination'; +import PinInputShowcase from 'ui/showcases/PinInput'; +import PopoverShowcase from 'ui/showcases/Popover'; +import ProgressCircleShowcase from 'ui/showcases/ProgressCircle'; +import RadioShowcase from 'ui/showcases/Radio'; +import RatingShowcase from 'ui/showcases/Rating'; +import SelectShowcase from 'ui/showcases/Select'; +import SkeletonShowcase from 'ui/showcases/Skeleton'; +import SpinnerShowcase from 'ui/showcases/Spinner'; +import SwitchShowcase from 'ui/showcases/Switch'; +import TableShowcase from 'ui/showcases/Table'; +import TabsShowcase from 'ui/showcases/Tabs'; +import TagShowcase from 'ui/showcases/Tag'; +import TextareaShowcase from 'ui/showcases/Textarea'; +import ToastShowcase from 'ui/showcases/Toast'; +import TooltipShowcase from 'ui/showcases/Tooltip'; + +const tabs = [ + { label: 'Accordion', value: 'accordion', component: }, + { label: 'Alert', value: 'alert', component: }, + { label: 'Badge', value: 'badge', component: }, + { label: 'Button', value: 'button', component: }, + { label: 'Checkbox', value: 'checkbox', component: }, + { label: 'Clipboard', value: 'clipboard', component: }, + { label: 'Close button', value: 'close-button', component: }, + { label: 'Collapsible', value: 'collapsible', component: }, + { label: 'Content loader', value: 'content-loader', component: }, + { label: 'Dialog', value: 'dialog', component: }, + { label: 'Icon button', value: 'icon-button', component: }, + { label: 'Input', value: 'input', component: }, + { label: 'Field', value: 'field', component: }, + { label: 'Link', value: 'link', component: }, + { label: 'Menu', value: 'menu', component: }, + { label: 'Pagination', value: 'pagination', component: }, + { label: 'Progress Circle', value: 'progress-circle', component: }, + { label: 'Radio', value: 'radio', component: }, + { label: 'Rating', value: 'rating', component: }, + { label: 'Pin input', value: 'pin-input', component: }, + { label: 'Popover', value: 'popover', component: }, + { label: 'Select', value: 'select', component: }, + { label: 'Skeleton', value: 'skeleton', component: }, + { label: 'Spinner', value: 'spinner', component: }, + { label: 'Switch', value: 'switch', component: }, + { label: 'Table', value: 'table', component: }, + { label: 'Tabs', value: 'tabs', component: }, + { label: 'Tag', value: 'tag', component: }, + { label: 'Textarea', value: 'textarea', component: }, + { label: 'Toast', value: 'toast', component: }, + { label: 'Tooltip', value: 'tooltip', component: }, +]; + +const ChakraShowcases = () => { + const colorMode = useColorMode(); + const isMobile = useIsMobile(); + + return ( + <> + + + Color mode: { colorMode.colorMode } + + + + + { tabs.map((tab) => ( + { tab.label } + )) } + + { tabs.map((tab) => { tab.component }) } + + + ); +}; + +export default React.memo(ChakraShowcases); diff --git a/ui/pages/Chart.tsx b/ui/pages/Chart.tsx index a5172de838..1c54468794 100644 --- a/ui/pages/Chart.tsx +++ b/ui/pages/Chart.tsx @@ -1,4 +1,4 @@ -import { Button, Flex, Link, Text } from '@chakra-ui/react'; +import { createListCollection, Flex, Text } from '@chakra-ui/react'; import type { NextRouter } from 'next/router'; import { useRouter } from 'next/router'; import React from 'react'; @@ -15,8 +15,11 @@ import isBrowser from 'lib/isBrowser'; import * as metadata from 'lib/metadata'; import * as mixpanel from 'lib/mixpanel/index'; import getQueryParamString from 'lib/router/getQueryParamString'; +import { Button } from 'toolkit/chakra/button'; +import type { SelectOption } from 'toolkit/chakra/select'; +import { Select } from 'toolkit/chakra/select'; +import { Skeleton } from 'toolkit/chakra/skeleton'; import isCustomAppError from 'ui/shared/AppError/isCustomAppError'; -import Skeleton from 'ui/shared/chakra/Skeleton'; import ChartIntervalSelect from 'ui/shared/chart/ChartIntervalSelect'; import ChartMenu from 'ui/shared/chart/ChartMenu'; import ChartWidgetContent from 'ui/shared/chart/ChartWidgetContent'; @@ -25,7 +28,6 @@ import useZoom from 'ui/shared/chart/useZoom'; import CopyToClipboard from 'ui/shared/CopyToClipboard'; import IconSvg from 'ui/shared/IconSvg'; import PageTitle from 'ui/shared/Page/PageTitle'; -import Select from 'ui/shared/select/Select'; import { STATS_RESOLUTIONS } from 'ui/stats/constants'; const DEFAULT_RESOLUTION = Resolution.DAY; @@ -108,11 +110,11 @@ const Chart = () => { ); }, [ setIntervalState, router ]); - const onResolutionChange = React.useCallback((resolution: Resolution) => { - setResolution(resolution); + const onResolutionChange = React.useCallback(({ value }: { value: Array }) => { + setResolution(value[0] as Resolution); router.push({ pathname: router.pathname, - query: { ...router.query, resolution }, + query: { ...router.query, resolution: value[0] }, }, undefined, { shallow: true }, @@ -121,7 +123,7 @@ const Chart = () => { const handleReset = React.useCallback(() => { handleZoomReset(); - onResolutionChange(DEFAULT_RESOLUTION); + onResolutionChange({ value: [ DEFAULT_RESOLUTION ] }); }, [ handleZoomReset, onResolutionChange ]); const { items, info, lineQuery } = useChartQuery(id, resolution, interval); @@ -155,22 +157,24 @@ const Chart = () => { const shareButton = ( ); - const resolutionOptions = React.useMemo(() => { + const resolutionCollection = React.useMemo(() => { const resolutions = lineQuery.data?.info?.resolutions || []; - return STATS_RESOLUTIONS + const items = STATS_RESOLUTIONS .filter((resolution) => resolutions.includes(resolution.id)) .map((resolution) => ({ value: resolution.id, label: resolution.title })); + + return createListCollection({ items }); }, [ lineQuery.data?.info?.resolutions ]); return ( @@ -194,21 +198,22 @@ const Chart = () => { (!info && lineQuery.data?.info?.resolutions && lineQuery.data?.info?.resolutions.length > 1) ) && ( - + { isMobile ? 'Res.' : 'Resolution' }