Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b220203c91 |
@@ -4,11 +4,6 @@ VITE_ALCHEMY_API_KEY=
|
|||||||
|
|
||||||
VITE_PK_ENCRYPTION_KEY=
|
VITE_PK_ENCRYPTION_KEY=
|
||||||
|
|
||||||
VITE_V3_TOKEN_ADDRESS=
|
VITE_WALLETCONNECT2_PROJECT_ID=
|
||||||
VITE_TOKEN_MIGRATION_URI=
|
|
||||||
|
|
||||||
AMPLITUDE_API_KEY=
|
VITE_V3_TOKEN_ADDRESS=
|
||||||
BUGSNAG_API_KEY=
|
|
||||||
IOS_APP_ID=
|
|
||||||
INTERCOM_APP_ID=
|
|
||||||
STATUS_PAGE_SCRIPT_URI=
|
|
||||||
@@ -5,35 +5,29 @@ import styled from 'styled-components';
|
|||||||
|
|
||||||
import { store } from '@/state/_store';
|
import { store } from '@/state/_store';
|
||||||
|
|
||||||
import { GlobalStyle } from '@/styles/globalStyle';
|
|
||||||
|
|
||||||
import { SelectMenu, SelectItem } from '@/components/SelectMenu';
|
import { SelectMenu, SelectItem } from '@/components/SelectMenu';
|
||||||
|
|
||||||
import { AppThemeProvider } from '@/hooks/useAppTheme';
|
|
||||||
|
|
||||||
import { AppTheme, setAppTheme } from '@/state/configs';
|
|
||||||
import { setLocaleLoaded } from '@/state/localization';
|
import { setLocaleLoaded } from '@/state/localization';
|
||||||
|
|
||||||
import '@/index.css';
|
import '@/index.css';
|
||||||
import './ladle.css';
|
import './ladle.css';
|
||||||
|
|
||||||
export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
const [theme, setTheme] = useState(AppTheme.Classic);
|
const [theme, setTheme] = useState('Default theme');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
store.dispatch(setAppTheme(theme));
|
|
||||||
switch (theme) {
|
switch (theme) {
|
||||||
case AppTheme.Dark: {
|
case 'Dark theme': {
|
||||||
document?.documentElement?.classList.remove('theme-light');
|
document?.documentElement?.classList.remove('theme-light');
|
||||||
document?.documentElement?.classList.add('theme-dark');
|
document?.documentElement?.classList.add('theme-dark');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case AppTheme.Light: {
|
case 'Light theme': {
|
||||||
document?.documentElement?.classList.remove('theme-dark');
|
document?.documentElement?.classList.remove('theme-dark');
|
||||||
document?.documentElement?.classList.add('theme-light');
|
document?.documentElement?.classList.add('theme-light');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case AppTheme.Classic: {
|
default: {
|
||||||
document?.documentElement?.classList.remove('theme-dark', 'theme-light');
|
document?.documentElement?.classList.remove('theme-dark', 'theme-light');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -54,15 +48,15 @@ export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children
|
|||||||
>
|
>
|
||||||
{[
|
{[
|
||||||
{
|
{
|
||||||
value: AppTheme.Classic,
|
value: 'Default theme',
|
||||||
label: 'Default theme',
|
label: 'Default theme',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: AppTheme.Dark,
|
value: 'Dark theme',
|
||||||
label: 'Dark theme',
|
label: 'Dark theme',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: AppTheme.Light,
|
value: 'Light theme',
|
||||||
label: 'Light theme',
|
label: 'Light theme',
|
||||||
},
|
},
|
||||||
].map(({ value, label }) => (
|
].map(({ value, label }) => (
|
||||||
@@ -75,11 +69,8 @@ export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children
|
|||||||
</SelectMenu>
|
</SelectMenu>
|
||||||
</StoryHeader>
|
</StoryHeader>
|
||||||
<hr />
|
<hr />
|
||||||
<AppThemeProvider>
|
<StoryContent>{children}</StoryContent>
|
||||||
<GlobalStyle />
|
</Provider>
|
||||||
<StoryContent>{children}</StoryContent>
|
|
||||||
</AppThemeProvider>
|
|
||||||
</Provider>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,48 @@
|
|||||||
|
Subject to your compliance with applicable law, you are granted the right to use the Licensed Work (defined below) under the terms of the below licenses; provided, however, that if you violate any such applicable law in your use of the Licensed Work, all of your rights and licenses to use (including any rights to reproduce, distribute, install or modify) the Licensed Work will automatically and immediately terminate.
|
||||||
|
|
||||||
|
Use of this software before the Change Date specified in the LICENSE file is governed by the Business Source License included in the LICENSE file and at https://spdx.org/licenses/BUSL-1.1.html.
|
||||||
|
|
||||||
|
On and after the Change Date specified in the LICENSE file, use of this software will be governed by the Change License (GNU Affero GPL v3) as specified in the LICENSE file.
|
||||||
|
|
||||||
|
|
||||||
|
Business Source License 1.1
|
||||||
|
Business Source License text copyright © 2023 MariaDB plc, All Rights Reserved. “Business Source License” is a trademark of MariaDB plc.
|
||||||
Copyright (C) 2023 dYdX Trading Inc.
|
Copyright (C) 2023 dYdX Trading Inc.
|
||||||
|
|
||||||
Subject to your compliance with applicable law and the v4 Terms of Use, available at dydx.exchange/legal, you are granted the right to use the Program or Licensed Work (defined below) under the terms of the GNU Affero General Public License as set forth below; provided, however, that if you violate any such applicable law in your use of the Program or Licensed Work, all of your rights and licenses to use (including any rights to reproduce, distribute, install or modify) the Program or Licensed Work will automatically and immediately terminate.
|
Parameters
|
||||||
|
Licensor: dYdX Trading Inc.
|
||||||
|
Licensed Work: dydxprotocol/cosmos-sdk, dydxprotocol/cometbft, dydxprotocol/v4-chain, dydxprotocol/v4-clients, dydxprotocol/v4-web, dydxprotocol/v4-abacus, dydxprotocol/v4-localization, dydxprotocol/v4-documentation, and any dYdX or dYdX Trading Inc. github repository reflecting a copy of this license, or link to this license.
|
||||||
|
Additional Use Grant: None
|
||||||
|
Change Date: On the later to occur of (i) the release of the Licensed Work labeled “dYdX Version 1.0 Affero GPL”, and which includes a document uploaded to the relevant github repository stating “The software in this repository is Version 1.0, subject to Affero GPL license”, and (ii) September 30, 2023 (approximate projected release of “Version 1.0” of this software).
|
||||||
|
Change License: GNU Affero GPL License v3
|
||||||
|
|
||||||
|
License text copyright © 2023 MariaDB plc, All Rights Reserved. “Business Source License” is a trademark of MariaDB plc.
|
||||||
|
Terms
|
||||||
|
The Licensor hereby grants you the right to copy, modify, create derivative works, redistribute, and make non-production use of the Licensed Work. The Licensor may make an Additional Use Grant, above, permitting limited production use.
|
||||||
|
Effective on the Change Date, or the fourth anniversary of the first publicly available distribution of a specific version of the Licensed Work under this License, whichever comes first, the Licensor hereby grants you rights under the terms of the Change License, and the rights granted in the paragraph above terminate.
|
||||||
|
If your use of the Licensed Work does not comply with the requirements currently in effect as described in this License, you must purchase a commercial license from the Licensor, its affiliated entities, or authorized resellers, or you must refrain from using the Licensed Work.
|
||||||
|
All copies of the original and modified Licensed Work, and derivative works of the Licensed Work, are subject to this License. This License applies separately for each version of the Licensed Work and the Change Date may vary for each version of the Licensed Work released by Licensor.
|
||||||
|
You must conspicuously display this License on each original or modified copy of the Licensed Work. If you receive the Licensed Work in original or modified form from a third party, the terms and conditions set forth in this License apply to your use of that work.
|
||||||
|
Any use of the Licensed Work in violation of this License will automatically terminate your rights under this License for the current and all other versions of the Licensed Work.
|
||||||
|
This License does not grant you any right in any trademark or logo of Licensor or its affiliates (provided that you may use a trademark or logo of Licensor as expressly required by this License).TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND TITLE. MariaDB hereby grants you permission to use this License’s text to license your works, and to refer to it using the trademark “Business Source License”, as long as you comply with the Covenants of Licensor below.
|
||||||
|
Covenants of Licensor
|
||||||
|
In consideration of the right to use this License’s text and the “Business Source License” name and trademark, Licensor covenants to MariaDB, and to all other recipients of the licensed work to be provided by Licensor:
|
||||||
|
To specify as the Change License the GPL Version 2.0 or any later version, or a license that is compatible with GPL Version 2.0 or a later version, where “compatible” means that software provided under the Change License can be included in a program with software provided under GPL Version 2.0 or a later version. Licensor may specify additional Change Licenses without limitation.
|
||||||
|
To either: (a) specify an additional grant of rights to use that does not impose any additional restriction on the right granted in this License, as the Additional Use Grant; or (b) insert the text “None” to specify a Change Date. Not to modify this License in any other way.
|
||||||
|
Notice
|
||||||
|
The Business Source License (this document, or the “License”) is not an Open Source license. However, the Licensed Work will eventually be made available under an Open Source License, as stated in this License.
|
||||||
|
|
||||||
|
|
||||||
The “Program” or “Licensed Work” shall mean any of the following: dydxprotocol/cosmos-sdk, dydxprotocol/cometbft, dydxprotocol/v4-chain, dydxprotocol/v4-clients, dydxprotocol/v4-web, dydxprotocol/v4-abacus, dydxprotocol/v4-localization, dydxprotocol/v4-documentation, and any dYdX or dYdX Trading Inc. repository reflecting a copy of, or link to, this license.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
OPEN SOURCE LICENSE BELOW - TO BE APPLIED AFTER THE CHANGE DATE.
|
||||||
|
|
||||||
|
|
||||||
|
Copyright (C) 2023 dYdX Trading Inc.
|
||||||
|
|
||||||
|
Subject to your compliance with applicable law, you are granted the right to use the Licensed Work (defined below) under the terms of the GNU Affero General Public License as set forth below; provided, however, that if you violate any such applicable law in your use of the Licensed Work, all of your rights and licenses to use (including any rights to reproduce, distribute, install or modify) the Licensed Work will automatically and immediately terminate.
|
||||||
|
|
||||||
|
|
||||||
The GNU Affero General Public License
|
The GNU Affero General Public License
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<p align="center"><img src="https://dydx.exchange/icon.svg?" width="256" /></p>
|
<p align="center"><img src="https://dydx.exchange/icon.svg?" width="256" /></p>
|
||||||
|
|
||||||
<h1 align="center">dYdX Chain Web</h1>
|
<h1 align="center">v4 Web</h1>
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<a href='https://github.com/dydxprotocol/v4-web/blob/main/LICENSE'>
|
<a href='https://github.com/dydxprotocol/v4-web/blob/main/LICENSE'>
|
||||||
<img src='https://img.shields.io/badge/License-AGPL_v3-blue.svg' alt='License' />
|
<img src='https://img.shields.io/badge/License-BSL_1.1-blue' alt='License' />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ Alternatively, follow the [IPFS Kubo installation guide](https://docs.ipfs.tech
|
|||||||
|
|
||||||
## Part 1: Setting up your local environment
|
## Part 1: Setting up your local environment
|
||||||
|
|
||||||
### 1. Clone the repo
|
### Step 1: Clone the repo
|
||||||
|
|
||||||
Clone the repository and navigate to its directory:
|
Clone the repository and navigate to its directory:
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ git clone https://github.com/dydxprotocol/v4-web.git
|
|||||||
cd v4-web
|
cd v4-web
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Install pnpm and dependencies
|
### Step 2: Install pnpm and dependencies
|
||||||
|
|
||||||
Install pnpm and the project dependencies:
|
Install pnpm and the project dependencies:
|
||||||
|
|
||||||
@@ -69,24 +69,7 @@ You'll need to provide a Wallet Connect project id to enable onboarding and wall
|
|||||||
- Create a project on https://cloud.walletconnect.com/app
|
- Create a project on https://cloud.walletconnect.com/app
|
||||||
- Copy over the project ID into this [field](https://github.com/dydxprotocol/v4-web/blob/67ecbd75b43e0c264b7b4d2d9b3d969830b0621c/public/configs/env.json#L822C33-L822C46)
|
- Copy over the project ID into this [field](https://github.com/dydxprotocol/v4-web/blob/67ecbd75b43e0c264b7b4d2d9b3d969830b0621c/public/configs/env.json#L822C33-L822C46)
|
||||||
|
|
||||||
## Part 4: Set Enviornment variables
|
## Part 4: Deploying with Vercel
|
||||||
Set environment variables via `.env`.
|
|
||||||
|
|
||||||
- `VITE_BASE_URL` (required): The base URL of the deployment (e.g., `https://www.example.com`).
|
|
||||||
- `VITE_ALCHEMY_API_KEY` (optional): Add an Alchemy API key for EVM interactions; the app will fall back to public RPCs if not provided.
|
|
||||||
- `VITE_PK_ENCRYPTION_KEY` (optional): AES encryption key used for signature obfuscation; necessary for enabling the "Remember Me" feature.
|
|
||||||
- `VITE_V3_TOKEN_ADDRESS` (optional): Address of the V3 $DYDX token.
|
|
||||||
- `VITE_TOKEN_MIGRATION_URI` (optional): The URL of the token migration website.
|
|
||||||
- `AMPLITUDE_API_KEY` (optional): Amplitude API key for enabling Amplitude; used with `pnpm run build:inject-amplitude`.
|
|
||||||
- `BUGSNAG_API_KEY` (optional): API key for enabling Bugsnag; used with `pnpm run build:inject-bugsnag`.
|
|
||||||
- `IOS_APP_ID` (optional): iOS app ID used for enabling deep linking to the iOS app; used with `pnpm run build:inject-app-deeplinks`.
|
|
||||||
- `INTERCOM_APP_ID` (optional): Used for enabling Intercom; utilized with `pnpm run build:inject-intercom`.
|
|
||||||
- `STATUS_PAGE_SCRIPT_URI` (optional): Used for enabling the status page; used with `pnpm run build:inject-statuspage`.
|
|
||||||
|
|
||||||
|
|
||||||
# Deployments
|
|
||||||
|
|
||||||
## Deploying with Vercel
|
|
||||||
|
|
||||||
### Step 1: Connect your repository to Vercel
|
### Step 1: Connect your repository to Vercel
|
||||||
|
|
||||||
@@ -98,17 +81,12 @@ For the "Build & Development Settings", we recommend the following:
|
|||||||
- Framework Preset: `Vite`
|
- Framework Preset: `Vite`
|
||||||
- Build Command (override): `pnpm run build`
|
- Build Command (override): `pnpm run build`
|
||||||
|
|
||||||
By default, the dev server runs in development mode and the build command runs in production mode. To override the default mode, you can pass in the `--mode` option flag. For example, if you want to build your app for testnet:
|
|
||||||
```
|
|
||||||
pnpm run build --mode testnet
|
|
||||||
```
|
|
||||||
|
|
||||||
If you wish to incorporate analytics via Amplitude and Bugsnag, you can use our scripts:
|
If you wish to incorporate analytics via Amplitude and Bugsnag, you can use our scripts:
|
||||||
`pnpm run build:inject-amplitude` and `pnpm run build:inject-bugsnag`. You will need to provide your own API keys for these services. In the Environment Variables section, name the variables as `AMPLITUDE_API_KEY` and `BUGSNAG_API_KEY` and provide the respective keys as their values.
|
`pnpm run build:inject-amplitude` and `pnpm run build:inject-bugsnag`. You will need to provide your own API keys for these services. In the Environment Variables section, name the variables as `AMPLITUDE_API_KEY` and `BUGSNAG_API_KEY` and provide the respective keys as their values.
|
||||||
|
|
||||||
For more details, check out Vercel's [official documentation](https://vercel.com/docs).
|
For more details, check out Vercel's [official documentation](https://vercel.com/docs).
|
||||||
|
|
||||||
## Deploying to IPFS
|
## Part 5: Deploying to IPFS
|
||||||
|
|
||||||
### web3.storage: deploy to IPFS via web3.storage using the provided script
|
### web3.storage: deploy to IPFS via web3.storage using the provided script
|
||||||
|
|
||||||
@@ -140,18 +118,3 @@ To access your content on IPFS:
|
|||||||
2. **Public IPFS gateway:** Access your content via a public IPFS gateway, such as [https://dweb.link](https://dweb.link/) or [https://w3s.link/](https://w3s.link/). Use the gateway URL with your CID appended, like `https://dweb.link/ipfs/your_cid`.
|
2. **Public IPFS gateway:** Access your content via a public IPFS gateway, such as [https://dweb.link](https://dweb.link/) or [https://w3s.link/](https://w3s.link/). Use the gateway URL with your CID appended, like `https://dweb.link/ipfs/your_cid`.
|
||||||
|
|
||||||
Replace `your_cid` with the actual CID.
|
Replace `your_cid` with the actual CID.
|
||||||
|
|
||||||
## Cloudflare Settings
|
|
||||||
|
|
||||||
We recommend that you add your website to Cloudflare for additional security settings.
|
|
||||||
|
|
||||||
To block OFAC Sanctioned countries:
|
|
||||||
1. Navigate Websites > Domain > Security > WAF
|
|
||||||
|
|
||||||
2. Create Rule with the following settings:
|
|
||||||
* If incoming requests match
|
|
||||||
`(ip.geoip.country eq "CU") or (ip.geoip.country eq "IR") or (ip.geoip.country eq "KP") or (ip.geoip.country eq "SY") or (ip.geoip.country eq "MM") or (ip.geoip.subdivision_1_iso_code eq "UA-09") or (ip.geoip.subdivision_1_iso_code eq "UA-14") or (ip.geoip.subdivision_1_iso_code eq "UA-43")`
|
|
||||||
* This rule will bring up a Cloudflare page when a restricted geography tries to access your site. You will have the option to display:
|
|
||||||
1. Custom Text
|
|
||||||
- (e.g. `Because you appear to be a resident of, or trading from, a jurisdiction that violates our terms of use, or have engaged in activity that violates our terms of use, you have been blocked. You may withdraw your funds from the protocol at any time.`)
|
|
||||||
2. Default Cloudflare WAF block page
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "dydx-chain-web",
|
"name": "dydx-chain-web",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.0",
|
"version": "0.0.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "AGPL-3.0",
|
"license": "BSL-1.1",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
@@ -33,23 +33,20 @@
|
|||||||
"packageManager": "pnpm@8.6.6",
|
"packageManager": "pnpm@8.6.6",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@0xsquid/sdk": "^1.10.0",
|
"@0xsquid/sdk": "^1.10.0",
|
||||||
"@cosmjs/amino": "^0.32.1",
|
"@cosmjs/amino": "^0.31.0",
|
||||||
"@cosmjs/crypto": "^0.32.1",
|
"@cosmjs/crypto": "^0.31.0",
|
||||||
"@cosmjs/encoding": "^0.32.1",
|
"@cosmjs/encoding": "^0.31.0",
|
||||||
"@cosmjs/proto-signing": "^0.32.1",
|
"@cosmjs/proto-signing": "^0.31.0",
|
||||||
"@cosmjs/stargate": "^0.32.1",
|
"@cosmjs/stargate": "^0.31.0",
|
||||||
"@cosmjs/tendermint-rpc": "^0.32.1",
|
"@cosmjs/tendermint-rpc": "^0.31.0",
|
||||||
"@dydxprotocol/v4-abacus": "^1.3.2",
|
"@dydxprotocol/v4-abacus": "^0.7.6",
|
||||||
"@dydxprotocol/v4-client-js": "^1.0.17",
|
"@dydxprotocol/v4-client-js": "^0.40.3",
|
||||||
"@dydxprotocol/v4-localization": "^1.1.17",
|
"@dydxprotocol/v4-localization": "^0.1.32",
|
||||||
"@ethersproject/providers": "^5.7.2",
|
"@ethersproject/providers": "^5.7.2",
|
||||||
"@js-joda/core": "^5.5.3",
|
"@js-joda/core": "^5.5.3",
|
||||||
"@radix-ui/react-accordion": "^1.1.2",
|
|
||||||
"@radix-ui/react-checkbox": "^1.0.4",
|
|
||||||
"@radix-ui/react-collapsible": "^1.0.3",
|
"@radix-ui/react-collapsible": "^1.0.3",
|
||||||
"@radix-ui/react-dialog": "^1.0.4",
|
"@radix-ui/react-dialog": "^1.0.4",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
|
||||||
"@radix-ui/react-navigation-menu": "^1.1.3",
|
"@radix-ui/react-navigation-menu": "^1.1.3",
|
||||||
"@radix-ui/react-popover": "^1.0.6",
|
"@radix-ui/react-popover": "^1.0.6",
|
||||||
"@radix-ui/react-radio-group": "^1.1.3",
|
"@radix-ui/react-radio-group": "^1.1.3",
|
||||||
@@ -99,7 +96,6 @@
|
|||||||
"long": "^5.2.3",
|
"long": "^5.2.3",
|
||||||
"luxon": "^3.3.0",
|
"luxon": "^3.3.0",
|
||||||
"qr-code-styling": "1.6.0-rc.1",
|
"qr-code-styling": "1.6.0-rc.1",
|
||||||
"query-string": "^8.1.0",
|
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-aria": "^3.25.0",
|
"react-aria": "^3.25.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
@@ -111,8 +107,8 @@
|
|||||||
"reselect": "^4.1.8",
|
"reselect": "^4.1.8",
|
||||||
"styled-components": "^5.3.11",
|
"styled-components": "^5.3.11",
|
||||||
"use-latest": "^1.2.1",
|
"use-latest": "^1.2.1",
|
||||||
"viem": "^1.20.0",
|
"viem": "^1.12.2",
|
||||||
"wagmi": "^1.4.12"
|
"wagmi": "^1.3.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.22.5",
|
"@babel/core": "^7.22.5",
|
||||||
|
|||||||
@@ -8,43 +8,67 @@
|
|||||||
],
|
],
|
||||||
"components": [
|
"components": [
|
||||||
{
|
{
|
||||||
"/": "/"
|
"/": "/trade/*"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"/": "/#/"
|
"/": "/r/*"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"/": "*/markets"
|
"/": "/portfolio/overview"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"/": "*/trade"
|
"/": "/portfolio/positions"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"/": "*/trade/*"
|
"/": "/portfolio/orders"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"/": "*/portfolio"
|
"/": "/portfolio/fees"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"/": "*/portfolio/overview"
|
"/": "/portfolio/history"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"/": "*/portfolio/positions"
|
"/": "/portfolio/history/trades"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"/": "*/portfolio/orders"
|
"/": "/portfolio/history/transfers"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"/": "*/portfolio/fees"
|
"/": "/portfolio/history/funding"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"/": "*/portfolio/history/trades"
|
"/": "/rewards"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"/": "*/portfolio/history/transfers"
|
"/": "/my-profile"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"/": "*/portfolio/history/funding"
|
"/": "/rankings/hedgies"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"/": "/rankings/competition"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"/": "/rankings/diamond"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"/": "/rankings/platinum"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"/": "/rankings/gold"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"/": "/rankings/silver"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"/": "/rankings/bronze"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"/": "/rankings/pnl-absolute"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"/": "/rankings/pnl-percent"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"/": "/walletsegue",
|
"/": "/walletsegue",
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"chainId": "1",
|
|
||||||
"tokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
||||||
"name": "Ethereum"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"chainId": "5",
|
|
||||||
"tokenAddress": "0x07865c6E87B9F70255377e024ace6630C1Eaa37F",
|
|
||||||
"name": "Ethereum Goerli"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
{
|
|
||||||
"tradingRewardsFAQs": [
|
|
||||||
{
|
|
||||||
"questionLocalizationKey": "APP.TRADING_REWARDS.FAQ_WHO_IS_ELIGIBLE_QUESTION",
|
|
||||||
"answerLocalizationKey": "APP.TRADING_REWARDS.FAQ_WHO_IS_ELIGIBLE_ANSWER"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"questionLocalizationKey": "APP.TRADING_REWARDS.FAQ_HOW_DO_TRADING_REWARDS_WORK_QUESTION",
|
|
||||||
"answerLocalizationKey": "APP.TRADING_REWARDS.FAQ_HOW_DO_TRADING_REWARDS_WORK_ANSWER"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"questionLocalizationKey": "APP.TRADING_REWARDS.FAQ_HOW_DO_I_CLAIM_MY_REWARDS_QUESTION",
|
|
||||||
"answerLocalizationKey": "APP.TRADING_REWARDS.FAQ_HOW_DO_I_CLAIM_MY_REWARDS_ANSWER"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,360 +1,454 @@
|
|||||||
{
|
{
|
||||||
"1INCH-USD": {
|
"1INCH-USD":{
|
||||||
"name": "1inch",
|
"name":"1inch",
|
||||||
"tags": ["Defi"],
|
"tags":[
|
||||||
"websiteLink": "https://1inch.io/",
|
"Defi"
|
||||||
"whitepaperLink": "https://github.com/1inch",
|
],
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/1inch/"
|
"websiteLink":"https://1inch.io/",
|
||||||
},
|
"whitepaperLink":"https://github.com/1inch",
|
||||||
"AAVE-USD": {
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/1inch/"
|
||||||
"name": "Aave",
|
},
|
||||||
"tags": ["Defi"],
|
"AAVE-USD":{
|
||||||
"websiteLink": "https://aave.com/",
|
"name":"Aave",
|
||||||
"whitepaperLink": "https://github.com/aave/protocol-v2/blob/master/aave-v2-whitepaper.pdf",
|
"tags":[
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/aave/"
|
"Defi"
|
||||||
},
|
],
|
||||||
"ADA-USD": {
|
"websiteLink":"https://aave.com/",
|
||||||
"name": "Cardano",
|
"whitepaperLink":"https://github.com/aave/protocol-v2/blob/master/aave-v2-whitepaper.pdf",
|
||||||
"tags": ["Layer 1"],
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/aave/"
|
||||||
"websiteLink": "https://cardano.org/",
|
},
|
||||||
"whitepaperLink": "https://why.cardano.org/en/introduction/motivation/",
|
"ADA-USD":{
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/cardano/"
|
"name":"Cardano",
|
||||||
},
|
"tags":[
|
||||||
"ALGO-USD": {
|
"Layer 1"
|
||||||
"name": "Algorand",
|
],
|
||||||
"tags": ["Layer 1"],
|
"websiteLink":"https://cardano.org/",
|
||||||
"websiteLink": "https://algorand.com/",
|
"whitepaperLink":"https://why.cardano.org/en/introduction/motivation/",
|
||||||
"whitepaperLink": "https://algorand.com/technology/white-papers",
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/cardano/"
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/algorand/"
|
},
|
||||||
},
|
"ALGO-USD":{
|
||||||
"APE-USD": {
|
"name":"Algorand",
|
||||||
"name": "ApeCoin",
|
"tags":[
|
||||||
"tags": [],
|
"Layer 1"
|
||||||
"websiteLink": "https://apecoin.com/",
|
],
|
||||||
"whitepaperLink": "https://apecoin.com/about",
|
"websiteLink":"https://algorand.com/",
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/apecoin-ape/"
|
"whitepaperLink":"https://algorand.com/technology/white-papers",
|
||||||
},
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/algorand/"
|
||||||
"APT-USD": {
|
},
|
||||||
"name": "Aptos",
|
"APE-USD":{
|
||||||
"tags": ["Layer 1"],
|
"name":"ApeCoin",
|
||||||
"websiteLink": "https://aptoslabs.com/",
|
"tags":[
|
||||||
"whitepaperLink": "https://aptos.dev/aptos-white-paper/",
|
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/aptos/"
|
],
|
||||||
},
|
"websiteLink":"https://apecoin.com/",
|
||||||
"ARB-USD": {
|
"whitepaperLink":"https://apecoin.com/about",
|
||||||
"name": "Arbitrum",
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/apecoin-ape/"
|
||||||
"tags": [],
|
},
|
||||||
"websiteLink": "https://arbitrum.io/",
|
"APT-USD":{
|
||||||
"whitepaperLink": "https://github.com/OffchainLabs/nitro",
|
"name":"Aptos",
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/arbitrum/"
|
"tags":[
|
||||||
},
|
"Layer 1"
|
||||||
"ATOM-USD": {
|
],
|
||||||
"name": "Cosmos",
|
"websiteLink":"https://aptoslabs.com/",
|
||||||
"tags": ["Layer 1"],
|
"whitepaperLink":"https://aptos.dev/aptos-white-paper/",
|
||||||
"websiteLink": "https://cosmos.network/",
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/aptos/"
|
||||||
"whitepaperLink": "https://v1.cosmos.network/resources/whitepaper",
|
},
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/cosmos/"
|
"ARB-USD":{
|
||||||
},
|
"name":"Arbitrum",
|
||||||
"AVAX-USD": {
|
"tags":[
|
||||||
"name": "Avalanche",
|
|
||||||
"tags": ["Layer 1"],
|
],
|
||||||
"websiteLink": "https://www.avalabs.org/",
|
"websiteLink":"https://arbitrum.io/",
|
||||||
"whitepaperLink": "https://assets.website-files.com/5d80307810123f5ffbb34d6e/6008d7bbf8b10d1eb01e7e16_Avalanche%20Platform%20Whitepaper.pdf",
|
"whitepaperLink":"https://github.com/OffchainLabs/nitro",
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/avalanche/"
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/arbitrum/"
|
||||||
},
|
},
|
||||||
"BCH-USD": {
|
"ATOM-USD":{
|
||||||
"name": "Bitcoin Cash",
|
"name":"Cosmos",
|
||||||
"tags": ["Layer 1"],
|
"tags":[
|
||||||
"websiteLink": "https://bitcoincash.org/",
|
"Layer 1"
|
||||||
"whitepaperLink": "https://bitcoincash.org/",
|
],
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/bitcoin-cash/"
|
"websiteLink":"https://cosmos.network/",
|
||||||
},
|
"whitepaperLink":"https://v1.cosmos.network/resources/whitepaper",
|
||||||
"BLUR-USD": {
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/cosmos/"
|
||||||
"name": "Blur",
|
},
|
||||||
"tags": [],
|
"AVAX-USD":{
|
||||||
"websiteLink": "https://blur.io/",
|
"name":"Avalanche",
|
||||||
"whitepaperLink": "https://docs.blur.foundation/",
|
"tags":[
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/blur-token/"
|
"Layer 1"
|
||||||
},
|
],
|
||||||
"CELO-USD": {
|
"websiteLink":"https://www.avalabs.org/",
|
||||||
"name": "Celo",
|
"whitepaperLink":"https://assets.website-files.com/5d80307810123f5ffbb34d6e/6008d7bbf8b10d1eb01e7e16_Avalanche%20Platform%20Whitepaper.pdf",
|
||||||
"tags": [],
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/avalanche/"
|
||||||
"websiteLink": "https://celo.org",
|
},
|
||||||
"whitepaperLink": "https://docs.celo.org",
|
"BCH-USD":{
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/celo/"
|
"name":"Bitcoin Cash",
|
||||||
},
|
"tags":[
|
||||||
"BTC-USD": {
|
"Layer 1"
|
||||||
"name": "Bitcoin",
|
],
|
||||||
"tags": ["Layer 1"],
|
"websiteLink":"https://bitcoincash.org/",
|
||||||
"websiteLink": "https://bitcoin.org/",
|
"whitepaperLink":"https://bitcoincash.org/",
|
||||||
"whitepaperLink": "https://bitcoin.org/bitcoin.pdf",
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/bitcoin-cash/"
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/bitcoin/"
|
},
|
||||||
},
|
"BLUR-USD":{
|
||||||
"COMP-USD": {
|
"name":"Blur",
|
||||||
"name": "Compound",
|
"tags":[
|
||||||
"tags": ["Defi"],
|
|
||||||
"websiteLink": "https://compound.finance/",
|
],
|
||||||
"whitepaperLink": "https://compound.finance/documents/Compound.Whitepaper.pdf",
|
"websiteLink":"https://blur.io/",
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/compound/"
|
"whitepaperLink":"https://docs.blur.foundation/",
|
||||||
},
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/blur-token/"
|
||||||
"CRV-USD": {
|
},
|
||||||
"name": "Curve",
|
"CELO-USD":{
|
||||||
"tags": ["Governance"],
|
"name":"Celo",
|
||||||
"websiteLink": "https://curve.fi/",
|
"tags":[
|
||||||
"whitepaperLink": "https://curve.fi/whitepaper",
|
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/curve-dao-token/"
|
],
|
||||||
},
|
"websiteLink":"https://celo.org",
|
||||||
"DOGE-USD": {
|
"whitepaperLink":"https://docs.celo.org",
|
||||||
"name": "Dogecoin",
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/celo/"
|
||||||
"tags": ["Layer 1"],
|
},
|
||||||
"websiteLink": "https://dogecoin.com/",
|
"BTC-USD":{
|
||||||
"whitepaperLink": "https://github.com/dogecoin/dogecoin",
|
"name":"Bitcoin",
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/dogecoin/"
|
"tags":[
|
||||||
},
|
"Layer 1"
|
||||||
"DOT-USD": {
|
],
|
||||||
"name": "Polkadot",
|
"websiteLink":"https://bitcoin.org/",
|
||||||
"tags": ["Layer 1"],
|
"whitepaperLink":"https://bitcoin.org/bitcoin.pdf",
|
||||||
"websiteLink": "https://polkadot.network/",
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/bitcoin/"
|
||||||
"whitepaperLink": "https://polkadot.network/PolkaDotPaper.pdf",
|
},
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/polkadot-new/"
|
"COMP-USD":{
|
||||||
},
|
"name":"Compound",
|
||||||
"ENJ-USD": {
|
"tags":[
|
||||||
"name": "Enjin",
|
"Defi"
|
||||||
"tags": [],
|
],
|
||||||
"websiteLink": "https://enjin.io/",
|
"websiteLink":"https://compound.finance/",
|
||||||
"whitepaperLink": "https://cdn.enjin.io/downloads/whitepapers/enjin-coin/en.pdf/",
|
"whitepaperLink":"https://compound.finance/documents/Compound.Whitepaper.pdf",
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/enjin-coin/"
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/compound/"
|
||||||
},
|
},
|
||||||
"EOS-USD": {
|
"CRV-USD":{
|
||||||
"name": "EOS",
|
"name":"Curve",
|
||||||
"tags": ["Layer 1"],
|
"tags":[
|
||||||
"websiteLink": "https://eos.io/",
|
"Governance"
|
||||||
"whitepaperLink": "https://github.com/EOSIO/Documentation/blob/master/TechnicalWhitePaper.md",
|
],
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/eos/"
|
"websiteLink":"https://curve.fi/",
|
||||||
},
|
"whitepaperLink":"https://curve.fi/whitepaper",
|
||||||
"ETC-USD": {
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/curve-dao-token/"
|
||||||
"name": "Ethereum Classic",
|
},
|
||||||
"tags": ["Layer 1"],
|
"DOGE-USD":{
|
||||||
"websiteLink": "https://ethereumclassic.org/",
|
"name":"Dogecoin",
|
||||||
"whitepaperLink": "https://ethereumclassic.org/why-classic",
|
"tags":[
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/ethereum-classic/"
|
"Layer 1"
|
||||||
},
|
],
|
||||||
"ETH-USD": {
|
"websiteLink":"https://dogecoin.com/",
|
||||||
"name": "Ethereum",
|
"whitepaperLink":"https://github.com/dogecoin/dogecoin",
|
||||||
"tags": ["Layer 1"],
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/dogecoin/"
|
||||||
"websiteLink": "https://ethereum.org/",
|
},
|
||||||
"whitepaperLink": "https://ethereum.org/whitepaper/",
|
"DOT-USD":{
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/ethereum/",
|
"name":"Polkadot",
|
||||||
"displayStepSize": "0.001",
|
"tags":[
|
||||||
"displayTickSize": "0.1"
|
"Layer 1"
|
||||||
},
|
],
|
||||||
"FIL-USD": {
|
"websiteLink":"https://polkadot.network/",
|
||||||
"name": "Filecoin",
|
"whitepaperLink":"https://polkadot.network/PolkaDotPaper.pdf",
|
||||||
"tags": ["Layer 1"],
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/polkadot-new/"
|
||||||
"websiteLink": "https://filecoin.io/",
|
},
|
||||||
"whitepaperLink": "https://filecoin.io/filecoin.pdf",
|
"ENJ-USD":{
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/filecoin/"
|
"name":"Enjin",
|
||||||
},
|
"tags":[
|
||||||
"HNT-USD": {
|
|
||||||
"name": "Helium",
|
],
|
||||||
"tags": ["Layer 1"],
|
"websiteLink":"https://enjin.io/",
|
||||||
"websiteLink": "https://www.helium.com",
|
"whitepaperLink":"https://cdn.enjin.io/downloads/whitepapers/enjin-coin/en.pdf/",
|
||||||
"whitepaperLink": "http://whitepaper.helium.com",
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/enjin-coin/"
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/helium/"
|
},
|
||||||
},
|
"EOS-USD":{
|
||||||
"ICP-USD": {
|
"name":"EOS",
|
||||||
"name": "Internet Computer",
|
"tags":[
|
||||||
"tags": ["Layer 1"],
|
"Layer 1"
|
||||||
"websiteLink": "https://dfinity.org",
|
],
|
||||||
"whitepaperLink": "https://dfinity.org/whitepaper.pdf",
|
"websiteLink":"https://eos.io/",
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/internet-computer/"
|
"whitepaperLink":"https://github.com/EOSIO/Documentation/blob/master/TechnicalWhitePaper.md",
|
||||||
},
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/eos/"
|
||||||
"LDO-USD": {
|
},
|
||||||
"name": "Lido DAO",
|
"ETC-USD":{
|
||||||
"tags": ["Defi"],
|
"name":"Ethereum Classic",
|
||||||
"websiteLink": "https://lido.fi/",
|
"tags":[
|
||||||
"whitepaperLink": "https://lido.fi/static/Lido:Ethereum-Liquid-Staking.pdf",
|
"Layer 1"
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/lido-dao/"
|
],
|
||||||
},
|
"websiteLink":"https://ethereumclassic.org/",
|
||||||
"LINK-USD": {
|
"whitepaperLink":"https://ethereumclassic.org/why-classic",
|
||||||
"name": "Chainlink",
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/ethereum-classic/"
|
||||||
"tags": ["Defi"],
|
},
|
||||||
"websiteLink": "https://chain.link/",
|
"ETH-USD":{
|
||||||
"whitepaperLink": "https://link.smartcontract.com/whitepaper",
|
"name":"Ethereum",
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/chainlink/"
|
"tags":[
|
||||||
},
|
"Layer 1"
|
||||||
"LTC-USD": {
|
],
|
||||||
"name": "Litecoin",
|
"websiteLink":"https://ethereum.org/",
|
||||||
"tags": ["Layer 1"],
|
"whitepaperLink":"https://ethereum.org/whitepaper/",
|
||||||
"websiteLink": "https://litecoin.org/",
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/ethereum/",
|
||||||
"whitepaperLink": "https://litecoin.info/index.php/Main_Page",
|
"displayStepSize":"0.001",
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/litecoin/"
|
"displayTickSize":"0.1"
|
||||||
},
|
},
|
||||||
"MATIC-USD": {
|
"FIL-USD":{
|
||||||
"name": "Polygon",
|
"name":"Filecoin",
|
||||||
"tags": ["Layer 2"],
|
"tags":[
|
||||||
"websiteLink": "https://polygon.technology/",
|
"Layer 1"
|
||||||
"whitepaperLink": "https://polygon.technology/lightpaper-polygon.pdf",
|
],
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/polygon/"
|
"websiteLink":"https://filecoin.io/",
|
||||||
},
|
"whitepaperLink":"https://filecoin.io/filecoin.pdf",
|
||||||
"MKR-USD": {
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/filecoin/"
|
||||||
"name": "Maker",
|
},
|
||||||
"tags": ["Governance"],
|
"HNT-USD":{
|
||||||
"websiteLink": "https://makerdao.com",
|
"name":"Helium",
|
||||||
"whitepaperLink": "https://makerdao.com/whitepaper",
|
"tags":[
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/maker/"
|
"Layer 1"
|
||||||
},
|
],
|
||||||
"NEAR-USD": {
|
"websiteLink":"https://www.helium.com",
|
||||||
"name": "NEAR Protocol",
|
"whitepaperLink":"http://whitepaper.helium.com",
|
||||||
"tags": ["Layer 1"],
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/helium/"
|
||||||
"websiteLink": "https://near.org",
|
},
|
||||||
"whitepaperLink": "https://near.org/papers/the-official-near-white-paper/",
|
"ICP-USD":{
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/near-protocol/"
|
"name":"Internet Computer",
|
||||||
},
|
"tags":[
|
||||||
"OP-USD": {
|
"Layer 1"
|
||||||
"name": "Optimism",
|
],
|
||||||
"tags": [],
|
"websiteLink":"https://dfinity.org",
|
||||||
"websiteLink": "https://www.optimism.io/",
|
"whitepaperLink":"https://dfinity.org/whitepaper.pdf",
|
||||||
"whitepaperLink": "https://github.com/ethereum-optimism",
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/internet-computer/"
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/optimism-ethereum/"
|
},
|
||||||
},
|
"LDO-USD":{
|
||||||
"PEPE-USD": {
|
"name":"Lido DAO",
|
||||||
"name": "Pepe",
|
"tags":[
|
||||||
"tags": [],
|
"Defi"
|
||||||
"websiteLink": "https://www.pepe.vip/",
|
],
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/pepe/"
|
"websiteLink":"https://lido.fi/",
|
||||||
},
|
"whitepaperLink":"https://lido.fi/static/Lido:Ethereum-Liquid-Staking.pdf",
|
||||||
"RUNE-USD": {
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/lido-dao/"
|
||||||
"name": "THORChain",
|
},
|
||||||
"tags": ["Layer 1"],
|
"LINK-USD":{
|
||||||
"websiteLink": "https://thorchain.org",
|
"name":"Chainlink",
|
||||||
"whitepaperLink": "https://whitepaper.io/document/709/thorchain-whitepaper",
|
"tags":[
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/thorchain/"
|
"Defi"
|
||||||
},
|
],
|
||||||
"SEI-USD": {
|
"websiteLink":"https://chain.link/",
|
||||||
"name": "Sei",
|
"whitepaperLink":"https://link.smartcontract.com/whitepaper",
|
||||||
"tags": ["Layer 1", "Defi"],
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/chainlink/"
|
||||||
"websiteLink": "https://www.sei.io/",
|
},
|
||||||
"whitepaperLink": "https://github.com/sei-protocol/sei-chain/blob/3c9576fee3494ce039df684624f918dd8066ba3f/whitepaper/Sei_Whitepaper.pdf",
|
"LTC-USD":{
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/sei/"
|
"name":"Litecoin",
|
||||||
},
|
"tags":[
|
||||||
"SHIB-USD": {
|
"Layer 1"
|
||||||
"name": "Shiba Inu",
|
],
|
||||||
"tags": [],
|
"websiteLink":"https://litecoin.org/",
|
||||||
"websiteLink": "https://shibatoken.com/",
|
"whitepaperLink":"https://litecoin.info/index.php/Main_Page",
|
||||||
"whitepaperLink": "https://docs.shibatoken.com/",
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/litecoin/"
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/shiba-inu/"
|
},
|
||||||
},
|
"MATIC-USD":{
|
||||||
"SNX-USD": {
|
"name":"Polygon",
|
||||||
"name": "Synthetix",
|
"tags":[
|
||||||
"tags": ["Defi"],
|
"Layer 2"
|
||||||
"websiteLink": "https://synthetix.io/",
|
],
|
||||||
"whitepaperLink": "https://docs.synthetix.io/litepaper",
|
"websiteLink":"https://polygon.technology/",
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/synthetix-network-token/"
|
"whitepaperLink":"https://polygon.technology/lightpaper-polygon.pdf",
|
||||||
},
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/polygon/"
|
||||||
"SOL-USD": {
|
},
|
||||||
"name": "Solana",
|
"MKR-USD":{
|
||||||
"tags": ["Layer 1"],
|
"name":"Maker",
|
||||||
"websiteLink": "https://solana.com/",
|
"tags":[
|
||||||
"whitepaperLink": "https://solana.com/solana-whitepaper.pdf",
|
"Governance"
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/solana/"
|
],
|
||||||
},
|
"websiteLink":"https://makerdao.com",
|
||||||
"SUI-USD": {
|
"whitepaperLink":"https://makerdao.com/whitepaper",
|
||||||
"name": "Sui",
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/maker/"
|
||||||
"tags": ["Layer 1"],
|
},
|
||||||
"websiteLink": "https://sui.io/",
|
"NEAR-USD":{
|
||||||
"whitepaperLink": "https://github.com/MystenLabs",
|
"name":"NEAR Protocol",
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/sui/"
|
"tags":[
|
||||||
},
|
"Layer 1"
|
||||||
"SUSHI-USD": {
|
],
|
||||||
"name": "Sushi",
|
"websiteLink":"https://near.org",
|
||||||
"tags": ["Defi"],
|
"whitepaperLink":"https://near.org/papers/the-official-near-white-paper/",
|
||||||
"websiteLink": "https://sushi.com/",
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/near-protocol/"
|
||||||
"whitepaperLink": "https://docs.sushi.com/",
|
},
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/sushiswap/"
|
"OP-USD":{
|
||||||
},
|
"name":"Optimism",
|
||||||
"TIA-USD": {
|
"tags":[
|
||||||
"name": "Celestia",
|
|
||||||
"tags": ["Layer 1"],
|
],
|
||||||
"websiteLink": "https://celestia.org/",
|
"websiteLink":"https://www.optimism.io/",
|
||||||
"whitepaperLink": "https://arxiv.org/pdf/1905.09274.pdf",
|
"whitepaperLink":"https://github.com/ethereum-optimism",
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/celestia/"
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/optimism-ethereum/"
|
||||||
},
|
},
|
||||||
"TRX-USD": {
|
"PEPE-USD":{
|
||||||
"name": "TRON",
|
"name":"Pepe",
|
||||||
"tags": ["Defi"],
|
"tags":[
|
||||||
"websiteLink": "https://tron.network/",
|
|
||||||
"whitepaperLink": "https://tron.network/static/doc/white_paper_v_2_0.pdf",
|
],
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/tron/"
|
"websiteLink":"https://www.pepe.vip/",
|
||||||
},
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/pepe/"
|
||||||
"UMA-USD": {
|
},
|
||||||
"name": "UMA",
|
"RUNE-USD":{
|
||||||
"tags": ["Defi"],
|
"name":"THORChain",
|
||||||
"websiteLink": "https://umaproject.org/",
|
"tags":[
|
||||||
"whitepaperLink": "https://github.com/UMAprotocol/whitepaper",
|
"Layer 1"
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/uma/"
|
],
|
||||||
},
|
"websiteLink":"https://thorchain.org",
|
||||||
"UNI-USD": {
|
"whitepaperLink":"https://whitepaper.io/document/709/thorchain-whitepaper",
|
||||||
"name": "Uniswap",
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/thorchain/"
|
||||||
"tags": ["Defi"],
|
},
|
||||||
"websiteLink": "https://uniswap.org/",
|
"SEI-USD":{
|
||||||
"whitepaperLink": "https://uniswap.org/whitepaper-v3.pdf",
|
"name":"Sei",
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/uniswap/"
|
"tags":[
|
||||||
},
|
"Layer 1",
|
||||||
"WLD-USD": {
|
"Defi"
|
||||||
"name": "Worldcoin",
|
],
|
||||||
"tags": [],
|
"websiteLink":"https://www.sei.io/",
|
||||||
"websiteLink": "https://worldcoin.org/",
|
"whitepaperLink":"https://github.com/sei-protocol/sei-chain/blob/3c9576fee3494ce039df684624f918dd8066ba3f/whitepaper/Sei_Whitepaper.pdf",
|
||||||
"whitepaperLink": "https://whitepaper.worldcoin.org/",
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/sei/"
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/worldcoin-org/"
|
},
|
||||||
},
|
"SHIB-USD":{
|
||||||
"XLM-USD": {
|
"name":"Shiba Inu",
|
||||||
"name": "Stellar",
|
"tags":[
|
||||||
"tags": ["Layer 1"],
|
|
||||||
"websiteLink": "https://www.stellar.org/",
|
],
|
||||||
"whitepaperLink": "https://www.stellar.org/papers/stellar-consensus-protocol",
|
"websiteLink":"https://shibatoken.com/",
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/stellar/"
|
"whitepaperLink":"https://docs.shibatoken.com/",
|
||||||
},
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/shiba-inu/"
|
||||||
"XMR-USD": {
|
},
|
||||||
"name": "Monero",
|
"SNX-USD":{
|
||||||
"tags": ["Layer 1"],
|
"name":"Synthetix",
|
||||||
"websiteLink": "https://www.getmonero.org/",
|
"tags":[
|
||||||
"whitepaperLink": "https://www.getmonero.org/resources/research-lab/",
|
"Defi"
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/monero/"
|
],
|
||||||
},
|
"websiteLink":"https://synthetix.io/",
|
||||||
"XRP-USD": {
|
"whitepaperLink":"https://docs.synthetix.io/litepaper",
|
||||||
"name": "Ripple",
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/synthetix-network-token/"
|
||||||
"tags": ["Layer 1"],
|
},
|
||||||
"websiteLink": "https://ripple.com/currency/",
|
"SOL-USD":{
|
||||||
"whitepaperLink": "https://github.com/ripple",
|
"name":"Solana",
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/xrp/"
|
"tags":[
|
||||||
},
|
"Layer 1"
|
||||||
"XTZ-USD": {
|
],
|
||||||
"name": "Tezos",
|
"websiteLink":"https://solana.com/",
|
||||||
"tags": ["Layer 1"],
|
"whitepaperLink":"https://solana.com/solana-whitepaper.pdf",
|
||||||
"websiteLink": "https://tezos.com",
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/solana/"
|
||||||
"whitepaperLink": "https://tezos.com/whitepaper.pdf",
|
},
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/tezos/"
|
"SUI-USD":{
|
||||||
},
|
"name":"Sui",
|
||||||
"YFI-USD": {
|
"tags":[
|
||||||
"name": "Yearn",
|
"Layer 1"
|
||||||
"tags": ["Defi"],
|
],
|
||||||
"websiteLink": "https://yearn.finance/",
|
"websiteLink":"https://sui.io/",
|
||||||
"whitepaperLink": "https://docs.yearn.finance/",
|
"whitepaperLink":"https://github.com/MystenLabs",
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/yearn-finance/"
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/sui/"
|
||||||
},
|
},
|
||||||
"ZEC-USD": {
|
"SUSHI-USD":{
|
||||||
"name": "Zcash",
|
"name":"Sushi",
|
||||||
"tags": ["Layer 1"],
|
"tags":[
|
||||||
"websiteLink": "https://z.cash/",
|
"Defi"
|
||||||
"whitepaperLink": "https://z.cash/technology/",
|
],
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/zcash/"
|
"websiteLink":"https://sushi.com/",
|
||||||
},
|
"whitepaperLink":"https://docs.sushi.com/",
|
||||||
"ZRX-USD": {
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/sushiswap/"
|
||||||
"name": "0x",
|
},
|
||||||
"tags": ["Defi"],
|
"TRX-USD":{
|
||||||
"websiteLink": "https://0x.org/",
|
"name":"TRON",
|
||||||
"whitepaperLink": "https://0x.org/pdfs/0x_white_paper.pdf",
|
"tags":[
|
||||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/0x/"
|
"Defi"
|
||||||
}
|
],
|
||||||
}
|
"websiteLink":"https://tron.network/",
|
||||||
|
"whitepaperLink":"https://tron.network/static/doc/white_paper_v_2_0.pdf",
|
||||||
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/tron/"
|
||||||
|
},
|
||||||
|
"UMA-USD":{
|
||||||
|
"name":"UMA",
|
||||||
|
"tags":[
|
||||||
|
"Defi"
|
||||||
|
],
|
||||||
|
"websiteLink":"https://umaproject.org/",
|
||||||
|
"whitepaperLink":"https://github.com/UMAprotocol/whitepaper",
|
||||||
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/uma/"
|
||||||
|
},
|
||||||
|
"UNI-USD":{
|
||||||
|
"name":"Uniswap",
|
||||||
|
"tags":[
|
||||||
|
"Defi"
|
||||||
|
],
|
||||||
|
"websiteLink":"https://uniswap.org/",
|
||||||
|
"whitepaperLink":"https://uniswap.org/whitepaper-v3.pdf",
|
||||||
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/uniswap/"
|
||||||
|
},
|
||||||
|
"WLD-USD":{
|
||||||
|
"name":"Worldcoin",
|
||||||
|
"tags":[
|
||||||
|
|
||||||
|
],
|
||||||
|
"websiteLink":"https://worldcoin.org/",
|
||||||
|
"whitepaperLink":"https://whitepaper.worldcoin.org/",
|
||||||
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/worldcoin-org/"
|
||||||
|
},
|
||||||
|
"XLM-USD":{
|
||||||
|
"name":"Stellar",
|
||||||
|
"tags":[
|
||||||
|
"Layer 1"
|
||||||
|
],
|
||||||
|
"websiteLink":"https://www.stellar.org/",
|
||||||
|
"whitepaperLink":"https://www.stellar.org/papers/stellar-consensus-protocol",
|
||||||
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/stellar/"
|
||||||
|
},
|
||||||
|
"XMR-USD":{
|
||||||
|
"name":"Monero",
|
||||||
|
"tags":[
|
||||||
|
"Layer 1"
|
||||||
|
],
|
||||||
|
"websiteLink":"https://www.getmonero.org/",
|
||||||
|
"whitepaperLink":"https://www.getmonero.org/resources/research-lab/",
|
||||||
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/monero/"
|
||||||
|
},
|
||||||
|
"XRP-USD":{
|
||||||
|
"name":"Ripple",
|
||||||
|
"tags":[
|
||||||
|
"Layer 1"
|
||||||
|
],
|
||||||
|
"websiteLink":"https://ripple.com/currency/",
|
||||||
|
"whitepaperLink":"https://github.com/ripple",
|
||||||
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/xrp/"
|
||||||
|
},
|
||||||
|
"XTZ-USD":{
|
||||||
|
"name":"Tezos",
|
||||||
|
"tags":[
|
||||||
|
"Layer 1"
|
||||||
|
],
|
||||||
|
"websiteLink":"https://tezos.com",
|
||||||
|
"whitepaperLink":"https://tezos.com/whitepaper.pdf",
|
||||||
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/tezos/"
|
||||||
|
},
|
||||||
|
"YFI-USD":{
|
||||||
|
"name":"Yearn",
|
||||||
|
"tags":[
|
||||||
|
"Defi"
|
||||||
|
],
|
||||||
|
"websiteLink":"https://yearn.finance/",
|
||||||
|
"whitepaperLink":"https://docs.yearn.finance/",
|
||||||
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/yearn-finance/"
|
||||||
|
},
|
||||||
|
"ZEC-USD":{
|
||||||
|
"name":"Zcash",
|
||||||
|
"tags":[
|
||||||
|
"Layer 1"
|
||||||
|
],
|
||||||
|
"websiteLink":"https://z.cash/",
|
||||||
|
"whitepaperLink":"https://z.cash/technology/",
|
||||||
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/zcash/"
|
||||||
|
},
|
||||||
|
"ZRX-USD":{
|
||||||
|
"name":"0x",
|
||||||
|
"tags":[
|
||||||
|
"Defi"
|
||||||
|
],
|
||||||
|
"websiteLink":"https://0x.org/",
|
||||||
|
"whitepaperLink":"https://0x.org/pdfs/0x_white_paper.pdf",
|
||||||
|
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/0x/"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,7 +40,6 @@
|
|||||||
"iosMinVersion": "0",
|
"iosMinVersion": "0",
|
||||||
"imageUrl": "",
|
"imageUrl": "",
|
||||||
"encoding": "=\"#%/<>?@\\^`{|}:&",
|
"encoding": "=\"#%/<>?@\\^`{|}:&",
|
||||||
"androidPackage": "io.metamask",
|
|
||||||
"connections": [
|
"connections": [
|
||||||
{
|
{
|
||||||
"type": "walletConnectV2",
|
"type": "walletConnectV2",
|
||||||
@@ -99,7 +98,6 @@
|
|||||||
"iosMinVersion": "1.8.0",
|
"iosMinVersion": "1.8.0",
|
||||||
"imageUrl": "",
|
"imageUrl": "",
|
||||||
"encoding": "=\"#%/<>?@\\^`{|}:&",
|
"encoding": "=\"#%/<>?@\\^`{|}:&",
|
||||||
"androidPackage": "org.toshi",
|
|
||||||
"connections": [
|
"connections": [
|
||||||
{
|
{
|
||||||
"type": "walletSegue",
|
"type": "walletSegue",
|
||||||
@@ -157,7 +155,6 @@
|
|||||||
"comment": "imToken",
|
"comment": "imToken",
|
||||||
"iosMinVersion": "0",
|
"iosMinVersion": "0",
|
||||||
"imageUrl": "",
|
"imageUrl": "",
|
||||||
"androidPackage": "im.token.app",
|
|
||||||
"connections": [
|
"connections": [
|
||||||
{
|
{
|
||||||
"type": "walletConnectV2",
|
"type": "walletConnectV2",
|
||||||
@@ -199,7 +196,7 @@
|
|||||||
},
|
},
|
||||||
"mobile": {
|
"mobile": {
|
||||||
"native": "trust:",
|
"native": "trust:",
|
||||||
"universal": ""
|
"universal": "https://link.trustwallet.com"
|
||||||
},
|
},
|
||||||
"desktop": {
|
"desktop": {
|
||||||
"native": "",
|
"native": "",
|
||||||
@@ -217,7 +214,6 @@
|
|||||||
"iosMinVersion": "0",
|
"iosMinVersion": "0",
|
||||||
"encoding": "=\"#%/<>?@\\^`{|}:&",
|
"encoding": "=\"#%/<>?@\\^`{|}:&",
|
||||||
"imageUrl": "",
|
"imageUrl": "",
|
||||||
"androidPackage": "com.wallet.crypto.trustapp",
|
|
||||||
"connections": [
|
"connections": [
|
||||||
{
|
{
|
||||||
"type": "walletConnectV2",
|
"type": "walletConnectV2",
|
||||||
@@ -275,7 +271,6 @@
|
|||||||
"iosMinVersion": "0",
|
"iosMinVersion": "0",
|
||||||
"encoding": "=\"#%/<>?@\\^`{|}:&",
|
"encoding": "=\"#%/<>?@\\^`{|}:&",
|
||||||
"imageUrl": "",
|
"imageUrl": "",
|
||||||
"androidPackage": "io.zerion.android",
|
|
||||||
"connections": [
|
"connections": [
|
||||||
{
|
{
|
||||||
"type": "walletConnectV2",
|
"type": "walletConnectV2",
|
||||||
@@ -332,7 +327,6 @@
|
|||||||
"comment": "TokenPocket",
|
"comment": "TokenPocket",
|
||||||
"iosMinVersion": "0",
|
"iosMinVersion": "0",
|
||||||
"imageUrl": "",
|
"imageUrl": "",
|
||||||
"androidPackage": "vip.mytokenpocket",
|
|
||||||
"connections": [
|
"connections": [
|
||||||
{
|
{
|
||||||
"type": "walletConnectV2",
|
"type": "walletConnectV2",
|
||||||
@@ -389,7 +383,6 @@
|
|||||||
"comment": "Rainbow",
|
"comment": "Rainbow",
|
||||||
"iosMinVersion": "0",
|
"iosMinVersion": "0",
|
||||||
"encoding": "=\"#%/<>?@\\^`{|}:&",
|
"encoding": "=\"#%/<>?@\\^`{|}:&",
|
||||||
"androidPackage": "me.rainbow",
|
|
||||||
"backlinked": true,
|
"backlinked": true,
|
||||||
"imageUrl": "",
|
"imageUrl": "",
|
||||||
"connections": [
|
"connections": [
|
||||||
@@ -449,7 +442,6 @@
|
|||||||
"comment": "1inch",
|
"comment": "1inch",
|
||||||
"iosMinVersion": "0",
|
"iosMinVersion": "0",
|
||||||
"encoding": "=\"#%/<>?@\\^`{|}:&",
|
"encoding": "=\"#%/<>?@\\^`{|}:&",
|
||||||
"androidPackage": "io.oneinch.android",
|
|
||||||
"imageUrl": "",
|
"imageUrl": "",
|
||||||
"connections": [
|
"connections": [
|
||||||
{
|
{
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
@@ -1,230 +0,0 @@
|
|||||||
<svg width="254" height="77" viewBox="0 0 254 77" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g id="Group 1258">
|
|
||||||
<g id="Ellipse 9" opacity="0.4" filter="url(#filter0_f_20300_14972)">
|
|
||||||
<ellipse cx="86.5425" cy="-3.91219" rx="116.542" ry="20.0878" fill="#7774FF"/>
|
|
||||||
</g>
|
|
||||||
<g id="Ellipse 10" opacity="0.3" filter="url(#filter1_f_20300_14972)">
|
|
||||||
<circle cx="74.5734" cy="4.63007" r="1.00507" fill="#9A9AFF"/>
|
|
||||||
</g>
|
|
||||||
<g id="Ellipse 12" opacity="0.3" filter="url(#filter2_f_20300_14972)">
|
|
||||||
<circle cx="82.1105" cy="7.64179" r="1.00507" fill="#9A9AFF"/>
|
|
||||||
</g>
|
|
||||||
<g id="Ellipse 22" opacity="0.3" filter="url(#filter3_f_20300_14972)">
|
|
||||||
<circle cx="116.785" cy="4.63007" r="1.00507" fill="#9A9AFF"/>
|
|
||||||
</g>
|
|
||||||
<g id="Ellipse 11" opacity="0.3" filter="url(#filter4_f_20300_14972)">
|
|
||||||
<circle cx="64.774" cy="8.90488" r="0.502536" fill="#9A9AFF"/>
|
|
||||||
</g>
|
|
||||||
<g id="Ellipse 18" opacity="0.3" filter="url(#filter5_f_20300_14972)">
|
|
||||||
<circle cx="96.4322" cy="3.87363" r="0.502536" fill="#9A9AFF"/>
|
|
||||||
</g>
|
|
||||||
<g id="Ellipse 20" opacity="0.3" filter="url(#filter6_f_20300_14972)">
|
|
||||||
<circle cx="76.0807" cy="14.9361" r="0.502536" fill="#9A9AFF"/>
|
|
||||||
</g>
|
|
||||||
<g id="Ellipse 21" opacity="0.3" filter="url(#filter7_f_20300_14972)">
|
|
||||||
<circle cx="104.725" cy="4.87754" r="0.502536" fill="#9A9AFF"/>
|
|
||||||
</g>
|
|
||||||
<g id="Ellipse 23" opacity="0.3" filter="url(#filter8_f_20300_14972)">
|
|
||||||
<circle cx="114.021" cy="7.13925" r="0.502536" fill="#9A9AFF"/>
|
|
||||||
</g>
|
|
||||||
<g id="Ellipse 24" opacity="0.3" filter="url(#filter9_f_20300_14972)">
|
|
||||||
<circle cx="109.75" cy="15.4361" r="0.502536" fill="#9A9AFF"/>
|
|
||||||
</g>
|
|
||||||
<g id="Ellipse 25" opacity="0.3" filter="url(#filter10_f_20300_14972)">
|
|
||||||
<circle cx="119.801" cy="12.6705" r="0.502536" fill="#9A9AFF"/>
|
|
||||||
</g>
|
|
||||||
<g id="Ellipse 27" opacity="0.3" filter="url(#filter11_f_20300_14972)">
|
|
||||||
<circle cx="94.0885" cy="11.0182" r="0.502536" fill="#9A9AFF"/>
|
|
||||||
</g>
|
|
||||||
<g id="Ellipse 30" opacity="0.3" filter="url(#filter12_f_20300_14972)">
|
|
||||||
<circle cx="154.475" cy="11.6666" r="0.502536" fill="#9A9AFF"/>
|
|
||||||
</g>
|
|
||||||
<g id="Ellipse 29" opacity="0.3" filter="url(#filter13_f_20300_14972)">
|
|
||||||
<circle cx="146.435" cy="8.90488" r="0.502536" fill="#9A9AFF"/>
|
|
||||||
</g>
|
|
||||||
<g id="Ellipse 15" opacity="0.3" filter="url(#filter14_f_20300_14972)">
|
|
||||||
<circle cx="24.0103" cy="4.44785" r="0.502536" fill="#9A9AFF"/>
|
|
||||||
</g>
|
|
||||||
<g id="Ellipse 33" opacity="0.3" filter="url(#filter15_f_20300_14972)">
|
|
||||||
<circle cx="-1.05899" cy="13.6783" r="0.502536" fill="#9A9AFF"/>
|
|
||||||
</g>
|
|
||||||
<g id="Ellipse 16" opacity="0.3" filter="url(#filter16_f_20300_14972)">
|
|
||||||
<circle cx="17.441" cy="16.4908" r="0.502536" fill="#9A9AFF"/>
|
|
||||||
</g>
|
|
||||||
<g id="Ellipse 35" opacity="0.3" filter="url(#filter17_f_20300_14972)">
|
|
||||||
<circle cx="36.0553" cy="6.63535" r="0.502536" fill="#9A9AFF"/>
|
|
||||||
</g>
|
|
||||||
<g id="Ellipse 32" opacity="0.3" filter="url(#filter18_f_20300_14972)">
|
|
||||||
<circle cx="10.8707" cy="14.2994" r="0.502536" fill="#9A9AFF"/>
|
|
||||||
</g>
|
|
||||||
<g id="Ellipse 13" opacity="0.3" filter="url(#filter19_f_20300_14972)">
|
|
||||||
<circle cx="55.7274" cy="9.65615" r="0.753803" fill="#9A9AFF"/>
|
|
||||||
</g>
|
|
||||||
<g id="Ellipse 19" opacity="0.3" filter="url(#filter20_f_20300_14972)">
|
|
||||||
<circle cx="87.3886" cy="20.7108" r="0.753803" fill="#9A9AFF"/>
|
|
||||||
</g>
|
|
||||||
<g id="Ellipse 26" opacity="0.3" filter="url(#filter21_f_20300_14972)">
|
|
||||||
<circle cx="123.82" cy="15.1874" r="0.753803" fill="#9A9AFF"/>
|
|
||||||
</g>
|
|
||||||
<g id="Ellipse 36" opacity="0.3" filter="url(#filter22_f_20300_14972)">
|
|
||||||
<circle cx="134.876" cy="14.4335" r="0.753803" fill="#9A9AFF"/>
|
|
||||||
</g>
|
|
||||||
<g id="Ellipse 28" opacity="0.3" filter="url(#filter23_f_20300_14972)">
|
|
||||||
<circle cx="129.6" cy="6.38662" r="0.753803" fill="#9A9AFF"/>
|
|
||||||
</g>
|
|
||||||
<g id="Ellipse 14" opacity="0.3" filter="url(#filter24_f_20300_14972)">
|
|
||||||
<circle cx="41.6571" cy="14.9335" r="0.753803" fill="#9A9AFF"/>
|
|
||||||
</g>
|
|
||||||
<g id="Ellipse 31" opacity="0.3" filter="url(#filter25_f_20300_14972)">
|
|
||||||
<circle cx="4.21767" cy="17.9491" r="0.753803" fill="#9A9AFF"/>
|
|
||||||
</g>
|
|
||||||
<g id="Ellipse 34" opacity="0.3" filter="url(#filter26_f_20300_14972)">
|
|
||||||
<circle cx="12.2177" cy="6.88662" r="0.753803" fill="#9A9AFF"/>
|
|
||||||
</g>
|
|
||||||
<g id="Ellipse 17" opacity="0.3" filter="url(#filter27_f_20300_14972)">
|
|
||||||
<circle cx="50.2001" cy="3.6249" r="0.753803" fill="#9A9AFF"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<filter id="filter0_f_20300_14972" x="-94" y="-88" width="361.085" height="168.176" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
||||||
<feGaussianBlur stdDeviation="32" result="effect1_foregroundBlur_20300_14972"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="filter1_f_20300_14972" x="72.5633" y="2.61993" width="4.01991" height="4.02186" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
||||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="filter2_f_20300_14972" x="80.6029" y="6.13418" width="3.01484" height="3.01679" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
||||||
<feGaussianBlur stdDeviation="0.251268" result="effect1_foregroundBlur_20300_14972"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="filter3_f_20300_14972" x="115.278" y="3.12246" width="3.01484" height="3.01679" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
||||||
<feGaussianBlur stdDeviation="0.251268" result="effect1_foregroundBlur_20300_14972"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="filter4_f_20300_14972" x="63.2664" y="7.39727" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
||||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="filter5_f_20300_14972" x="94.9246" y="2.36602" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
||||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="filter6_f_20300_14972" x="74.5731" y="13.4285" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
||||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="filter7_f_20300_14972" x="103.218" y="3.36993" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
||||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="filter8_f_20300_14972" x="112.513" y="5.63165" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
||||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="filter9_f_20300_14972" x="108.242" y="13.9285" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
||||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="filter10_f_20300_14972" x="118.294" y="11.1629" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
||||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="filter11_f_20300_14972" x="92.5809" y="9.51055" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
||||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="filter12_f_20300_14972" x="153.47" y="10.6615" width="2.00995" height="2.00898" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
||||||
<feGaussianBlur stdDeviation="0.251268" result="effect1_foregroundBlur_20300_14972"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="filter13_f_20300_14972" x="144.928" y="7.39727" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
||||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="filter14_f_20300_14972" x="22.5027" y="2.94024" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
||||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="filter15_f_20300_14972" x="-2.56659" y="12.1707" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
||||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="filter16_f_20300_14972" x="15.9334" y="14.9832" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
||||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="filter17_f_20300_14972" x="35.0502" y="5.63028" width="2.00995" height="2.00898" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
||||||
<feGaussianBlur stdDeviation="0.251268" result="effect1_foregroundBlur_20300_14972"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="filter18_f_20300_14972" x="9.86563" y="13.2943" width="2.00995" height="2.00898" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
||||||
<feGaussianBlur stdDeviation="0.251268" result="effect1_foregroundBlur_20300_14972"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="filter19_f_20300_14972" x="53.9686" y="7.89727" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
||||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="filter20_f_20300_14972" x="85.6297" y="18.952" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
||||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="filter21_f_20300_14972" x="122.061" y="13.4285" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
||||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="filter22_f_20300_14972" x="133.117" y="12.6746" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
||||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="filter23_f_20300_14972" x="127.842" y="4.62774" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
||||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="filter24_f_20300_14972" x="39.8982" y="13.1746" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
||||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="filter25_f_20300_14972" x="2.4588" y="16.1902" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
||||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="filter26_f_20300_14972" x="10.4588" y="5.12774" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
||||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="filter27_f_20300_14972" x="48.4412" y="1.86602" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
||||||
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
|
|
||||||
</filter>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 300 KiB |
|
Before Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 481 KiB |
|
Before Width: | Height: | Size: 297 B After Width: | Height: | Size: 817 B |
@@ -1,38 +0,0 @@
|
|||||||
## App Integrations
|
|
||||||
### Amplitude
|
|
||||||
Analytics service used to collect data to help the dYdX Product team make informed product decisions.
|
|
||||||
|
|
||||||
<b>To use with dydxprotocol/v4-web:</b>
|
|
||||||
1. Amplitude account with API key.
|
|
||||||
2. Add API key in Github > Secrets and Variables > Actions as `AMPLITUDE_API_KEY`
|
|
||||||
3. In your deploy scripts add `pnpm run build:inject-amplitude` after your pnpm build / vite build command.
|
|
||||||
|
|
||||||
### Bugsnag
|
|
||||||
Error handling service used to collect handled/unhandled errors within the app. The information collected is used to help debug and alert the engineering team of crashes/unhandled errors within the app to improve stability.
|
|
||||||
|
|
||||||
<b>To use with dydxprotocol/v4-web:</b>
|
|
||||||
1. Bugsnag account with API key.
|
|
||||||
2. Add API key in Github > Secrets and Variables > Actions as `BUGSNAG_API_KEY`
|
|
||||||
3. In your deploy scripts add `pnpm run build:inject-bugsnag` after your pnpm build / vite build command.
|
|
||||||
4. If you are using with the Amplitude deployment scripts, your build command may look like the following: `pnpm build && pnpm run build:inject-amplitude && pnpm run build:inject-bugsnag`
|
|
||||||
|
|
||||||
### StatusPage
|
|
||||||
Service used to inform users of any status updates to our platform. These status updates are manually set and updated by the On Call engineers.
|
|
||||||
|
|
||||||
<b>To use with dydxprotocol/v4-web:</b>
|
|
||||||
1. StatusPage account and script URI
|
|
||||||
2. Add API key in Github > Secrets and Variables > Actions as `STATUS_PAGE_SCRIPT_URI`
|
|
||||||
3. In your deploy scripts add `pnpm run build:inject-statuspage` after your pnpm build / vite build command.
|
|
||||||
4. If you are using with the Amplitude deployment scripts, your build command may look like the following: `pnpm build && pnpm run build:inject-amplitude && pnpm run build:inject-statuspage`
|
|
||||||
|
|
||||||
### Intercom
|
|
||||||
Service used for live customer support (chat/inbox) as well as home for Help Articles.
|
|
||||||
|
|
||||||
<b>To use with dydxprotocol/v4-web:</b>
|
|
||||||
1. Create Intercom account
|
|
||||||
2. In Intercom UI
|
|
||||||
Getting started > Set up Messenger > will give you your API Key on Step 2
|
|
||||||
Customize Intercom Messenger by adding logo and brand colors
|
|
||||||
3. Add API key in Github > Secrets and Variables > Actions as `INTERCOM_APP_ID`
|
|
||||||
4. In your deploy scripts add `pnpm run build:inject-intercom` after your pnpm build / vite build command.
|
|
||||||
5. If you are using with the Amplitude deployment scripts, your build command may look like the following: `pnpm build && pnpm run build:inject-amplitude && pnpm run build:inject-intercom`
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { lazy, Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||||
import styled, { AnyStyledComponent, css } from 'styled-components';
|
import styled, { AnyStyledComponent, css } from 'styled-components';
|
||||||
import { WagmiConfig } from 'wagmi';
|
import { WagmiConfig } from 'wagmi';
|
||||||
@@ -16,16 +16,26 @@ import {
|
|||||||
} from '@/hooks';
|
} from '@/hooks';
|
||||||
import { DydxProvider } from '@/hooks/useDydxClient';
|
import { DydxProvider } from '@/hooks/useDydxClient';
|
||||||
import { AccountsProvider } from '@/hooks/useAccounts';
|
import { AccountsProvider } from '@/hooks/useAccounts';
|
||||||
import { AppThemeProvider } from '@/hooks/useAppTheme';
|
|
||||||
import { DialogAreaProvider, useDialogArea } from '@/hooks/useDialogArea';
|
import { DialogAreaProvider, useDialogArea } from '@/hooks/useDialogArea';
|
||||||
import { LocaleProvider } from '@/hooks/useLocaleSeparators';
|
import { LocaleProvider } from '@/hooks/useLocaleSeparators';
|
||||||
import { NotificationsProvider } from '@/hooks/useNotifications';
|
import { NotificationsProvider } from '@/hooks/useNotifications';
|
||||||
import { LocalNotificationsProvider } from '@/hooks/useLocalNotifications';
|
import { LocalNotificationsProvider } from '@/hooks/useLocalNotifications';
|
||||||
import { RestrictionProvider } from '@/hooks/useRestrictions';
|
import { RestrictionProvider } from '@/hooks/useRestrictions';
|
||||||
import { SubaccountProvider } from '@/hooks/useSubaccount';
|
import { SubaccountProvider } from '@/hooks/useSubaccount';
|
||||||
|
import { SquidProvider } from '@/hooks/useSquid';
|
||||||
|
|
||||||
import { GuardedMobileRoute } from '@/components/GuardedMobileRoute';
|
import { GuardedMobileRoute } from '@/components/GuardedMobileRoute';
|
||||||
|
|
||||||
|
import MarketsPage from '@/pages/markets/Markets';
|
||||||
|
import PortfolioPage from '@/pages/portfolio/Portfolio';
|
||||||
|
import { AlertsPage } from '@/pages/AlertsPage';
|
||||||
|
import ProfilePage from '@/pages/Profile';
|
||||||
|
import { SettingsPage } from '@/pages/settings/Settings';
|
||||||
|
import TradePage from '@/pages/trade/Trade';
|
||||||
|
import { RewardsPage } from '@/pages/rewards/RewardsPage';
|
||||||
|
import { TermsOfUsePage } from '@/pages/TermsOfUsePage';
|
||||||
|
import { PrivacyPolicyPage } from '@/pages/PrivacyPolicyPage';
|
||||||
|
|
||||||
import { HeaderDesktop } from '@/layout/Header/HeaderDesktop';
|
import { HeaderDesktop } from '@/layout/Header/HeaderDesktop';
|
||||||
import { FooterDesktop } from '@/layout/Footer/FooterDesktop';
|
import { FooterDesktop } from '@/layout/Footer/FooterDesktop';
|
||||||
import { FooterMobile } from '@/layout/Footer/FooterMobile';
|
import { FooterMobile } from '@/layout/Footer/FooterMobile';
|
||||||
@@ -36,24 +46,12 @@ import { GlobalCommandDialog } from '@/views/dialogs/GlobalCommandDialog';
|
|||||||
import { config } from '@/lib/wagmi';
|
import { config } from '@/lib/wagmi';
|
||||||
|
|
||||||
import { breakpoints } from '@/styles';
|
import { breakpoints } from '@/styles';
|
||||||
import { GlobalStyle } from '@/styles/globalStyle';
|
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
import { LoadingSpace } from './components/Loading/LoadingSpinner';
|
|
||||||
|
|
||||||
import '@/styles/constants.css';
|
import '@/styles/constants.css';
|
||||||
import '@/styles/fonts.css';
|
import '@/styles/fonts.css';
|
||||||
import '@/styles/web3modal.css';
|
import '@/styles/web3modal.css';
|
||||||
|
|
||||||
const MarketsPage = lazy(() => import('@/pages/markets/Markets'));
|
|
||||||
const PortfolioPage = lazy(() => import('@/pages/portfolio/Portfolio'));
|
|
||||||
const AlertsPage = lazy(() => import('@/pages/AlertsPage'));
|
|
||||||
const ProfilePage = lazy(() => import('@/pages/Profile'));
|
|
||||||
const SettingsPage = lazy(() => import('@/pages/settings/Settings'));
|
|
||||||
const TradePage = lazy(() => import('@/pages/trade/Trade'));
|
|
||||||
const RewardsPage = lazy(() => import('@/pages/rewards/RewardsPage'));
|
|
||||||
const TermsOfUsePage = lazy(() => import('@/pages/TermsOfUsePage'));
|
|
||||||
const PrivacyPolicyPage = lazy(() => import('@/pages/PrivacyPolicyPage'));
|
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
const Content = () => {
|
const Content = () => {
|
||||||
@@ -68,52 +66,49 @@ const Content = () => {
|
|||||||
const { chainTokenLabel } = useTokenConfigs();
|
const { chainTokenLabel } = useTokenConfigs();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Styled.Content isShowingHeader={isShowingHeader} isShowingFooter={isShowingFooter}>
|
||||||
<GlobalStyle />
|
{isNotTablet && <HeaderDesktop />}
|
||||||
<Styled.Content isShowingHeader={isShowingHeader} isShowingFooter={isShowingFooter}>
|
|
||||||
{isNotTablet && <HeaderDesktop />}
|
|
||||||
|
|
||||||
<Styled.Main>
|
<Styled.Main>
|
||||||
<Suspense fallback={<LoadingSpace id="main" />}>
|
<Suspense fallback={null}>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path={AppRoute.Trade}>
|
<Route path={AppRoute.Trade}>
|
||||||
<Route path=":market" element={<TradePage />} />
|
<Route path=":market" element={<TradePage />} />
|
||||||
<Route path={AppRoute.Trade} element={<TradePage />} />
|
<Route path={AppRoute.Trade} element={<TradePage />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path={AppRoute.Markets} element={<MarketsPage />} />
|
<Route path={AppRoute.Markets} element={<MarketsPage />} />
|
||||||
<Route path={`/${chainTokenLabel}`} element={<RewardsPage />} />
|
<Route path={`/${chainTokenLabel}`} element={<RewardsPage />} />
|
||||||
{isTablet && (
|
{isTablet && (
|
||||||
<>
|
<>
|
||||||
<Route path={AppRoute.Alerts} element={<AlertsPage />} />
|
<Route path={AppRoute.Alerts} element={<AlertsPage />} />
|
||||||
<Route path={AppRoute.Profile} element={<ProfilePage />} />
|
<Route path={AppRoute.Profile} element={<ProfilePage />} />
|
||||||
<Route path={`${AppRoute.Settings}/*`} element={<SettingsPage />} />
|
<Route path={`${AppRoute.Settings}/*`} element={<SettingsPage />} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Route element={<GuardedMobileRoute />}>
|
<Route element={<GuardedMobileRoute />}>
|
||||||
<Route path={`${AppRoute.Portfolio}/*`} element={<PortfolioPage />} />
|
<Route path={`${AppRoute.Portfolio}/*`} element={<PortfolioPage />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path={AppRoute.Terms} element={<TermsOfUsePage />} />
|
<Route path={AppRoute.Terms} element={<TermsOfUsePage />} />
|
||||||
<Route path={AppRoute.Privacy} element={<PrivacyPolicyPage />} />
|
<Route path={AppRoute.Privacy} element={<PrivacyPolicyPage />} />
|
||||||
|
|
||||||
<Route path="*" element={<Navigate to={DEFAULT_TRADE_ROUTE} replace />} />
|
<Route path="*" element={<Navigate to={DEFAULT_TRADE_ROUTE} replace />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</Styled.Main>
|
</Styled.Main>
|
||||||
|
|
||||||
{isTablet ? <FooterMobile /> : <FooterDesktop />}
|
{isTablet ? <FooterMobile /> : <FooterDesktop />}
|
||||||
|
|
||||||
<Styled.NotificationsToastArea />
|
<Styled.NotificationsToastArea />
|
||||||
|
|
||||||
<Styled.DialogArea ref={setDialogArea}>
|
<Styled.DialogArea ref={setDialogArea}>
|
||||||
<DialogManager />
|
<DialogManager />
|
||||||
</Styled.DialogArea>
|
</Styled.DialogArea>
|
||||||
|
|
||||||
<GlobalCommandDialog />
|
<GlobalCommandDialog />
|
||||||
</Styled.Content>
|
</Styled.Content>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -133,10 +128,10 @@ const providers = [
|
|||||||
wrapProvider(DydxProvider),
|
wrapProvider(DydxProvider),
|
||||||
wrapProvider(AccountsProvider),
|
wrapProvider(AccountsProvider),
|
||||||
wrapProvider(SubaccountProvider),
|
wrapProvider(SubaccountProvider),
|
||||||
|
wrapProvider(SquidProvider),
|
||||||
wrapProvider(LocalNotificationsProvider),
|
wrapProvider(LocalNotificationsProvider),
|
||||||
wrapProvider(NotificationsProvider),
|
wrapProvider(NotificationsProvider),
|
||||||
wrapProvider(DialogAreaProvider),
|
wrapProvider(DialogAreaProvider),
|
||||||
wrapProvider(AppThemeProvider),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
|
|||||||
@@ -1,390 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "name",
|
|
||||||
"outputs": [{ "name": "", "type": "string" }],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": false,
|
|
||||||
"inputs": [{ "name": "_upgradedAddress", "type": "address" }],
|
|
||||||
"name": "deprecate",
|
|
||||||
"outputs": [],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": false,
|
|
||||||
"inputs": [
|
|
||||||
{ "name": "_spender", "type": "address" },
|
|
||||||
{ "name": "_value", "type": "uint256" }
|
|
||||||
],
|
|
||||||
"name": "approve",
|
|
||||||
"outputs": [],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "deprecated",
|
|
||||||
"outputs": [{ "name": "", "type": "bool" }],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": false,
|
|
||||||
"inputs": [{ "name": "_evilUser", "type": "address" }],
|
|
||||||
"name": "addBlackList",
|
|
||||||
"outputs": [],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "totalSupply",
|
|
||||||
"outputs": [{ "name": "", "type": "uint256" }],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": false,
|
|
||||||
"inputs": [
|
|
||||||
{ "name": "_from", "type": "address" },
|
|
||||||
{ "name": "_to", "type": "address" },
|
|
||||||
{ "name": "_value", "type": "uint256" }
|
|
||||||
],
|
|
||||||
"name": "transferFrom",
|
|
||||||
"outputs": [],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "upgradedAddress",
|
|
||||||
"outputs": [{ "name": "", "type": "address" }],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [{ "name": "", "type": "address" }],
|
|
||||||
"name": "balances",
|
|
||||||
"outputs": [{ "name": "", "type": "uint256" }],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "decimals",
|
|
||||||
"outputs": [{ "name": "", "type": "uint256" }],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "maximumFee",
|
|
||||||
"outputs": [{ "name": "", "type": "uint256" }],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "_totalSupply",
|
|
||||||
"outputs": [{ "name": "", "type": "uint256" }],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": false,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "unpause",
|
|
||||||
"outputs": [],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [{ "name": "_maker", "type": "address" }],
|
|
||||||
"name": "getBlackListStatus",
|
|
||||||
"outputs": [{ "name": "", "type": "bool" }],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{ "name": "", "type": "address" },
|
|
||||||
{ "name": "", "type": "address" }
|
|
||||||
],
|
|
||||||
"name": "allowed",
|
|
||||||
"outputs": [{ "name": "", "type": "uint256" }],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "paused",
|
|
||||||
"outputs": [{ "name": "", "type": "bool" }],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [{ "name": "who", "type": "address" }],
|
|
||||||
"name": "balanceOf",
|
|
||||||
"outputs": [{ "name": "", "type": "uint256" }],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": false,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "pause",
|
|
||||||
"outputs": [],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "getOwner",
|
|
||||||
"outputs": [{ "name": "", "type": "address" }],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "owner",
|
|
||||||
"outputs": [{ "name": "", "type": "address" }],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "symbol",
|
|
||||||
"outputs": [{ "name": "", "type": "string" }],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": false,
|
|
||||||
"inputs": [
|
|
||||||
{ "name": "_to", "type": "address" },
|
|
||||||
{ "name": "_value", "type": "uint256" }
|
|
||||||
],
|
|
||||||
"name": "transfer",
|
|
||||||
"outputs": [],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": false,
|
|
||||||
"inputs": [
|
|
||||||
{ "name": "newBasisPoints", "type": "uint256" },
|
|
||||||
{ "name": "newMaxFee", "type": "uint256" }
|
|
||||||
],
|
|
||||||
"name": "setParams",
|
|
||||||
"outputs": [],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": false,
|
|
||||||
"inputs": [{ "name": "amount", "type": "uint256" }],
|
|
||||||
"name": "issue",
|
|
||||||
"outputs": [],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": false,
|
|
||||||
"inputs": [{ "name": "amount", "type": "uint256" }],
|
|
||||||
"name": "redeem",
|
|
||||||
"outputs": [],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{ "name": "_owner", "type": "address" },
|
|
||||||
{ "name": "_spender", "type": "address" }
|
|
||||||
],
|
|
||||||
"name": "allowance",
|
|
||||||
"outputs": [{ "name": "remaining", "type": "uint256" }],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "basisPointsRate",
|
|
||||||
"outputs": [{ "name": "", "type": "uint256" }],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [{ "name": "", "type": "address" }],
|
|
||||||
"name": "isBlackListed",
|
|
||||||
"outputs": [{ "name": "", "type": "bool" }],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": false,
|
|
||||||
"inputs": [{ "name": "_clearedUser", "type": "address" }],
|
|
||||||
"name": "removeBlackList",
|
|
||||||
"outputs": [],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "MAX_UINT",
|
|
||||||
"outputs": [{ "name": "", "type": "uint256" }],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": false,
|
|
||||||
"inputs": [{ "name": "newOwner", "type": "address" }],
|
|
||||||
"name": "transferOwnership",
|
|
||||||
"outputs": [],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": false,
|
|
||||||
"inputs": [{ "name": "_blackListedUser", "type": "address" }],
|
|
||||||
"name": "destroyBlackFunds",
|
|
||||||
"outputs": [],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{ "name": "_initialSupply", "type": "uint256" },
|
|
||||||
{ "name": "_name", "type": "string" },
|
|
||||||
{ "name": "_symbol", "type": "string" },
|
|
||||||
{ "name": "_decimals", "type": "uint256" }
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "constructor"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"anonymous": false,
|
|
||||||
"inputs": [{ "indexed": false, "name": "amount", "type": "uint256" }],
|
|
||||||
"name": "Issue",
|
|
||||||
"type": "event"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"anonymous": false,
|
|
||||||
"inputs": [{ "indexed": false, "name": "amount", "type": "uint256" }],
|
|
||||||
"name": "Redeem",
|
|
||||||
"type": "event"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"anonymous": false,
|
|
||||||
"inputs": [{ "indexed": false, "name": "newAddress", "type": "address" }],
|
|
||||||
"name": "Deprecate",
|
|
||||||
"type": "event"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"anonymous": false,
|
|
||||||
"inputs": [
|
|
||||||
{ "indexed": false, "name": "feeBasisPoints", "type": "uint256" },
|
|
||||||
{ "indexed": false, "name": "maxFee", "type": "uint256" }
|
|
||||||
],
|
|
||||||
"name": "Params",
|
|
||||||
"type": "event"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"anonymous": false,
|
|
||||||
"inputs": [
|
|
||||||
{ "indexed": false, "name": "_blackListedUser", "type": "address" },
|
|
||||||
{ "indexed": false, "name": "_balance", "type": "uint256" }
|
|
||||||
],
|
|
||||||
"name": "DestroyedBlackFunds",
|
|
||||||
"type": "event"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"anonymous": false,
|
|
||||||
"inputs": [{ "indexed": false, "name": "_user", "type": "address" }],
|
|
||||||
"name": "AddedBlackList",
|
|
||||||
"type": "event"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"anonymous": false,
|
|
||||||
"inputs": [{ "indexed": false, "name": "_user", "type": "address" }],
|
|
||||||
"name": "RemovedBlackList",
|
|
||||||
"type": "event"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"anonymous": false,
|
|
||||||
"inputs": [
|
|
||||||
{ "indexed": true, "name": "owner", "type": "address" },
|
|
||||||
{ "indexed": true, "name": "spender", "type": "address" },
|
|
||||||
{ "indexed": false, "name": "value", "type": "uint256" }
|
|
||||||
],
|
|
||||||
"name": "Approval",
|
|
||||||
"type": "event"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"anonymous": false,
|
|
||||||
"inputs": [
|
|
||||||
{ "indexed": true, "name": "from", "type": "address" },
|
|
||||||
{ "indexed": true, "name": "to", "type": "address" },
|
|
||||||
{ "indexed": false, "name": "value", "type": "uint256" }
|
|
||||||
],
|
|
||||||
"name": "Transfer",
|
|
||||||
"type": "event"
|
|
||||||
},
|
|
||||||
{ "anonymous": false, "inputs": [], "name": "Pause", "type": "event" },
|
|
||||||
{ "anonymous": false, "inputs": [], "name": "Unpause", "type": "event" }
|
|
||||||
]
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import type { Story } from '@ladle/react';
|
|
||||||
|
|
||||||
import { Accordion as AccordionComponent, AccordionProps } from '@/components/Accordion';
|
|
||||||
|
|
||||||
import { StoryWrapper } from '.ladle/components';
|
|
||||||
|
|
||||||
export const Accordion: Story<AccordionProps> = (args) => {
|
|
||||||
return (
|
|
||||||
<StoryWrapper>
|
|
||||||
<AccordionComponent {...args} />
|
|
||||||
</StoryWrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
Accordion.args = {
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
header: 'Question 1?',
|
|
||||||
content: 'Answer 1.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
header: 'Question 2?',
|
|
||||||
content: 'Answer 2.',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
import styled, { keyframes, type AnyStyledComponent } from 'styled-components';
|
|
||||||
|
|
||||||
import { Root, Item, Header, Trigger, Content } from '@radix-ui/react-accordion';
|
|
||||||
|
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
|
||||||
import { breakpoints } from '@/styles';
|
|
||||||
|
|
||||||
import { PlusIcon } from '@/icons';
|
|
||||||
|
|
||||||
export type AccordionItem = {
|
|
||||||
header: React.ReactNode;
|
|
||||||
content: React.ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type AccordionProps = {
|
|
||||||
items: AccordionItem[];
|
|
||||||
className?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Accordion = ({ items, className }: AccordionProps) => (
|
|
||||||
<Styled.Root className={className} type="single" collapsible>
|
|
||||||
{items.map(({ header, content }, idx) => (
|
|
||||||
<Styled.Item key={idx} value={idx.toString()}>
|
|
||||||
<Header>
|
|
||||||
<Styled.Trigger>
|
|
||||||
{header}
|
|
||||||
<Styled.Icon>
|
|
||||||
<PlusIcon />
|
|
||||||
</Styled.Icon>
|
|
||||||
</Styled.Trigger>
|
|
||||||
</Header>
|
|
||||||
<Styled.Content>{content}</Styled.Content>
|
|
||||||
</Styled.Item>
|
|
||||||
))}
|
|
||||||
</Styled.Root>
|
|
||||||
);
|
|
||||||
|
|
||||||
const Styled: Record<string, AnyStyledComponent> = {};
|
|
||||||
|
|
||||||
Styled.Root = styled(Root)`
|
|
||||||
--accordion-paddingY: 1rem;
|
|
||||||
--accordion-paddingX: 1rem;
|
|
||||||
|
|
||||||
@media ${breakpoints.notTablet} {
|
|
||||||
--accordion-paddingX: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
> *:not(:last-child) {
|
|
||||||
border-bottom: var(--border-width) solid var(--border-color);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
Styled.Item = styled(Item)``;
|
|
||||||
|
|
||||||
Styled.Icon = styled.div`
|
|
||||||
display: inline-flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
width: 2.25rem;
|
|
||||||
height: 2.25rem;
|
|
||||||
|
|
||||||
--color-border: var(--color-layer-6);
|
|
||||||
color: var(--color-text-1);
|
|
||||||
background-color: var(--color-layer-5);
|
|
||||||
border: solid var(--border-width) var(--color-border);
|
|
||||||
border-radius: 50%;
|
|
||||||
font: var(--font-small-book);
|
|
||||||
|
|
||||||
svg {
|
|
||||||
height: 1.125em;
|
|
||||||
width: 1.125em;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
Styled.Trigger = styled(Trigger)`
|
|
||||||
${layoutMixins.spacedRow}
|
|
||||||
width: 100%;
|
|
||||||
padding: var(--accordion-paddingY) var(--accordion-paddingX);
|
|
||||||
gap: 0.5rem;
|
|
||||||
|
|
||||||
color: var(--color-text-1);
|
|
||||||
text-align: start;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
${Styled.Icon} {
|
|
||||||
color: var(--color-text-2);
|
|
||||||
filter: brightness(1.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
|
||||||
color: var(--color-text-0);
|
|
||||||
transition: transform 0.3s var(--ease-out-expo);
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-state='open'] svg {
|
|
||||||
transform: rotate(45deg);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
Styled.Content = styled(Content)`
|
|
||||||
overflow: hidden;
|
|
||||||
margin: 0 var(--accordion-paddingX) var(--accordion-paddingY);
|
|
||||||
|
|
||||||
color: var(--color-text-0);
|
|
||||||
|
|
||||||
&[data-state='open'] {
|
|
||||||
animation: ${keyframes`
|
|
||||||
from {
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
height: var(--radix-accordion-content-height);
|
|
||||||
}
|
|
||||||
`} 0.3s var(--ease-out-expo);
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-state='closed'] {
|
|
||||||
animation: ${keyframes`
|
|
||||||
from {
|
|
||||||
height: var(--radix-accordion-content-height);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
`} 0.1s var(--ease-in-expo);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
@@ -45,7 +45,6 @@ const assetIcons = {
|
|||||||
SOL: '/currencies/sol.png',
|
SOL: '/currencies/sol.png',
|
||||||
SUI: '/currencies/sui.png',
|
SUI: '/currencies/sui.png',
|
||||||
SUSHI: '/currencies/sushi.png',
|
SUSHI: '/currencies/sushi.png',
|
||||||
TIA: '/currencies/tia.png',
|
|
||||||
TRX: '/currencies/trx.png',
|
TRX: '/currencies/trx.png',
|
||||||
UMA: '/currencies/uma.png',
|
UMA: '/currencies/uma.png',
|
||||||
UNI: '/currencies/uni.png',
|
UNI: '/currencies/uni.png',
|
||||||
@@ -72,13 +71,8 @@ export const AssetIcon = ({
|
|||||||
}: {
|
}: {
|
||||||
symbol?: Nullable<string>;
|
symbol?: Nullable<string>;
|
||||||
className?: string;
|
className?: string;
|
||||||
}) => (
|
}) =>
|
||||||
<Styled.Img
|
isAssetSymbol(symbol) ? <Styled.Img src={assetIcons[symbol]} className={className} /> : null;
|
||||||
src={isAssetSymbol(symbol) ? assetIcons[symbol] : '/currencies/unavailable.png'}
|
|
||||||
className={className}
|
|
||||||
alt={symbol}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
const Styled: Record<string, AnyStyledComponent> = {};
|
const Styled: Record<string, AnyStyledComponent> = {};
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export const Button = forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonPr
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledBaseButton
|
<StyledBaseButton
|
||||||
disabled={state[ButtonState.Disabled] || state[ButtonState.Loading]}
|
disabled={state[ButtonState.Disabled]}
|
||||||
{...{ ref, action, size, shape, state, ...otherProps }}
|
{...{ ref, action, size, shape, state, ...otherProps }}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import { forwardRef } from 'react';
|
|
||||||
|
|
||||||
type CanvasProps = {
|
|
||||||
className?: string;
|
|
||||||
width: number | string;
|
|
||||||
height: number | string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Canvas = forwardRef<HTMLCanvasElement, CanvasProps>(
|
|
||||||
({ className, width, height }, canvasRef) => (
|
|
||||||
<canvas ref={canvasRef} className={className} width={width} height={height} />
|
|
||||||
)
|
|
||||||
);
|
|
||||||
@@ -5,7 +5,7 @@ import { Checkbox, CheckboxProps } from '@/components/Checkbox';
|
|||||||
|
|
||||||
import { StoryWrapper } from '.ladle/components';
|
import { StoryWrapper } from '.ladle/components';
|
||||||
|
|
||||||
export const CheckboxStory: Story<CheckboxProps> = (args) => {
|
export const Checkboxes: Story<CheckboxProps> = (args) => {
|
||||||
const [isChecked, setIsChecked] = useState(false);
|
const [isChecked, setIsChecked] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -13,20 +13,10 @@ export const CheckboxStory: Story<CheckboxProps> = (args) => {
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
{...args}
|
{...args}
|
||||||
checked={isChecked}
|
checked={isChecked}
|
||||||
onCheckedChange={setIsChecked}
|
onClick={(e: React.ChangeEvent) => setIsChecked((e.target as HTMLInputElement).checked)}
|
||||||
id="story-checkbox"
|
|
||||||
label="example label"
|
|
||||||
/>
|
/>
|
||||||
</StoryWrapper>
|
</StoryWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
CheckboxStory.args = {};
|
Checkboxes.args = {};
|
||||||
|
|
||||||
CheckboxStory.argTypes = {
|
|
||||||
disabled: {
|
|
||||||
options: [true, false],
|
|
||||||
control: { type: 'select' },
|
|
||||||
defaultValue: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
import styled, { css, type AnyStyledComponent } from 'styled-components';
|
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||||
import { Root, Indicator } from '@radix-ui/react-checkbox';
|
|
||||||
import { CheckIcon } from '@radix-ui/react-icons';
|
|
||||||
|
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { breakpoints } from '@/styles';
|
||||||
|
|
||||||
type ElementProps = {
|
type ElementProps = {
|
||||||
checked: boolean;
|
checked: boolean;
|
||||||
onCheckedChange: (checked: boolean) => void;
|
onClick: (e: React.ChangeEvent) => void;
|
||||||
id?: string;
|
|
||||||
label?: React.ReactNode;
|
|
||||||
disabled?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type StyleProps = {
|
type StyleProps = {
|
||||||
@@ -18,69 +13,85 @@ type StyleProps = {
|
|||||||
|
|
||||||
export type CheckboxProps = ElementProps & StyleProps;
|
export type CheckboxProps = ElementProps & StyleProps;
|
||||||
|
|
||||||
export const Checkbox: React.FC<CheckboxProps> = ({
|
export const Checkbox: React.FC<CheckboxProps> = ({ checked, className, onClick }) => {
|
||||||
checked,
|
return (
|
||||||
className,
|
<Styled.CheckboxWrapper className={className}>
|
||||||
onCheckedChange,
|
<Styled.Checkbox
|
||||||
id,
|
type="checkbox"
|
||||||
label,
|
checked={checked}
|
||||||
disabled
|
onChange={onClick}
|
||||||
}) => (
|
/>
|
||||||
<Styled.Container>
|
<Styled.CustomCheckbox />
|
||||||
<Styled.Root className={className} checked={checked} disabled={disabled} onCheckedChange={onCheckedChange} id={id}>
|
</Styled.CheckboxWrapper>
|
||||||
<Styled.Indicator>
|
);
|
||||||
<CheckIcon />
|
};
|
||||||
</Styled.Indicator>
|
|
||||||
</Styled.Root>
|
|
||||||
{label && <Styled.label disabled={disabled} htmlFor={id}>{label}</Styled.label>}
|
|
||||||
</Styled.Container>
|
|
||||||
);
|
|
||||||
|
|
||||||
const Styled: Record<string, AnyStyledComponent> = {};
|
const Styled: Record<string, AnyStyledComponent> = {};
|
||||||
|
|
||||||
Styled.Container = styled.div`
|
Styled.CustomCheckbox = styled.span`
|
||||||
${layoutMixins.row}
|
width: 1.25em;
|
||||||
gap: 1ch;
|
height: 1.25em;
|
||||||
font: var(--font-small-book);
|
|
||||||
|
|
||||||
label {
|
position: absolute;
|
||||||
cursor: pointer;
|
left: 0;
|
||||||
}
|
right: 0;
|
||||||
`;
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
Styled.Root = styled(Root)`
|
|
||||||
--checkbox-backgroundColor: var(--color-layer-0);
|
|
||||||
--checkbox-borderColor: var(--color-border);
|
|
||||||
|
|
||||||
min-width: 1.25rem;
|
|
||||||
height: 1.25rem;
|
|
||||||
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
border: var(--border-width) solid var(--checkbox-borderColor);
|
|
||||||
background-color: var(--checkbox-backgroundColor);
|
background-color: var(--checkbox-backgroundColor);
|
||||||
|
|
||||||
&[data-state='checked'] {
|
&::after {
|
||||||
--checkbox-backgroundColor: var(--color-accent);
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
top: 0.25em;
|
||||||
|
left: 0.4375em;
|
||||||
|
width: 0.3125em;
|
||||||
|
height: 0.5em;
|
||||||
|
border: solid var(--checkbox-checkColor);
|
||||||
|
border-width: 0 0.125em 0.125em 0;
|
||||||
|
border-radius: 0.0625em;
|
||||||
|
opacity: 0;
|
||||||
|
transform: rotate(0deg) scale(0);
|
||||||
|
transition: opacity 0.2s ease-in-out, transform 0.2s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-disabled] {
|
@media ${breakpoints.tablet} {
|
||||||
cursor: not-allowed;
|
width: 1.5em;
|
||||||
--checkbox-backgroundColor: var(--color-layer-1);
|
height: 1.5em;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
top: 0.25em;
|
||||||
|
left: 0.4375em;
|
||||||
|
width: 0.4375em;
|
||||||
|
height: 0.625em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.Indicator = styled(Indicator)`
|
Styled.CheckboxWrapper = styled.div`
|
||||||
|
--checkbox-backgroundColor: var(--color-layer-1);
|
||||||
|
--checkbox-checkColor: var(--color-text-1);
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
border-radius: 0.25em;
|
||||||
justify-content: center;
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
color: var(--color-text-2);
|
> input:checked ~ ${Styled.CustomCheckbox}::after {
|
||||||
|
opacity: 1;
|
||||||
|
transform: rotate(40deg) scale(1);
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.label = styled.div<{ disabled: boolean; }>`
|
Styled.Checkbox = styled.input`
|
||||||
color: var(--color-text-2);
|
width: 1.25em;
|
||||||
|
height: 1.25em;
|
||||||
|
z-index: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
${({disabled}) => disabled && css`
|
@media ${breakpoints.tablet} {
|
||||||
color: var(--color-text-0);
|
width: 1.5em;
|
||||||
`}
|
height: 1.5em;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
import type { Story } from '@ladle/react';
|
import type { Story } from '@ladle/react';
|
||||||
|
|
||||||
import { Collapsible, type CollapsibleProps } from '@/components/Collapsible';
|
import { Collapsible } from '@/components/Collapsible';
|
||||||
|
|
||||||
import { StoryWrapper } from '.ladle/components';
|
import { StoryWrapper } from '.ladle/components';
|
||||||
import { IconName } from './Icon';
|
import { IconName } from './Icon';
|
||||||
|
|
||||||
export const CollapsibleStory: Story<CollapsibleProps> = (args) => (
|
export const CollapsibleStory: Story<Parameters<typeof Collapsible>> = (args) => (
|
||||||
<StoryWrapper>
|
<StoryWrapper>
|
||||||
<Collapsible {...args}>
|
<Collapsible
|
||||||
|
label="Collapsible List of Items"
|
||||||
|
{...args}
|
||||||
|
>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Collapsible Item 1</li>
|
<li>Collapsible Item 1</li>
|
||||||
<li>Collapsible Item 2</li>
|
<li>Collapsible Item 2</li>
|
||||||
@@ -20,8 +23,6 @@ export const CollapsibleStory: Story<CollapsibleProps> = (args) => (
|
|||||||
|
|
||||||
CollapsibleStory.args = {
|
CollapsibleStory.args = {
|
||||||
disabled: false,
|
disabled: false,
|
||||||
withTrigger: true,
|
|
||||||
label: 'Collapsible List of Items',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
CollapsibleStory.argTypes = {
|
CollapsibleStory.argTypes = {
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ type ElementProps = {
|
|||||||
label: React.ReactNode;
|
label: React.ReactNode;
|
||||||
triggerIcon?: IconName;
|
triggerIcon?: IconName;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
withTrigger?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type StyleProps = {
|
type StyleProps = {
|
||||||
@@ -24,8 +23,6 @@ type StyleProps = {
|
|||||||
fullWidth?: boolean;
|
fullWidth?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CollapsibleProps = ElementProps & StyleProps;
|
|
||||||
|
|
||||||
export const Collapsible = ({
|
export const Collapsible = ({
|
||||||
defaultOpen = false,
|
defaultOpen = false,
|
||||||
disabled,
|
disabled,
|
||||||
@@ -38,29 +35,26 @@ export const Collapsible = ({
|
|||||||
triggerIconSide = 'left',
|
triggerIconSide = 'left',
|
||||||
fullWidth,
|
fullWidth,
|
||||||
className,
|
className,
|
||||||
withTrigger = true,
|
}: ElementProps & StyleProps) => (
|
||||||
}: CollapsibleProps) => (
|
|
||||||
<Styled.Root defaultOpen={defaultOpen} open={open} onOpenChange={onOpenChange}>
|
<Styled.Root defaultOpen={defaultOpen} open={open} onOpenChange={onOpenChange}>
|
||||||
{withTrigger && (
|
<Styled.Trigger className={className} disabled={disabled}>
|
||||||
<Styled.Trigger className={className} disabled={disabled}>
|
{triggerIconSide === 'right' && (
|
||||||
{triggerIconSide === 'right' && (
|
<>
|
||||||
<>
|
{label}
|
||||||
{label}
|
{fullWidth && <HorizontalSeparatorFiller />}
|
||||||
{fullWidth && <HorizontalSeparatorFiller />}
|
</>
|
||||||
</>
|
)}
|
||||||
)}
|
|
||||||
|
|
||||||
<Styled.TriggerIcon>
|
<Styled.TriggerIcon>
|
||||||
<Icon iconName={triggerIcon} />
|
<Icon iconName={triggerIcon} />
|
||||||
</Styled.TriggerIcon>
|
</Styled.TriggerIcon>
|
||||||
{triggerIconSide === 'left' && (
|
{triggerIconSide === 'left' && (
|
||||||
<>
|
<>
|
||||||
{fullWidth && <HorizontalSeparatorFiller />}
|
{fullWidth && <HorizontalSeparatorFiller />}
|
||||||
{label}
|
{label}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Styled.Trigger>
|
</Styled.Trigger>
|
||||||
)}
|
|
||||||
<Styled.Content $transitionDuration={transitionDuration}>{children}</Styled.Content>
|
<Styled.Content $transitionDuration={transitionDuration}>{children}</Styled.Content>
|
||||||
</Styled.Root>
|
</Styled.Root>
|
||||||
);
|
);
|
||||||
@@ -86,7 +80,7 @@ Styled.TriggerIcon = styled.span`
|
|||||||
width: var(--trigger-icon-width);
|
width: var(--trigger-icon-width);
|
||||||
|
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
transition: rotate 0.3s var(--ease-out-expo);
|
transition: transform 0.3s var(--ease-out-expo);
|
||||||
color: var(--trigger-icon-color);
|
color: var(--trigger-icon-color);
|
||||||
|
|
||||||
${Styled.Trigger}[data-state='open'] & {
|
${Styled.Trigger}[data-state='open'] & {
|
||||||
|
|||||||
@@ -31,12 +31,10 @@ type ElementProps<TabItemsValue> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type StyleProps = {
|
type StyleProps = {
|
||||||
className?: string;
|
|
||||||
fullWidthTabs?: boolean;
|
fullWidthTabs?: boolean;
|
||||||
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CollapsibleTabsProps<TabItemsValue> = ElementProps<TabItemsValue> & StyleProps;
|
|
||||||
|
|
||||||
export const CollapsibleTabs = <TabItemsValue extends string>({
|
export const CollapsibleTabs = <TabItemsValue extends string>({
|
||||||
defaultValue,
|
defaultValue,
|
||||||
items,
|
items,
|
||||||
@@ -45,9 +43,8 @@ export const CollapsibleTabs = <TabItemsValue extends string>({
|
|||||||
onOpenChange,
|
onOpenChange,
|
||||||
|
|
||||||
fullWidthTabs,
|
fullWidthTabs,
|
||||||
|
|
||||||
className,
|
className,
|
||||||
}: CollapsibleTabsProps<TabItemsValue>) => {
|
}: ElementProps<TabItemsValue> & StyleProps) => {
|
||||||
const [value, setValue] = useState(defaultValue);
|
const [value, setValue] = useState(defaultValue);
|
||||||
|
|
||||||
const currentItem = items.find((item) => item.value === value);
|
const currentItem = items.find((item) => item.value === value);
|
||||||
@@ -89,8 +86,8 @@ export const CollapsibleTabs = <TabItemsValue extends string>({
|
|||||||
</Styled.Header>
|
</Styled.Header>
|
||||||
|
|
||||||
<Styled.CollapsibleContent>
|
<Styled.CollapsibleContent>
|
||||||
{items.map(({ asChild, value, content }) => (
|
{items.map(({ value, content }) => (
|
||||||
<Styled.TabsContent key={value} asChild={asChild} value={value}>
|
<Styled.TabsContent key={value} value={value}>
|
||||||
{content}
|
{content}
|
||||||
</Styled.TabsContent>
|
</Styled.TabsContent>
|
||||||
))}
|
))}
|
||||||
@@ -222,12 +219,12 @@ Styled.Header = styled.header`
|
|||||||
|
|
||||||
Styled.CollapsibleContent = styled(CollapsibleContent)`
|
Styled.CollapsibleContent = styled(CollapsibleContent)`
|
||||||
${layoutMixins.stack}
|
${layoutMixins.stack}
|
||||||
|
${layoutMixins.perspectiveArea}
|
||||||
|
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.IconButton = styled(IconButton)`
|
Styled.IconButton = styled(IconButton)`
|
||||||
--button-icon-size: 1em;
|
|
||||||
${Styled.CollapsibleRoot}[data-state='closed'] & {
|
${Styled.CollapsibleRoot}[data-state='closed'] & {
|
||||||
rotate: -0.5turn;
|
rotate: -0.5turn;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,12 +22,7 @@ type PickComboxMenuProps<
|
|||||||
MenuGroupValue extends string | number
|
MenuGroupValue extends string | number
|
||||||
> = Pick<
|
> = Pick<
|
||||||
ComboboxMenuProps<MenuItemValue, MenuGroupValue>,
|
ComboboxMenuProps<MenuItemValue, MenuGroupValue>,
|
||||||
| 'inputPlaceholder'
|
'inputPlaceholder' | 'onItemSelected' | 'slotEmpty' | 'withSearch' | 'withStickyLayout'
|
||||||
| 'onItemSelected'
|
|
||||||
| 'slotEmpty'
|
|
||||||
| 'withItemBorders'
|
|
||||||
| 'withSearch'
|
|
||||||
| 'withStickyLayout'
|
|
||||||
>;
|
>;
|
||||||
|
|
||||||
type PickDialogProps = Pick<
|
type PickDialogProps = Pick<
|
||||||
@@ -39,7 +34,6 @@ type PickDialogProps = Pick<
|
|||||||
| 'slotHeaderInner'
|
| 'slotHeaderInner'
|
||||||
| 'slotTrigger'
|
| 'slotTrigger'
|
||||||
| 'slotFooter'
|
| 'slotFooter'
|
||||||
| 'preventClose'
|
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export const ComboboxDialogMenu = <
|
export const ComboboxDialogMenu = <
|
||||||
@@ -58,13 +52,11 @@ export const ComboboxDialogMenu = <
|
|||||||
onItemSelected,
|
onItemSelected,
|
||||||
inputPlaceholder,
|
inputPlaceholder,
|
||||||
slotEmpty,
|
slotEmpty,
|
||||||
withItemBorders,
|
|
||||||
withSearch,
|
withSearch,
|
||||||
withStickyLayout = true,
|
withStickyLayout = true,
|
||||||
children,
|
children,
|
||||||
|
|
||||||
placement = DialogPlacement.Default,
|
placement = DialogPlacement.Default,
|
||||||
preventClose,
|
|
||||||
className,
|
className,
|
||||||
}: ElementProps<MenuItemValue, MenuGroupValue> &
|
}: ElementProps<MenuItemValue, MenuGroupValue> &
|
||||||
PickComboxMenuProps<MenuItemValue, MenuGroupValue> &
|
PickComboxMenuProps<MenuItemValue, MenuGroupValue> &
|
||||||
@@ -80,7 +72,6 @@ export const ComboboxDialogMenu = <
|
|||||||
slotTrigger={slotTrigger}
|
slotTrigger={slotTrigger}
|
||||||
slotFooter={slotFooter}
|
slotFooter={slotFooter}
|
||||||
placement={placement}
|
placement={placement}
|
||||||
preventClose={preventClose}
|
|
||||||
className={className}
|
className={className}
|
||||||
>
|
>
|
||||||
<Styled.ComboboxMenu
|
<Styled.ComboboxMenu
|
||||||
@@ -89,7 +80,6 @@ export const ComboboxDialogMenu = <
|
|||||||
title={title}
|
title={title}
|
||||||
inputPlaceholder={inputPlaceholder}
|
inputPlaceholder={inputPlaceholder}
|
||||||
slotEmpty={slotEmpty}
|
slotEmpty={slotEmpty}
|
||||||
withItemBorders={withItemBorders}
|
|
||||||
withSearch={withSearch}
|
withSearch={withSearch}
|
||||||
withStickyLayout={withStickyLayout}
|
withStickyLayout={withStickyLayout}
|
||||||
/>
|
/>
|
||||||
@@ -102,8 +92,6 @@ const Styled: Record<string, AnyStyledComponent> = {};
|
|||||||
Styled.Dialog = styled(Dialog)`
|
Styled.Dialog = styled(Dialog)`
|
||||||
/* Params */
|
/* Params */
|
||||||
--comboboxDialogMenu-backgroundColor: var(--color-layer-2);
|
--comboboxDialogMenu-backgroundColor: var(--color-layer-2);
|
||||||
--comboboxDialogMenu-item-gap: 0.5rem;
|
|
||||||
--comboboxDialogMenu-item-padding: 0.5rem 1rem;
|
|
||||||
|
|
||||||
/* Overrides */
|
/* Overrides */
|
||||||
& {
|
& {
|
||||||
@@ -122,12 +110,10 @@ Styled.Dialog = styled(Dialog)`
|
|||||||
|
|
||||||
/* Net 0 sticky top inset (let stickyArea1 header stick to top) */
|
/* Net 0 sticky top inset (let stickyArea1 header stick to top) */
|
||||||
--stickyArea0-topGap: calc(-1 * var(--stickyArea0-topHeight));
|
--stickyArea0-topGap: calc(-1 * var(--stickyArea0-topHeight));
|
||||||
overflow-x: clip;
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.ComboboxMenu = styled(ComboboxMenu)`
|
Styled.ComboboxMenu = styled(ComboboxMenu)`
|
||||||
--comboboxMenu-backgroundColor: var(--comboboxDialogMenu-backgroundColor);
|
--comboboxMenu-backgroundColor: var(--comboboxDialogMenu-backgroundColor);
|
||||||
--comboboxMenu-item-gap: var(--comboboxDialogMenu-item-gap);
|
--comboboxMenu-item-gap: var(--comboxDialogMenu-item-gap, 0.5rem);
|
||||||
--comboboxMenu-item-padding: var(--comboboxDialogMenu-item-padding);
|
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Fragment, type ReactNode, useState } from 'react';
|
|||||||
import styled, { type AnyStyledComponent, css } from 'styled-components';
|
import styled, { type AnyStyledComponent, css } from 'styled-components';
|
||||||
import { Command } from 'cmdk';
|
import { Command } from 'cmdk';
|
||||||
|
|
||||||
import { MenuItem, type MenuConfig } from '@/constants/menus';
|
import { type MenuConfig } from '@/constants/menus';
|
||||||
import { popoverMixins } from '@/styles/popoverMixins';
|
import { popoverMixins } from '@/styles/popoverMixins';
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
|
||||||
@@ -20,7 +20,6 @@ type ElementProps<MenuItemValue extends string | number, MenuGroupValue extends
|
|||||||
|
|
||||||
type StyleProps = {
|
type StyleProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
withItemBorders?: boolean;
|
|
||||||
withStickyLayout?: boolean;
|
withStickyLayout?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -39,11 +38,17 @@ export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extend
|
|||||||
withSearch = true,
|
withSearch = true,
|
||||||
|
|
||||||
className,
|
className,
|
||||||
withItemBorders,
|
|
||||||
withStickyLayout,
|
withStickyLayout,
|
||||||
}: ComboboxMenuProps<MenuItemValue, MenuGroupValue>) => {
|
}: ComboboxMenuProps<MenuItemValue, MenuGroupValue>) => {
|
||||||
const [highlightedCommand, setHighlightedCommand] = useState<MenuItemValue>();
|
const [highlightedCommand, setHighlightedCommand] = useState<MenuItemValue>();
|
||||||
const [searchValue, setSearchValue] = useState('');
|
const [searchValue, setSearchValue] = useState('');
|
||||||
|
// const inputRef = useRef<HTMLInputElement | null>(null);
|
||||||
|
|
||||||
|
// console.log({ commandValue: highlightedCommand });
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// inputRef?.current?.focus();
|
||||||
|
// }, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Styled.Command
|
<Styled.Command
|
||||||
@@ -60,14 +65,11 @@ export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extend
|
|||||||
{withSearch && (
|
{withSearch && (
|
||||||
<Styled.Header $withStickyLayout={withStickyLayout}>
|
<Styled.Header $withStickyLayout={withStickyLayout}>
|
||||||
<Styled.Input
|
<Styled.Input
|
||||||
/**
|
// ref={inputRef}
|
||||||
* Mobile Issue: Search Input will always trigger mobile keyboard drawer. There is no fix.
|
|
||||||
* https://github.com/pacocoursey/cmdk/issues/127
|
|
||||||
*/
|
|
||||||
autoFocus
|
|
||||||
type="search"
|
type="search"
|
||||||
value={searchValue}
|
value={searchValue}
|
||||||
onValueChange={setSearchValue}
|
onValueChange={setSearchValue}
|
||||||
|
autoFocus
|
||||||
placeholder={inputPlaceholder}
|
placeholder={inputPlaceholder}
|
||||||
/>
|
/>
|
||||||
</Styled.Header>
|
</Styled.Header>
|
||||||
@@ -78,7 +80,6 @@ export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extend
|
|||||||
<Styled.Group
|
<Styled.Group
|
||||||
key={group.group}
|
key={group.group}
|
||||||
heading={group.groupLabel}
|
heading={group.groupLabel}
|
||||||
$withItemBorders={withItemBorders}
|
|
||||||
$withStickyLayout={withStickyLayout}
|
$withStickyLayout={withStickyLayout}
|
||||||
>
|
>
|
||||||
{group.items.map((item) => (
|
{group.items.map((item) => (
|
||||||
@@ -98,31 +99,22 @@ export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extend
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={item.disabled}
|
disabled={item.disabled}
|
||||||
$withItemBorders={withItemBorders}
|
|
||||||
>
|
>
|
||||||
{
|
{item.slotBefore}
|
||||||
<>
|
<Styled.ItemLabel>
|
||||||
{item.slotBefore}
|
<span>
|
||||||
{item.slotCustomContent ?? (
|
{`${item.label}${item.subitems?.length ? '…' : ''}`}
|
||||||
<Styled.ItemLabel>
|
{item.tag && (
|
||||||
<span>
|
<>
|
||||||
{typeof item.label === 'string'
|
{' '}
|
||||||
? `${item.label}${item.subitems?.length ? '…' : ''}`
|
<Tag>{item.tag}</Tag>
|
||||||
: item.label}
|
</>
|
||||||
{item.tag && (
|
|
||||||
<>
|
|
||||||
{' '}
|
|
||||||
<Tag>{item.tag}</Tag>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
{item.description && <span>{item.description}</span>}
|
|
||||||
</Styled.ItemLabel>
|
|
||||||
)}
|
)}
|
||||||
{item.slotAfter}
|
</span>
|
||||||
{item.subitems && '→'}
|
{item.description && <span>{item.description}</span>}
|
||||||
</>
|
</Styled.ItemLabel>
|
||||||
}
|
{item.slotAfter}
|
||||||
|
{item.subitems && '→'}
|
||||||
</Styled.Item>
|
</Styled.Item>
|
||||||
|
|
||||||
{searchValue &&
|
{searchValue &&
|
||||||
@@ -145,7 +137,6 @@ export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extend
|
|||||||
onItemSelected?.();
|
onItemSelected?.();
|
||||||
}}
|
}}
|
||||||
disabled={subitem.disabled}
|
disabled={subitem.disabled}
|
||||||
$withItemBorders={withItemBorders}
|
|
||||||
>
|
>
|
||||||
{subitem.slotBefore}
|
{subitem.slotBefore}
|
||||||
<Styled.ItemLabel>
|
<Styled.ItemLabel>
|
||||||
@@ -178,17 +169,14 @@ const Styled: Record<string, AnyStyledComponent> = {};
|
|||||||
|
|
||||||
Styled.Command = styled(Command)<{ $withStickyLayout?: boolean }>`
|
Styled.Command = styled(Command)<{ $withStickyLayout?: boolean }>`
|
||||||
--comboboxMenu-backgroundColor: var(--color-layer-2);
|
--comboboxMenu-backgroundColor: var(--color-layer-2);
|
||||||
|
|
||||||
--comboboxMenu-input-backgroundColor: var(--color-layer-3);
|
--comboboxMenu-input-backgroundColor: var(--color-layer-3);
|
||||||
--comboboxMenu-input-height: 2.5rem;
|
--comboboxMenu-input-height: 2.5rem;
|
||||||
|
|
||||||
--comboboxMenu-item-checked-backgroundColor: ;
|
--comboboxMenu-item-checked-backgroundColor: ;
|
||||||
--comboboxMenu-item-checked-textColor: ;
|
--comboboxMenu-item-checked-textColor: ;
|
||||||
--comboboxMenu-item-highlighted-backgroundColor: var(--color-layer-3);
|
--comboboxMenu-item-highlighted-backgroundColor: var(--color-layer-3);
|
||||||
--comboboxMenu-item-highlighted-textColor: var(--color-text-1);
|
--comboboxMenu-item-highlighted-textColor: var(--color-text-1);
|
||||||
--comboboxMenu-item-backgroundColor: ;
|
--comboboxMenu-item-backgroundColor: ;
|
||||||
--comboboxMenu-item-gap: 0.5rem;
|
--comboboxMenu-item-gap: 0.5rem;
|
||||||
--comboboxMenu-item-padding: 0.5em 1em;
|
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
align-content: start;
|
align-content: start;
|
||||||
@@ -237,7 +225,7 @@ Styled.Input = styled(Command.Input)`
|
|||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.Group = styled(Command.Group)<{ $withItemBorders?: boolean; $withStickyLayout?: boolean }>`
|
Styled.Group = styled(Command.Group)<{ $withStickyLayout?: boolean }>`
|
||||||
color: var(--color-text-0);
|
color: var(--color-text-0);
|
||||||
|
|
||||||
> [cmdk-group-heading] {
|
> [cmdk-group-heading] {
|
||||||
@@ -254,21 +242,12 @@ Styled.Group = styled(Command.Group)<{ $withItemBorders?: boolean; $withStickyLa
|
|||||||
|
|
||||||
> [cmdk-group-heading] {
|
> [cmdk-group-heading] {
|
||||||
${layoutMixins.stickyHeader}
|
${layoutMixins.stickyHeader}
|
||||||
z-index: 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
> [cmdk-group-items] {
|
> [cmdk-group-items] {
|
||||||
${layoutMixins.stickyArea3}
|
${layoutMixins.stickyArea3}
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
|
|
||||||
${({ $withItemBorders }) =>
|
|
||||||
$withItemBorders &&
|
|
||||||
css`
|
|
||||||
> [cmdk-group-items] {
|
|
||||||
padding: var(--border-width) 0;
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.List = styled(Command.List)<{ $withStickyLayout?: boolean }>`
|
Styled.List = styled(Command.List)<{ $withStickyLayout?: boolean }>`
|
||||||
@@ -295,14 +274,13 @@ Styled.List = styled(Command.List)<{ $withStickyLayout?: boolean }>`
|
|||||||
`}
|
`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.Item = styled(Command.Item)<{ $withItemBorders?: boolean }>`
|
Styled.Item = styled(Command.Item)`
|
||||||
${layoutMixins.scrollSnapItem}
|
${layoutMixins.scrollSnapItem}
|
||||||
${popoverMixins.item}
|
${popoverMixins.item}
|
||||||
--item-checked-backgroundColor: var(--comboboxMenu-item-checked-backgroundColor);
|
--item-checked-backgroundColor: var(--comboboxMenu-item-checked-backgroundColor);
|
||||||
--item-checked-textColor: var(--comboboxMenu-item-checked-textColor);
|
--item-checked-textColor: var(--comboboxMenu-item-checked-textColor);
|
||||||
--item-highlighted-textColor: var(--comboboxMenu-item-highlighted-textColor);
|
--item-highlighted-textColor: var(--comboboxMenu-item-highlighted-textColor);
|
||||||
--item-gap: var(--comboboxMenu-item-gap);
|
--item-gap: var(--comboboxMenu-item-gap);
|
||||||
--item-padding: var(--comboboxMenu-item-padding);
|
|
||||||
|
|
||||||
background-color: var(--comboboxMenu-backgroundColor, inherit);
|
background-color: var(--comboboxMenu-backgroundColor, inherit);
|
||||||
|
|
||||||
@@ -313,12 +291,6 @@ Styled.Item = styled(Command.Item)<{ $withItemBorders?: boolean }>`
|
|||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
${({ $withItemBorders }) =>
|
|
||||||
$withItemBorders &&
|
|
||||||
css`
|
|
||||||
${layoutMixins.withOuterBorder}
|
|
||||||
`}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.ItemLabel = styled.div`
|
Styled.ItemLabel = styled.div`
|
||||||
@@ -337,8 +309,6 @@ Styled.ItemLabel = styled.div`
|
|||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
min-width: 0;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.Empty = styled(Command.Empty)`
|
Styled.Empty = styled(Command.Empty)`
|
||||||
|
|||||||
@@ -11,5 +11,6 @@ export const DetachedScrollableSection = styled.section`
|
|||||||
|
|
||||||
export const AttachedExpandingSection = styled.section`
|
export const AttachedExpandingSection = styled.section`
|
||||||
${layoutMixins.contentSectionAttached}
|
${layoutMixins.contentSectionAttached}
|
||||||
|
${layoutMixins.expandingColumnWithHeader}
|
||||||
gap: var(--border-width);
|
gap: var(--border-width);
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ CopyButtonStory.args = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
CopyButtonStory.argTypes = {
|
CopyButtonStory.argTypes = {
|
||||||
buttonType: {
|
shownAsText: {
|
||||||
options: ["text", "icon", "default"],
|
options: [true, false],
|
||||||
control: { type: 'select' },
|
control: { type: 'select' },
|
||||||
defaultValue: "default",
|
defaultValue: false,
|
||||||
},
|
},
|
||||||
children: {
|
children: {
|
||||||
options: ['some text to copy'],
|
options: ['some text to copy'],
|
||||||
|
|||||||
@@ -9,21 +9,14 @@ import { layoutMixins } from '@/styles/layoutMixins';
|
|||||||
|
|
||||||
import { Button, ButtonProps } from './Button';
|
import { Button, ButtonProps } from './Button';
|
||||||
import { Icon, IconName } from './Icon';
|
import { Icon, IconName } from './Icon';
|
||||||
import { IconButton } from './IconButton';
|
|
||||||
import { WithTooltip } from './WithTooltip';
|
|
||||||
|
|
||||||
export type CopyButtonProps = {
|
export type CopyButtonProps = {
|
||||||
value?: string;
|
value?: string;
|
||||||
buttonType?: 'text' | 'icon' | 'default';
|
shownAsText?: boolean;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
} & ButtonProps;
|
} & ButtonProps;
|
||||||
|
|
||||||
export const CopyButton = ({
|
export const CopyButton = ({ value, shownAsText, children, ...buttonProps }: CopyButtonProps) => {
|
||||||
value,
|
|
||||||
buttonType = 'default',
|
|
||||||
children,
|
|
||||||
...buttonProps
|
|
||||||
}: CopyButtonProps) => {
|
|
||||||
const stringGetter = useStringGetter();
|
const stringGetter = useStringGetter();
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
@@ -35,23 +28,11 @@ export const CopyButton = ({
|
|||||||
setTimeout(() => setCopied(false), 500);
|
setTimeout(() => setCopied(false), 500);
|
||||||
};
|
};
|
||||||
|
|
||||||
return buttonType === 'text' ? (
|
return shownAsText ? (
|
||||||
<Styled.InlineRow onClick={onCopy} copied={copied}>
|
<Styled.InlineRow onClick={onCopy} copied={copied}>
|
||||||
{children}
|
{children}
|
||||||
<Styled.Icon copied={copied} iconName={copied ? IconName.Check : IconName.Copy} />
|
<Icon iconName={IconName.Copy} />
|
||||||
</Styled.InlineRow>
|
</Styled.InlineRow>
|
||||||
) : buttonType === 'icon' ? (
|
|
||||||
<WithTooltip
|
|
||||||
tooltipString={stringGetter({ key: copied ? STRING_KEYS.COPIED : STRING_KEYS.COPY })}
|
|
||||||
>
|
|
||||||
<Styled.IconButton
|
|
||||||
{...buttonProps}
|
|
||||||
copied={copied}
|
|
||||||
action={ButtonAction.Base}
|
|
||||||
iconName={copied ? IconName.Check : IconName.Copy}
|
|
||||||
onClick={onCopy}
|
|
||||||
/>
|
|
||||||
</WithTooltip>
|
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
{...buttonProps}
|
{...buttonProps}
|
||||||
@@ -82,21 +63,3 @@ Styled.InlineRow = styled.div<{ copied: boolean }>`
|
|||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.Icon = styled(Icon)<{ copied: boolean }>`
|
|
||||||
${({ copied }) =>
|
|
||||||
copied &&
|
|
||||||
css`
|
|
||||||
color: var(--color-positive);
|
|
||||||
`}
|
|
||||||
`;
|
|
||||||
|
|
||||||
Styled.IconButton = styled(IconButton)<{ copied: boolean }>`
|
|
||||||
${({ copied }) =>
|
|
||||||
copied &&
|
|
||||||
css`
|
|
||||||
svg {
|
|
||||||
color: var(--color-positive);
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
`;
|
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ type ElementProps = {
|
|||||||
slotTrigger?: React.ReactNode;
|
slotTrigger?: React.ReactNode;
|
||||||
slotHeaderInner?: React.ReactNode;
|
slotHeaderInner?: React.ReactNode;
|
||||||
slotFooter?: React.ReactNode;
|
slotFooter?: React.ReactNode;
|
||||||
withClose?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type StyleProps = {
|
type StyleProps = {
|
||||||
@@ -47,7 +46,6 @@ type StyleProps = {
|
|||||||
hasHeaderBorder?: boolean;
|
hasHeaderBorder?: boolean;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
withAnimation?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DialogProps = ElementProps & StyleProps;
|
export type DialogProps = ElementProps & StyleProps;
|
||||||
@@ -81,11 +79,9 @@ export const Dialog = ({
|
|||||||
slotTrigger,
|
slotTrigger,
|
||||||
slotHeaderInner,
|
slotHeaderInner,
|
||||||
slotFooter,
|
slotFooter,
|
||||||
withClose = true,
|
|
||||||
placement = DialogPlacement.Default,
|
placement = DialogPlacement.Default,
|
||||||
portalContainer,
|
portalContainer,
|
||||||
hasHeaderBorder = false,
|
hasHeaderBorder = false,
|
||||||
withAnimation = false,
|
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
}: DialogProps) => {
|
}: DialogProps) => {
|
||||||
@@ -109,7 +105,6 @@ export const Dialog = ({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
$withAnimation={withAnimation}
|
|
||||||
>
|
>
|
||||||
<Styled.Header $withBorder={hasHeaderBorder}>
|
<Styled.Header $withBorder={hasHeaderBorder}>
|
||||||
<Styled.HeaderTopRow>
|
<Styled.HeaderTopRow>
|
||||||
@@ -119,7 +114,7 @@ export const Dialog = ({
|
|||||||
|
|
||||||
{title && <Styled.Title>{title}</Styled.Title>}
|
{title && <Styled.Title>{title}</Styled.Title>}
|
||||||
|
|
||||||
{!preventClose && withClose && (
|
{!preventClose && (
|
||||||
<Styled.Close ref={closeButtonRef}>
|
<Styled.Close ref={closeButtonRef}>
|
||||||
<Icon iconName={IconName.Close} />
|
<Icon iconName={IconName.Close} />
|
||||||
</Styled.Close>
|
</Styled.Close>
|
||||||
@@ -173,7 +168,7 @@ Styled.Overlay = styled(Overlay)`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.Container = styled(Content)<{ placement: DialogPlacement; $withAnimation?: boolean }>`
|
Styled.Container = styled(Content)<{ placement: DialogPlacement }>`
|
||||||
/* Params */
|
/* Params */
|
||||||
--dialog-inset: 1rem;
|
--dialog-inset: 1rem;
|
||||||
--dialog-width: 30rem;
|
--dialog-width: 30rem;
|
||||||
@@ -229,7 +224,7 @@ Styled.Container = styled(Content)<{ placement: DialogPlacement; $withAnimation?
|
|||||||
|
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
||||||
${({ placement, $withAnimation }) =>
|
${({ placement }) =>
|
||||||
({
|
({
|
||||||
[DialogPlacement.Default]: css`
|
[DialogPlacement.Default]: css`
|
||||||
inset: var(--dialog-inset);
|
inset: var(--dialog-inset);
|
||||||
@@ -265,11 +260,9 @@ Styled.Container = styled(Content)<{ placement: DialogPlacement; $withAnimation?
|
|||||||
padding-bottom: var(--dialog-radius); */
|
padding-bottom: var(--dialog-radius); */
|
||||||
}
|
}
|
||||||
|
|
||||||
${$withAnimation &&
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
css`
|
&[data-state='open'] {
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
animation: ${keyframes`
|
||||||
&[data-state='open'] {
|
|
||||||
animation: ${keyframes`
|
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
@@ -277,19 +270,18 @@ Styled.Container = styled(Content)<{ placement: DialogPlacement; $withAnimation?
|
|||||||
max-height: 0;
|
max-height: 0;
|
||||||
}
|
}
|
||||||
`} 0.15s var(--ease-out-expo);
|
`} 0.15s var(--ease-out-expo);
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-state='closed'] {
|
&[data-state='closed'] {
|
||||||
animation: ${keyframes`
|
animation: ${keyframes`
|
||||||
to {
|
to {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
scale: 0.9;
|
scale: 0.9;
|
||||||
max-height: 0;
|
max-height: 0;
|
||||||
}
|
}
|
||||||
`} 0.15s;
|
`} 0.15s;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
`}
|
}
|
||||||
`,
|
`,
|
||||||
[DialogPlacement.Sidebar]: css`
|
[DialogPlacement.Sidebar]: css`
|
||||||
--dialog-width: var(--sidebar-width);
|
--dialog-width: var(--sidebar-width);
|
||||||
@@ -299,52 +291,50 @@ Styled.Container = styled(Content)<{ placement: DialogPlacement; $withAnimation?
|
|||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
${$withAnimation &&
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
css`
|
&[data-state='open'] {
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
animation: ${keyframes`
|
||||||
&[data-state='open'] {
|
|
||||||
animation: ${keyframes`
|
|
||||||
from {
|
from {
|
||||||
translate: 100% 0;
|
translate: 100% 0;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
`} 0.15s var(--ease-out-expo);
|
`} 0.15s var(--ease-out-expo);
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-state='closed'] {
|
&[data-state='closed'] {
|
||||||
animation: ${keyframes`
|
animation: ${keyframes`
|
||||||
to {
|
to {
|
||||||
translate: 100% 0;
|
translate: 100% 0;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
`} 0.15s var(--ease-out-expo);
|
`} 0.15s var(--ease-out-expo);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
`}
|
}
|
||||||
`,
|
`,
|
||||||
[DialogPlacement.Inline]: css`
|
[DialogPlacement.Inline]: css`
|
||||||
${$withAnimation &&
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
css`
|
&[data-state='open'] {
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
animation: ${keyframes`
|
||||||
&[data-state='open'] {
|
|
||||||
animation: ${keyframes`
|
|
||||||
from {
|
from {
|
||||||
scale: 0.99;
|
scale: 0.99;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
/* filter: blur(2px); */
|
||||||
|
/* backdrop-filter: none; */
|
||||||
}
|
}
|
||||||
`} 0.15s var(--ease-out-expo);
|
`} 0.15s var(--ease-out-expo);
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-state='closed'] {
|
&[data-state='closed'] {
|
||||||
animation: ${keyframes`
|
animation: ${keyframes`
|
||||||
to {
|
to {
|
||||||
scale: 0.99;
|
scale: 0.99;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
/* filter: blur(2px); */
|
||||||
|
/* backdrop-filter: none; */
|
||||||
}
|
}
|
||||||
`} 0.15s var(--ease-out-expo);
|
`} 0.15s var(--ease-out-expo);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
`}
|
}
|
||||||
`,
|
`,
|
||||||
[DialogPlacement.FullScreen]: css`
|
[DialogPlacement.FullScreen]: css`
|
||||||
--dialog-width: 100vw;
|
--dialog-width: 100vw;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const DiffArrowStory: Story<DiffArrowProps> = (args) => (
|
|||||||
|
|
||||||
DiffArrowStory.argTypes = {
|
DiffArrowStory.argTypes = {
|
||||||
direction: {
|
direction: {
|
||||||
options: ['left', 'right', 'up', 'down'],
|
options: ['left', 'right'],
|
||||||
control: { type: 'select' },
|
control: { type: 'select' },
|
||||||
defaultValue: 'right',
|
defaultValue: 'right',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ type ElementProps = {
|
|||||||
|
|
||||||
type StyleProps = {
|
type StyleProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
direction?: 'right' | 'left' | 'up' | 'down';
|
direction?: 'right' | 'left';
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DiffArrowProps = ElementProps & StyleProps;
|
export type DiffArrowProps = ElementProps & StyleProps;
|
||||||
@@ -57,11 +57,5 @@ Styled.DiffArrowContainer = styled.span<DiffArrowProps>`
|
|||||||
left: css`
|
left: css`
|
||||||
transform: scaleX(-1);
|
transform: scaleX(-1);
|
||||||
`,
|
`,
|
||||||
up: css`
|
|
||||||
transform: rotate(-90deg);
|
|
||||||
`,
|
|
||||||
down: css`
|
|
||||||
transform: rotate(90deg);
|
|
||||||
`
|
|
||||||
}[direction || 'right'])}
|
}[direction || 'right'])}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -33,11 +33,9 @@ export const DropdownHeaderMenu = <MenuItemValue extends string>({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Root>
|
<Root>
|
||||||
<Styled.Trigger className={className} asChild>
|
<Styled.Trigger className={className}>
|
||||||
<div>
|
|
||||||
{children}
|
{children}
|
||||||
<Styled.DropdownIconButton iconName={IconName.Caret} isToggle />
|
<Styled.DropdownIconButton iconName={IconName.Caret} isToggle />
|
||||||
</div>
|
|
||||||
</Styled.Trigger>
|
</Styled.Trigger>
|
||||||
<Portal>
|
<Portal>
|
||||||
<Styled.Content
|
<Styled.Content
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
RadioItem,
|
RadioItem,
|
||||||
Portal,
|
Portal,
|
||||||
} from '@radix-ui/react-dropdown-menu';
|
} from '@radix-ui/react-dropdown-menu';
|
||||||
import { CheckIcon } from '@radix-ui/react-icons';
|
|
||||||
|
|
||||||
import { type MenuItem } from '@/constants/menus';
|
import { type MenuItem } from '@/constants/menus';
|
||||||
|
|
||||||
@@ -91,9 +90,7 @@ export const DropdownSelectMenu = <MenuItemValue extends string>({
|
|||||||
|
|
||||||
{slotAfter}
|
{slotAfter}
|
||||||
|
|
||||||
<Styled.ItemIndicator>
|
<Styled.ItemIndicator>✔{/* <CheckIcon /> */}</Styled.ItemIndicator>
|
||||||
<CheckIcon />
|
|
||||||
</Styled.ItemIndicator>
|
|
||||||
</Styled.RadioItem>
|
</Styled.RadioItem>
|
||||||
))}
|
))}
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
|||||||
@@ -1,16 +1,43 @@
|
|||||||
import styled, { AnyStyledComponent } from 'styled-components';
|
import styled, { AnyStyledComponent } from 'styled-components';
|
||||||
|
|
||||||
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
|
||||||
import { Icon, IconName } from '@/components/Icon';
|
import { Icon, IconName } from '@/components/Icon';
|
||||||
|
|
||||||
export const GreenCheckCircle = ({ className }: { className?: string }) => (
|
export const GreenCheckCircle = ({ className }: { className?: string }) => (
|
||||||
<Styled.Icon className={className} iconName={IconName.CheckCircle} />
|
<Styled.GreenCheckCircle className={className}>
|
||||||
|
<Icon iconName={IconName.Check} />
|
||||||
|
</Styled.GreenCheckCircle>
|
||||||
);
|
);
|
||||||
|
|
||||||
const Styled: Record<string, AnyStyledComponent> = {};
|
const Styled: Record<string, AnyStyledComponent> = {};
|
||||||
|
|
||||||
Styled.Icon = styled(Icon)`
|
Styled.GreenCheckCircle = styled.div`
|
||||||
|
${layoutMixins.stack}
|
||||||
|
|
||||||
--icon-size: 1.25rem;
|
--icon-size: 1.25rem;
|
||||||
|
--icon-border-width: 3px;
|
||||||
|
|
||||||
width: var(--icon-size);
|
width: var(--icon-size);
|
||||||
height: var(--icon-size);
|
height: var(--icon-size);
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: '';
|
||||||
|
width: calc(var(--icon-size) - var(--icon-border-width) * 2);
|
||||||
|
height: calc(var(--icon-size) - var(--icon-border-width) * 2);
|
||||||
|
border: solid var(--icon-border-width) var(--color-positive);
|
||||||
|
border-radius: 50%;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: calc(var(--icon-size) / 3);
|
||||||
|
height: calc(var(--icon-size) / 3);
|
||||||
|
|
||||||
|
color: var(--color-positive);
|
||||||
|
justify-self: center;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import {
|
|||||||
CautionCircleIcon,
|
CautionCircleIcon,
|
||||||
ChatIcon,
|
ChatIcon,
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
CheckCircleIcon,
|
|
||||||
ChevronLeftIcon,
|
ChevronLeftIcon,
|
||||||
ChevronRightIcon,
|
ChevronRightIcon,
|
||||||
ClockIcon,
|
ClockIcon,
|
||||||
@@ -24,10 +23,10 @@ import {
|
|||||||
CoinsIcon,
|
CoinsIcon,
|
||||||
CommentIcon,
|
CommentIcon,
|
||||||
CopyIcon,
|
CopyIcon,
|
||||||
DepositIcon,
|
CubeIcon,
|
||||||
DepthChartIcon,
|
|
||||||
DiscordIcon,
|
DiscordIcon,
|
||||||
EtherscanIcon,
|
EtherscanIcon,
|
||||||
|
DepthChartIcon,
|
||||||
ExportKeysIcon,
|
ExportKeysIcon,
|
||||||
FeedbackIcon,
|
FeedbackIcon,
|
||||||
FileIcon,
|
FileIcon,
|
||||||
@@ -37,14 +36,11 @@ import {
|
|||||||
HelpCircleIcon,
|
HelpCircleIcon,
|
||||||
HideIcon,
|
HideIcon,
|
||||||
HistoryIcon,
|
HistoryIcon,
|
||||||
LeaderboardIcon,
|
|
||||||
LinkOutIcon,
|
LinkOutIcon,
|
||||||
LockIcon,
|
LockIcon,
|
||||||
LogoShortIcon,
|
LogoShortIcon,
|
||||||
MenuIcon,
|
MenuIcon,
|
||||||
MigrateIcon,
|
|
||||||
MintscanIcon,
|
MintscanIcon,
|
||||||
MoonIcon,
|
|
||||||
OrderbookIcon,
|
OrderbookIcon,
|
||||||
OrderCanceledIcon,
|
OrderCanceledIcon,
|
||||||
OrderFilledIcon,
|
OrderFilledIcon,
|
||||||
@@ -54,20 +50,16 @@ import {
|
|||||||
OrderUntriggeredIcon,
|
OrderUntriggeredIcon,
|
||||||
OverviewIcon,
|
OverviewIcon,
|
||||||
PencilIcon,
|
PencilIcon,
|
||||||
PlayIcon,
|
|
||||||
PlusIcon,
|
|
||||||
PositionsIcon,
|
PositionsIcon,
|
||||||
PriceChartIcon,
|
PriceChartIcon,
|
||||||
PrivacyIcon,
|
PrivacyIcon,
|
||||||
QrIcon,
|
QrIcon,
|
||||||
RewardStarIcon,
|
|
||||||
SearchIcon,
|
SearchIcon,
|
||||||
SendIcon,
|
SendIcon,
|
||||||
ShareIcon,
|
ShareIcon,
|
||||||
ShowIcon,
|
ShowIcon,
|
||||||
TogglesMenuIcon,
|
TogglesMenuIcon,
|
||||||
StarIcon,
|
StarIcon,
|
||||||
SunIcon,
|
|
||||||
TerminalIcon,
|
TerminalIcon,
|
||||||
TradeIcon,
|
TradeIcon,
|
||||||
TransferIcon,
|
TransferIcon,
|
||||||
@@ -76,7 +68,6 @@ import {
|
|||||||
WarningIcon,
|
WarningIcon,
|
||||||
WebsiteIcon,
|
WebsiteIcon,
|
||||||
WhitepaperIcon,
|
WhitepaperIcon,
|
||||||
WithdrawIcon,
|
|
||||||
} from '@/icons';
|
} from '@/icons';
|
||||||
|
|
||||||
export enum IconName {
|
export enum IconName {
|
||||||
@@ -92,7 +83,6 @@ export enum IconName {
|
|||||||
CautionCircleStroked = 'CautionCircleStroked',
|
CautionCircleStroked = 'CautionCircleStroked',
|
||||||
Chat = 'Chat',
|
Chat = 'Chat',
|
||||||
Check = 'Check',
|
Check = 'Check',
|
||||||
CheckCircle = 'CheckCircle',
|
|
||||||
ChevronLeft = 'ChevronLeft',
|
ChevronLeft = 'ChevronLeft',
|
||||||
ChevronRight = 'ChevronRight',
|
ChevronRight = 'ChevronRight',
|
||||||
Clock = 'Clock',
|
Clock = 'Clock',
|
||||||
@@ -101,7 +91,7 @@ export enum IconName {
|
|||||||
Coins = 'Coins',
|
Coins = 'Coins',
|
||||||
Comment = 'Comment',
|
Comment = 'Comment',
|
||||||
Copy = 'Copy',
|
Copy = 'Copy',
|
||||||
Deposit = 'Deposit',
|
Cube = 'Cube',
|
||||||
DepthChart = 'DepthChart',
|
DepthChart = 'DepthChart',
|
||||||
Discord = 'Discord',
|
Discord = 'Discord',
|
||||||
Etherscan = 'Etherscan',
|
Etherscan = 'Etherscan',
|
||||||
@@ -114,14 +104,11 @@ export enum IconName {
|
|||||||
HelpCircle = 'HelpCircle',
|
HelpCircle = 'HelpCircle',
|
||||||
Hide = 'Hide',
|
Hide = 'Hide',
|
||||||
History = 'History',
|
History = 'History',
|
||||||
Leaderboard = 'Leaderboard',
|
|
||||||
LinkOut = 'LinkOut',
|
LinkOut = 'LinkOut',
|
||||||
Lock = 'Lock',
|
Lock = 'Lock',
|
||||||
LogoShort = 'LogoShort',
|
LogoShort = 'LogoShort',
|
||||||
Menu = 'Menu',
|
Menu = 'Menu',
|
||||||
Migrate = 'Migrate',
|
|
||||||
Mintscan = 'Mintscan',
|
Mintscan = 'Mintscan',
|
||||||
Moon = 'Moon',
|
|
||||||
Onboarding = 'Onboarding',
|
Onboarding = 'Onboarding',
|
||||||
Orderbook = 'OrderbookIcon',
|
Orderbook = 'OrderbookIcon',
|
||||||
OrderCanceled = 'OrderCanceled',
|
OrderCanceled = 'OrderCanceled',
|
||||||
@@ -132,19 +119,15 @@ export enum IconName {
|
|||||||
OrderUntriggered = 'OrderUntriggered',
|
OrderUntriggered = 'OrderUntriggered',
|
||||||
Overview = 'Overview',
|
Overview = 'Overview',
|
||||||
Pencil = 'Pencil',
|
Pencil = 'Pencil',
|
||||||
Play = 'Play',
|
|
||||||
Plus = 'Plus',
|
|
||||||
Positions = 'Positions',
|
Positions = 'Positions',
|
||||||
PriceChart = 'PriceChart',
|
PriceChart = 'PriceChart',
|
||||||
Privacy = 'Privacy',
|
Privacy = 'Privacy',
|
||||||
Qr = 'Qr',
|
Qr = 'Qr',
|
||||||
RewardStar = 'RewardStar',
|
|
||||||
Search = 'Search',
|
Search = 'Search',
|
||||||
Send = 'Send',
|
Send = 'Send',
|
||||||
Share = 'Share',
|
Share = 'Share',
|
||||||
Show = 'Show',
|
Show = 'Show',
|
||||||
Star = 'Star',
|
Star = 'Star',
|
||||||
Sun = 'Sun',
|
|
||||||
Terminal = 'Terminal',
|
Terminal = 'Terminal',
|
||||||
TogglesMenu = 'TogglesMenu',
|
TogglesMenu = 'TogglesMenu',
|
||||||
Trade = 'Trade',
|
Trade = 'Trade',
|
||||||
@@ -154,7 +137,6 @@ export enum IconName {
|
|||||||
Warning = 'Warning',
|
Warning = 'Warning',
|
||||||
Website = 'Website',
|
Website = 'Website',
|
||||||
Whitepaper = 'Whitepaper',
|
Whitepaper = 'Whitepaper',
|
||||||
Withdraw = 'Withdraw',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const icons = {
|
const icons = {
|
||||||
@@ -170,7 +152,6 @@ const icons = {
|
|||||||
[IconName.CautionCircleStroked]: CautionCircleStrokeIcon,
|
[IconName.CautionCircleStroked]: CautionCircleStrokeIcon,
|
||||||
[IconName.Chat]: ChatIcon,
|
[IconName.Chat]: ChatIcon,
|
||||||
[IconName.Check]: CheckIcon,
|
[IconName.Check]: CheckIcon,
|
||||||
[IconName.CheckCircle]: CheckCircleIcon,
|
|
||||||
[IconName.ChevronLeft]: ChevronLeftIcon,
|
[IconName.ChevronLeft]: ChevronLeftIcon,
|
||||||
[IconName.ChevronRight]: ChevronRightIcon,
|
[IconName.ChevronRight]: ChevronRightIcon,
|
||||||
[IconName.Clock]: ClockIcon,
|
[IconName.Clock]: ClockIcon,
|
||||||
@@ -179,7 +160,7 @@ const icons = {
|
|||||||
[IconName.Coins]: CoinsIcon,
|
[IconName.Coins]: CoinsIcon,
|
||||||
[IconName.Comment]: CommentIcon,
|
[IconName.Comment]: CommentIcon,
|
||||||
[IconName.Copy]: CopyIcon,
|
[IconName.Copy]: CopyIcon,
|
||||||
[IconName.Deposit]: DepositIcon,
|
[IconName.Cube]: CubeIcon,
|
||||||
[IconName.DepthChart]: DepthChartIcon,
|
[IconName.DepthChart]: DepthChartIcon,
|
||||||
[IconName.Discord]: DiscordIcon,
|
[IconName.Discord]: DiscordIcon,
|
||||||
[IconName.Etherscan]: EtherscanIcon,
|
[IconName.Etherscan]: EtherscanIcon,
|
||||||
@@ -192,14 +173,11 @@ const icons = {
|
|||||||
[IconName.HelpCircle]: HelpCircleIcon,
|
[IconName.HelpCircle]: HelpCircleIcon,
|
||||||
[IconName.Hide]: HideIcon,
|
[IconName.Hide]: HideIcon,
|
||||||
[IconName.History]: HistoryIcon,
|
[IconName.History]: HistoryIcon,
|
||||||
[IconName.Leaderboard]: LeaderboardIcon,
|
|
||||||
[IconName.LinkOut]: LinkOutIcon,
|
[IconName.LinkOut]: LinkOutIcon,
|
||||||
[IconName.Lock]: LockIcon,
|
[IconName.Lock]: LockIcon,
|
||||||
[IconName.LogoShort]: LogoShortIcon,
|
[IconName.LogoShort]: LogoShortIcon,
|
||||||
[IconName.Menu]: MenuIcon,
|
[IconName.Menu]: MenuIcon,
|
||||||
[IconName.Migrate]: MigrateIcon,
|
|
||||||
[IconName.Mintscan]: MintscanIcon,
|
[IconName.Mintscan]: MintscanIcon,
|
||||||
[IconName.Moon]: MoonIcon,
|
|
||||||
[IconName.Orderbook]: OrderbookIcon,
|
[IconName.Orderbook]: OrderbookIcon,
|
||||||
[IconName.OrderCanceled]: OrderCanceledIcon,
|
[IconName.OrderCanceled]: OrderCanceledIcon,
|
||||||
[IconName.OrderFilled]: OrderFilledIcon,
|
[IconName.OrderFilled]: OrderFilledIcon,
|
||||||
@@ -209,19 +187,15 @@ const icons = {
|
|||||||
[IconName.OrderUntriggered]: OrderUntriggeredIcon,
|
[IconName.OrderUntriggered]: OrderUntriggeredIcon,
|
||||||
[IconName.Overview]: OverviewIcon,
|
[IconName.Overview]: OverviewIcon,
|
||||||
[IconName.Pencil]: PencilIcon,
|
[IconName.Pencil]: PencilIcon,
|
||||||
[IconName.Play]: PlayIcon,
|
|
||||||
[IconName.Plus]: PlusIcon,
|
|
||||||
[IconName.Positions]: PositionsIcon,
|
[IconName.Positions]: PositionsIcon,
|
||||||
[IconName.PriceChart]: PriceChartIcon,
|
[IconName.PriceChart]: PriceChartIcon,
|
||||||
[IconName.Privacy]: PrivacyIcon,
|
[IconName.Privacy]: PrivacyIcon,
|
||||||
[IconName.Qr]: QrIcon,
|
[IconName.Qr]: QrIcon,
|
||||||
[IconName.RewardStar]: RewardStarIcon,
|
|
||||||
[IconName.Search]: SearchIcon,
|
[IconName.Search]: SearchIcon,
|
||||||
[IconName.Send]: SendIcon,
|
[IconName.Send]: SendIcon,
|
||||||
[IconName.Share]: ShareIcon,
|
[IconName.Share]: ShareIcon,
|
||||||
[IconName.Show]: ShowIcon,
|
[IconName.Show]: ShowIcon,
|
||||||
[IconName.Star]: StarIcon,
|
[IconName.Star]: StarIcon,
|
||||||
[IconName.Sun]: SunIcon,
|
|
||||||
[IconName.Terminal]: TerminalIcon,
|
[IconName.Terminal]: TerminalIcon,
|
||||||
[IconName.TogglesMenu]: TogglesMenuIcon,
|
[IconName.TogglesMenu]: TogglesMenuIcon,
|
||||||
[IconName.Trade]: TradeIcon,
|
[IconName.Trade]: TradeIcon,
|
||||||
@@ -231,7 +205,6 @@ const icons = {
|
|||||||
[IconName.Warning]: WarningIcon,
|
[IconName.Warning]: WarningIcon,
|
||||||
[IconName.Website]: WebsiteIcon,
|
[IconName.Website]: WebsiteIcon,
|
||||||
[IconName.Whitepaper]: WhitepaperIcon,
|
[IconName.Whitepaper]: WhitepaperIcon,
|
||||||
[IconName.Withdraw]: WithdrawIcon,
|
|
||||||
} as Record<IconName, ElementType>;
|
} as Record<IconName, ElementType>;
|
||||||
|
|
||||||
type ElementProps = {
|
type ElementProps = {
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ const Styled: Record<string, AnyStyledComponent> = {};
|
|||||||
const buttonMixin = css`
|
const buttonMixin = css`
|
||||||
// Params
|
// Params
|
||||||
--button-icon-size: 1.125em;
|
--button-icon-size: 1.125em;
|
||||||
--button-padding: 0;
|
|
||||||
|
|
||||||
// Rules
|
// Rules
|
||||||
> * {
|
> * {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
USD_DECIMALS,
|
USD_DECIMALS,
|
||||||
} from '@/constants/numbers';
|
} from '@/constants/numbers';
|
||||||
|
|
||||||
import { BIG_NUMBERS } from '@/lib/numbers';
|
import { BIG_NUMBERS, MustBigNumber } from '@/lib/numbers';
|
||||||
import { useLocaleSeparators } from '@/hooks';
|
import { useLocaleSeparators } from '@/hooks';
|
||||||
|
|
||||||
export enum InputType {
|
export enum InputType {
|
||||||
@@ -29,38 +29,31 @@ type StyleProps = {
|
|||||||
type ElementProps = {
|
type ElementProps = {
|
||||||
type?: InputType;
|
type?: InputType;
|
||||||
value?: string | number | null;
|
value?: string | number | null;
|
||||||
|
|
||||||
|
allowNegative?: boolean;
|
||||||
|
decimals?: number;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
id?: string;
|
id?: string;
|
||||||
|
max?: number;
|
||||||
onBlur?: () => void;
|
onBlur?: () => void;
|
||||||
|
onChange?:
|
||||||
|
| Dispatch<SetStateAction<string>>
|
||||||
|
| React.ReactEventHandler<HTMLInputElement>
|
||||||
|
| ((values: NumberFormatValues, e: SourceInfo) => void);
|
||||||
onFocus?: () => void;
|
onFocus?: () => void;
|
||||||
|
onInput?: ({
|
||||||
|
value,
|
||||||
|
floatValue,
|
||||||
|
formattedValue,
|
||||||
|
}: {
|
||||||
|
value: string;
|
||||||
|
floatValue?: number;
|
||||||
|
formattedValue: string;
|
||||||
|
}) => void;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ConditionalProps =
|
export type InputProps = ElementProps & StyleProps;
|
||||||
| {
|
|
||||||
allowNegative?: boolean;
|
|
||||||
decimals?: number;
|
|
||||||
max?: number;
|
|
||||||
onChange?: (values: NumberFormatValues, e: SourceInfo) => void;
|
|
||||||
onInput?: ({
|
|
||||||
value,
|
|
||||||
floatValue,
|
|
||||||
formattedValue,
|
|
||||||
}: {
|
|
||||||
value: string;
|
|
||||||
floatValue?: number;
|
|
||||||
formattedValue: string;
|
|
||||||
}) => void;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
allowNegative?: never;
|
|
||||||
decimals?: never;
|
|
||||||
max?: never;
|
|
||||||
onChange?: Dispatch<SetStateAction<string>> | React.ReactEventHandler<HTMLInputElement>;
|
|
||||||
onInput?: (e: SyntheticInputEvent) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type InputProps = ElementProps & StyleProps & ConditionalProps;
|
|
||||||
|
|
||||||
export const Input = forwardRef<HTMLInputElement, InputProps>(
|
export const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||||
(
|
(
|
||||||
@@ -136,7 +129,6 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
value={value}
|
value={value}
|
||||||
// Other
|
// Other
|
||||||
data-1p-ignore // prevent 1Password fill
|
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
@@ -145,7 +137,6 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|||||||
ref={ref as React.Ref<typeof NumericFormat<unknown>>}
|
ref={ref as React.Ref<typeof NumericFormat<unknown>>}
|
||||||
id={id}
|
id={id}
|
||||||
// NumericFormat
|
// NumericFormat
|
||||||
valueIsNumericString
|
|
||||||
allowNegative={allowNegative}
|
allowNegative={allowNegative}
|
||||||
decimalScale={decimals}
|
decimalScale={decimals}
|
||||||
decimalSeparator={LOCALE_DECIMAL_SEPARATOR}
|
decimalSeparator={LOCALE_DECIMAL_SEPARATOR}
|
||||||
@@ -169,7 +160,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|||||||
? undefined
|
? undefined
|
||||||
: Number(formattedValue.replace(',', '.'));
|
: Number(formattedValue.replace(',', '.'));
|
||||||
|
|
||||||
onInput?.({ value, floatValue, formattedValue, ...e });
|
onInput?.({ value, floatValue, formattedValue });
|
||||||
}}
|
}}
|
||||||
// Native
|
// Native
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
@@ -177,7 +168,6 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|||||||
value={formattedValue}
|
value={formattedValue}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
autoCorrect="off"
|
autoCorrect="off"
|
||||||
data-1p-ignore // prevent 1Password fill
|
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -198,6 +188,7 @@ Styled.InputContainer = styled.div`
|
|||||||
border-radius: inherit;
|
border-radius: inherit;
|
||||||
|
|
||||||
input {
|
input {
|
||||||
|
user-select: all;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||||
|
|
||||||
import { RiskLevels } from '@/constants/abacus';
|
import { RiskLevels } from '@/constants/abacus';
|
||||||
|
import { UsageColorFromRiskLevel } from '@/styles/colors';
|
||||||
|
|
||||||
import { Ring } from '@/components/Ring';
|
import { Ring } from '@/components/Ring';
|
||||||
|
|
||||||
import { abacusHelper } from '@/lib/abacus';
|
import { abacusHelper } from '@/lib/abacus';
|
||||||
import { UsageColorFromRiskLevel } from '@/lib/styles';
|
|
||||||
|
|
||||||
type ElementProps = {
|
type ElementProps = {
|
||||||
value: number;
|
value: number;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { forwardRef, Ref } from 'react';
|
import { forwardRef, Ref, useEffect, useRef, useState } from 'react';
|
||||||
import styled, { type AnyStyledComponent, css, keyframes } from 'styled-components';
|
import styled, { type AnyStyledComponent, css, keyframes } from 'styled-components';
|
||||||
import { NavLink, matchPath, useLocation } from 'react-router-dom';
|
import { NavLink, matchPath, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
@@ -21,6 +21,8 @@ import {
|
|||||||
|
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
|
||||||
|
import { isTruthy } from '@/lib/isTruthy';
|
||||||
|
|
||||||
import { Tag } from './Tag';
|
import { Tag } from './Tag';
|
||||||
import { Icon, IconName } from './Icon';
|
import { Icon, IconName } from './Icon';
|
||||||
|
|
||||||
@@ -59,6 +61,7 @@ const NavItem = forwardRef(
|
|||||||
<>
|
<>
|
||||||
{slotBefore}
|
{slotBefore}
|
||||||
<span>
|
<span>
|
||||||
|
{/* {`${label}${subitems?.length ? ' ' : ''}`} */}
|
||||||
{label}
|
{label}
|
||||||
{tag && (
|
{tag && (
|
||||||
<>
|
<>
|
||||||
@@ -68,7 +71,7 @@ const NavItem = forwardRef(
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
{slotAfter}
|
{slotAfter}
|
||||||
{subitems?.length && <Styled.Icon iconName={IconName.Triangle} />}
|
{subitems?.length && <Styled.Icon iconName={IconName.Caret} />}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -97,6 +100,8 @@ const NavItem = forwardRef(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
type TriggerRef = HTMLAnchorElement | HTMLDivElement | HTMLButtonElement | null;
|
||||||
|
|
||||||
export const NavigationMenu = <MenuItemValue extends string, MenuGroupValue extends string>({
|
export const NavigationMenu = <MenuItemValue extends string, MenuGroupValue extends string>({
|
||||||
onSelectItem,
|
onSelectItem,
|
||||||
onSelectGroup,
|
onSelectGroup,
|
||||||
@@ -107,6 +112,33 @@ export const NavigationMenu = <MenuItemValue extends string, MenuGroupValue exte
|
|||||||
dir = 'ltr',
|
dir = 'ltr',
|
||||||
className,
|
className,
|
||||||
}: ElementProps<MenuItemValue, MenuGroupValue> & StyleProps) => {
|
}: ElementProps<MenuItemValue, MenuGroupValue> & StyleProps) => {
|
||||||
|
// Disable click (close) in the first 500ms after hover (open)
|
||||||
|
// https://github.com/radix-ui/primitives/issues/1630#issuecomment-1545995075
|
||||||
|
const [clickIsDisabled, setClickIsDisabled] = useState(false);
|
||||||
|
const triggerRefs = useRef({} as { [value: string]: TriggerRef });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const observer = new MutationObserver((mutationsList) => {
|
||||||
|
for (const mutation of mutationsList) {
|
||||||
|
if (
|
||||||
|
mutation.type === 'attributes' &&
|
||||||
|
mutation.attributeName === 'data-state' &&
|
||||||
|
(mutation.target as unknown as HTMLOrSVGElement).dataset.state === 'open' &&
|
||||||
|
mutation.target !== document.activeElement
|
||||||
|
) {
|
||||||
|
setClickIsDisabled(true);
|
||||||
|
setTimeout(() => setClickIsDisabled(false), 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const element of Object.values(triggerRefs.current).filter(isTruthy)) {
|
||||||
|
observer.observe(element, { attributes: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const renderSubitems = ({
|
const renderSubitems = ({
|
||||||
item,
|
item,
|
||||||
depth = 0,
|
depth = 0,
|
||||||
@@ -116,18 +148,18 @@ export const NavigationMenu = <MenuItemValue extends string, MenuGroupValue exte
|
|||||||
}) => (
|
}) => (
|
||||||
<>
|
<>
|
||||||
<Styled.SubMenuTrigger
|
<Styled.SubMenuTrigger
|
||||||
asChild
|
asChild={depth > 0}
|
||||||
onPointerMove={(e: MouseEvent) => e.preventDefault()}
|
ref={(ref: TriggerRef) => (triggerRefs.current[item.value] = ref)}
|
||||||
onPointerLeave={(e: MouseEvent) => e.preventDefault()}
|
onClick={(e: MouseEvent) => {
|
||||||
|
if (clickIsDisabled) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Styled.NavItem onSelect={onSelectGroup} orientation={itemOrientation} {...item} />
|
<Styled.NavItem onSelect={onSelectGroup} orientation={itemOrientation} {...item} />
|
||||||
</Styled.SubMenuTrigger>
|
</Styled.SubMenuTrigger>
|
||||||
|
|
||||||
<Styled.Content
|
<Styled.Content data-placement={submenuPlacement}>
|
||||||
onPointerEnter={(e: MouseEvent) => e.preventDefault()}
|
|
||||||
onPointerLeave={(e: MouseEvent) => e.preventDefault()}
|
|
||||||
data-placement={submenuPlacement}
|
|
||||||
>
|
|
||||||
<Styled.Sub data-placement={submenuPlacement}>
|
<Styled.Sub data-placement={submenuPlacement}>
|
||||||
<Styled.List
|
<Styled.List
|
||||||
data-orientation={depth > 0 ? 'menu' : orientation === 'vertical' ? 'vertical' : 'menu'}
|
data-orientation={depth > 0 ? 'menu' : orientation === 'vertical' ? 'vertical' : 'menu'}
|
||||||
@@ -378,12 +410,25 @@ Styled.List = styled(List)`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.ListItem = styled(Item)`
|
Styled.ListItem = styled(Item)`
|
||||||
|
/* display: contents; */
|
||||||
display: grid;
|
display: grid;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
${Styled.List}[data-orientation="horizontal"] > & {
|
${Styled.List}[data-orientation="horizontal"] > & {
|
||||||
gap: var(--submenu-side-offset);
|
gap: var(--submenu-side-offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ${Styled.List}[data-orientation="menu"] > & {
|
||||||
|
grid-template-columns: 1fr 0;
|
||||||
|
align-items: start;
|
||||||
|
gap: 2rem;
|
||||||
|
} */
|
||||||
|
|
||||||
|
/* &:has([data-state="open"]) {
|
||||||
|
position: sticky;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
} */
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.SubMenuTrigger = styled(Trigger)`
|
Styled.SubMenuTrigger = styled(Trigger)`
|
||||||
@@ -391,6 +436,11 @@ Styled.SubMenuTrigger = styled(Trigger)`
|
|||||||
outline-offset: -2px;
|
outline-offset: -2px;
|
||||||
|
|
||||||
&[data-state='open'] {
|
&[data-state='open'] {
|
||||||
|
div {
|
||||||
|
background-color: var(--navigationMenu-item-checked-backgroundColor);
|
||||||
|
color: var(--navigationMenu-item-checked-textColor);
|
||||||
|
}
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
rotate: 0.5turn;
|
rotate: 0.5turn;
|
||||||
}
|
}
|
||||||
@@ -402,10 +452,11 @@ Styled.NavItem = styled(NavItem)<{ orientation: 'horizontal' | 'vertical' }>`
|
|||||||
subitems?.length
|
subitems?.length
|
||||||
? css`
|
? css`
|
||||||
${popoverMixins.trigger}
|
${popoverMixins.trigger}
|
||||||
--trigger-open-backgroundColor: var(--navigationMenu-item-checked-backgroundColor);
|
--trigger-backgroundColor: transparent;
|
||||||
|
--trigger-open-backgroundColor: var(--color-layer-3);
|
||||||
`
|
`
|
||||||
: css`
|
: css`
|
||||||
&:hover:not(:active) {
|
&:hover:not(.active) {
|
||||||
background-color: var(--navigationMenu-item-highlighted-backgroundColor);
|
background-color: var(--navigationMenu-item-highlighted-backgroundColor);
|
||||||
color: var(--navigationMenu-item-highlighted-textColor);
|
color: var(--navigationMenu-item-highlighted-textColor);
|
||||||
}
|
}
|
||||||
@@ -419,6 +470,8 @@ Styled.NavItem = styled(NavItem)<{ orientation: 'horizontal' | 'vertical' }>`
|
|||||||
--item-radius: var(--navigationMenu-item-radius);
|
--item-radius: var(--navigationMenu-item-radius);
|
||||||
--item-padding: var(--navigationMenu-item-padding);
|
--item-padding: var(--navigationMenu-item-padding);
|
||||||
|
|
||||||
|
/* ${popoverMixins.backdropOverlay} */
|
||||||
|
|
||||||
${layoutMixins.scrollSnapItem}
|
${layoutMixins.scrollSnapItem}
|
||||||
|
|
||||||
min-height: var(--navigationMenu-item-height);
|
min-height: var(--navigationMenu-item-height);
|
||||||
@@ -472,9 +525,6 @@ Styled.NavItem = styled(NavItem)<{ orientation: 'horizontal' | 'vertical' }>`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.Icon = styled(Icon)`
|
Styled.Icon = styled(Icon)`
|
||||||
font-size: 0.375em;
|
|
||||||
transition: rotate 0.3s var(--ease-out-expo);
|
|
||||||
|
|
||||||
${Styled.List}[data-orientation="menu"] & {
|
${Styled.List}[data-orientation="menu"] & {
|
||||||
rotate: -0.25turn;
|
rotate: -0.25turn;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,201 +0,0 @@
|
|||||||
import React, { type MouseEvent } from 'react';
|
|
||||||
import styled, { css } from 'styled-components';
|
|
||||||
|
|
||||||
import { ButtonShape, ButtonSize } from '@/constants/buttons';
|
|
||||||
|
|
||||||
import {
|
|
||||||
NotificationStatus,
|
|
||||||
type Notification as NotificationDataType,
|
|
||||||
} from '@/constants/notifications';
|
|
||||||
|
|
||||||
import { useNotifications } from '@/hooks/useNotifications';
|
|
||||||
|
|
||||||
import { popoverMixins } from '@/styles/popoverMixins';
|
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
|
||||||
|
|
||||||
import { IconButton } from '@/components/IconButton';
|
|
||||||
import { IconName } from '@/components/Icon';
|
|
||||||
import { Output, OutputType } from '@/components/Output';
|
|
||||||
|
|
||||||
type ElementProps = {
|
|
||||||
notification: NotificationDataType;
|
|
||||||
onClick?: () => void;
|
|
||||||
slotIcon?: React.ReactNode;
|
|
||||||
slotTitle?: React.ReactNode;
|
|
||||||
slotTitleLeft?: React.ReactNode;
|
|
||||||
slotTitleRight?: React.ReactNode;
|
|
||||||
slotDescription?: React.ReactNode;
|
|
||||||
slotCustomContent?: React.ReactNode;
|
|
||||||
slotAction?: React.ReactNode;
|
|
||||||
withClose?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
type StyleProps = {
|
|
||||||
className?: string;
|
|
||||||
isToast?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type NotificationProps = ElementProps & StyleProps;
|
|
||||||
|
|
||||||
export const Notification = ({
|
|
||||||
className,
|
|
||||||
isToast,
|
|
||||||
notification,
|
|
||||||
onClick,
|
|
||||||
slotIcon,
|
|
||||||
slotTitle,
|
|
||||||
slotTitleLeft,
|
|
||||||
slotTitleRight,
|
|
||||||
slotDescription,
|
|
||||||
slotCustomContent,
|
|
||||||
slotAction,
|
|
||||||
withClose = !isToast,
|
|
||||||
}: NotificationProps) => {
|
|
||||||
const { markCleared, markSeen } = useNotifications();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<$Container className={className} isToast={isToast} onClick={onClick}>
|
|
||||||
<$Header>
|
|
||||||
{slotTitleLeft ?? (slotIcon && <$Icon>{slotIcon}</$Icon>)}
|
|
||||||
<$Title>{slotTitle}</$Title>
|
|
||||||
{slotTitleRight}
|
|
||||||
{withClose && !isToast && (
|
|
||||||
<$ActionItems>
|
|
||||||
<$Output
|
|
||||||
type={OutputType.RelativeTime}
|
|
||||||
value={notification.timestamps[NotificationStatus.Triggered]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{notification.status < NotificationStatus.Seen ? <$UnreadIndicator /> : null}
|
|
||||||
|
|
||||||
{notification.status < NotificationStatus.Cleared ? (
|
|
||||||
<$IconButton
|
|
||||||
iconName={IconName.Close}
|
|
||||||
shape={ButtonShape.Square}
|
|
||||||
size={ButtonSize.XSmall}
|
|
||||||
onClick={(e: MouseEvent) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
if (notification.status < NotificationStatus.Seen) {
|
|
||||||
markSeen(notification);
|
|
||||||
} else if (notification.status < NotificationStatus.Cleared) {
|
|
||||||
markCleared(notification);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</$ActionItems>
|
|
||||||
)}
|
|
||||||
</$Header>
|
|
||||||
<$Description>{slotCustomContent ?? slotDescription}</$Description>
|
|
||||||
{slotAction && <$Action>{slotAction}</$Action>}
|
|
||||||
</$Container>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const $Container = styled.div<{ isToast?: boolean }>`
|
|
||||||
// Params
|
|
||||||
--toast-icon-size: 1.75em;
|
|
||||||
|
|
||||||
// Rules
|
|
||||||
${popoverMixins.popover}
|
|
||||||
overflow: visible;
|
|
||||||
padding: 1rem 1.25rem;
|
|
||||||
|
|
||||||
${({ isToast }) =>
|
|
||||||
isToast
|
|
||||||
? css`
|
|
||||||
padding: 1rem;
|
|
||||||
box-shadow: 0 0 0 var(--border-width) var(--color-border),
|
|
||||||
// border
|
|
||||||
0 0 0.5rem 0.1rem var(--color-layer-2); // shadow
|
|
||||||
`
|
|
||||||
: css`
|
|
||||||
flex: 1;
|
|
||||||
background-color: transparent;
|
|
||||||
border-radius: 0;
|
|
||||||
backdrop-filter: none;
|
|
||||||
`}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const $Header = styled.header`
|
|
||||||
${layoutMixins.row}
|
|
||||||
position: relative;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const $Icon = styled.div`
|
|
||||||
${layoutMixins.row}
|
|
||||||
float: left;
|
|
||||||
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
|
|
||||||
line-height: 1;
|
|
||||||
|
|
||||||
> svg,
|
|
||||||
img {
|
|
||||||
width: 1.5rem;
|
|
||||||
height: 1.5rem;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const $Title = styled.div`
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
font: var(--font-base-medium);
|
|
||||||
color: var(--color-text-2);
|
|
||||||
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const $Description = styled.div`
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
color: var(--color-text-0);
|
|
||||||
font: var(--font-small-book);
|
|
||||||
`;
|
|
||||||
|
|
||||||
const $Action = styled.div`
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
font: var(--font-small-book);
|
|
||||||
`;
|
|
||||||
|
|
||||||
const $ActionItems = styled.div`
|
|
||||||
min-width: fit-content;
|
|
||||||
padding-left: 0.5rem;
|
|
||||||
height: 100%;
|
|
||||||
${layoutMixins.row};
|
|
||||||
justify-content: end;
|
|
||||||
gap: 0.5rem;
|
|
||||||
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
|
|
||||||
background-color: var(--color-layer-2);
|
|
||||||
font: var(--font-mini-book);
|
|
||||||
|
|
||||||
display: none;
|
|
||||||
z-index: 1;
|
|
||||||
|
|
||||||
${$Container}:hover & {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const $UnreadIndicator = styled.div`
|
|
||||||
width: 0.5rem;
|
|
||||||
height: 0.5rem;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: var(--color-accent);
|
|
||||||
border: 1px solid var(--color-layer-2);
|
|
||||||
`;
|
|
||||||
|
|
||||||
const $Output = styled(Output)`
|
|
||||||
color: var(--color-text-0);
|
|
||||||
`;
|
|
||||||
|
|
||||||
const $IconButton = styled(IconButton)`
|
|
||||||
--button-border: none;
|
|
||||||
--button-textColor: var(--color-text-0);
|
|
||||||
--button-hover-textColor: var(--color-text-1);
|
|
||||||
`;
|
|
||||||
@@ -4,12 +4,10 @@ import { Link } from 'react-router-dom';
|
|||||||
import { Icon, IconName } from '@/components/Icon';
|
import { Icon, IconName } from '@/components/Icon';
|
||||||
|
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
import { breakpoints } from '@/styles';
|
|
||||||
|
|
||||||
type PanelProps = {
|
type PanelProps = {
|
||||||
slotHeaderContent?: React.ReactNode;
|
slotHeaderContent?: string;
|
||||||
slotHeader?: React.ReactNode;
|
slotHeader?: React.ReactNode;
|
||||||
slotRight?: React.ReactNode;
|
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
href?: string;
|
href?: string;
|
||||||
onHeaderClick?: () => void;
|
onHeaderClick?: () => void;
|
||||||
@@ -24,7 +22,6 @@ type PanelStyleProps = {
|
|||||||
export const Panel = ({
|
export const Panel = ({
|
||||||
slotHeaderContent,
|
slotHeaderContent,
|
||||||
slotHeader,
|
slotHeader,
|
||||||
slotRight,
|
|
||||||
children,
|
children,
|
||||||
href,
|
href,
|
||||||
onHeaderClick,
|
onHeaderClick,
|
||||||
@@ -32,86 +29,56 @@ export const Panel = ({
|
|||||||
hasSeparator,
|
hasSeparator,
|
||||||
className,
|
className,
|
||||||
}: PanelProps & PanelStyleProps) => (
|
}: PanelProps & PanelStyleProps) => (
|
||||||
<Styled.Panel onClick={onClick} className={className}>
|
<Styled.Panel onClick={onClick}>
|
||||||
<Styled.Left>
|
{href ? (
|
||||||
{href ? (
|
<Link to={href}>
|
||||||
<Link to={href}>
|
{slotHeader ? (
|
||||||
{slotHeader ? (
|
slotHeader
|
||||||
slotHeader
|
) : (
|
||||||
) : (
|
|
||||||
<Styled.Header role="button" onClick={onHeaderClick} hasSeparator={hasSeparator}>
|
|
||||||
{slotHeaderContent}
|
|
||||||
<Styled.Icon iconName={IconName.ChevronRight} />
|
|
||||||
</Styled.Header>
|
|
||||||
)}
|
|
||||||
</Link>
|
|
||||||
) : slotHeader ? (
|
|
||||||
slotHeader
|
|
||||||
) : (
|
|
||||||
slotHeaderContent && (
|
|
||||||
<Styled.Header role="button" onClick={onHeaderClick} hasSeparator={hasSeparator}>
|
<Styled.Header role="button" onClick={onHeaderClick} hasSeparator={hasSeparator}>
|
||||||
{slotHeaderContent}
|
{slotHeaderContent}
|
||||||
|
<Styled.Icon iconName={IconName.ChevronRight} />
|
||||||
</Styled.Header>
|
</Styled.Header>
|
||||||
)
|
)}
|
||||||
)}
|
</Link>
|
||||||
<Styled.Content>{children}</Styled.Content>
|
) : slotHeader ? (
|
||||||
</Styled.Left>
|
slotHeader
|
||||||
{slotRight}
|
) : (
|
||||||
|
slotHeaderContent && (
|
||||||
|
<Styled.Header role="button" onClick={onHeaderClick} hasSeparator={hasSeparator}>
|
||||||
|
{slotHeaderContent}
|
||||||
|
</Styled.Header>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
<Styled.Content className={className}>{children}</Styled.Content>
|
||||||
</Styled.Panel>
|
</Styled.Panel>
|
||||||
);
|
);
|
||||||
|
|
||||||
const Styled: Record<string, AnyStyledComponent> = {};
|
const Styled: Record<string, AnyStyledComponent> = {};
|
||||||
|
|
||||||
Styled.Panel = styled.section<{ onClick?: () => void }>`
|
Styled.Panel = styled.section`
|
||||||
--panel-paddingY: 1rem;
|
${layoutMixins.flexColumn}
|
||||||
--panel-paddingX: 1rem;
|
|
||||||
--panel-content-paddingY: var(--panel-paddingY);
|
|
||||||
--panel-content-paddingX: var(--panel-paddingX);
|
|
||||||
|
|
||||||
@media ${breakpoints.notTablet} {
|
|
||||||
--panel-paddingX: 1.5rem;
|
|
||||||
--panel-paddingY: 1.25rem;
|
|
||||||
--panel-content-paddingY: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
${layoutMixins.row}
|
|
||||||
|
|
||||||
background-color: var(--color-layer-3);
|
background-color: var(--color-layer-3);
|
||||||
border-radius: 0.875rem;
|
border-radius: 0.875rem;
|
||||||
|
|
||||||
${({ onClick }) =>
|
|
||||||
onClick &&
|
|
||||||
css`
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
button:not(:disabled) {
|
|
||||||
color: var(--button-hover-textColor);
|
|
||||||
filter: var(--button-hover-filter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
`;
|
|
||||||
|
|
||||||
Styled.Left = styled.div`
|
|
||||||
${layoutMixins.flexColumn}
|
|
||||||
width: 100%;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.Header = styled.header<{ hasSeparator?: boolean }>`
|
Styled.Header = styled.header<{ hasSeparator?: boolean }>`
|
||||||
${layoutMixins.spacedRow}
|
${layoutMixins.spacedRow}
|
||||||
padding: var(--panel-paddingY) var(--panel-paddingX);
|
|
||||||
|
padding: 0.875rem 1rem 0.625rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
|
||||||
${({ hasSeparator }) =>
|
${({ hasSeparator }) =>
|
||||||
hasSeparator &&
|
hasSeparator &&
|
||||||
css`
|
css`
|
||||||
padding-bottom: 0.625rem;
|
|
||||||
box-shadow: 0 var(--border-width) var(--border-color);
|
box-shadow: 0 var(--border-width) var(--border-color);
|
||||||
`}
|
`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.Icon = styled(Icon)`
|
Styled.Icon = styled(Icon)`
|
||||||
color: var(--color-text-0);
|
color: var(--color-text-0);
|
||||||
|
|
||||||
font-size: 0.625rem;
|
font-size: 0.625rem;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -119,5 +86,6 @@ Styled.Content = styled.div`
|
|||||||
${layoutMixins.scrollArea}
|
${layoutMixins.scrollArea}
|
||||||
${layoutMixins.stickyArea0}
|
${layoutMixins.stickyArea0}
|
||||||
--stickyArea0-background: transparent;
|
--stickyArea0-background: transparent;
|
||||||
padding: var(--panel-content-paddingY) var(--panel-content-paddingX);
|
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useRef, useState } from 'react';
|
import { useState } from 'react';
|
||||||
import styled, { type AnyStyledComponent, css } from 'styled-components';
|
import styled, { type AnyStyledComponent, css } from 'styled-components';
|
||||||
|
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
@@ -22,11 +22,6 @@ export const SearchInput = ({
|
|||||||
}: SearchInputProps) => {
|
}: SearchInputProps) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [value, setValue] = useState('');
|
const [value, setValue] = useState('');
|
||||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isOpen) inputRef?.current?.focus();
|
|
||||||
}, [inputRef, isOpen]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Styled.Search>
|
<Styled.Search>
|
||||||
@@ -46,7 +41,6 @@ export const SearchInput = ({
|
|||||||
/>
|
/>
|
||||||
<Styled.Input
|
<Styled.Input
|
||||||
autoFocus
|
autoFocus
|
||||||
ref={inputRef}
|
|
||||||
value={value}
|
value={value}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
type={type}
|
type={type}
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ import { WithDetailsReceipt } from '@/components/WithDetailsReceipt';
|
|||||||
import { WithLabel } from '@/components/WithLabel';
|
import { WithLabel } from '@/components/WithLabel';
|
||||||
|
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
import { formMixins } from '@/styles/formMixins';
|
|
||||||
import breakpoints from '@/styles/breakpoints';
|
|
||||||
|
|
||||||
type ElementProps = {
|
type ElementProps = {
|
||||||
asChild?: boolean;
|
asChild?: boolean;
|
||||||
@@ -86,30 +84,27 @@ export const SearchSelectMenu = ({
|
|||||||
|
|
||||||
const Styled: Record<string, AnyStyledComponent> = {};
|
const Styled: Record<string, AnyStyledComponent> = {};
|
||||||
|
|
||||||
Styled.SearchSelectMenu = styled.div`
|
|
||||||
${layoutMixins.column}
|
|
||||||
`;
|
|
||||||
|
|
||||||
Styled.MenuTrigger = styled.div`
|
Styled.MenuTrigger = styled.div`
|
||||||
height: var(--form-input-height);
|
height: var(--form-input-height);
|
||||||
|
|
||||||
${layoutMixins.spacedRow}
|
${layoutMixins.spacedRow}
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: var(--form-input-paddingY) var(--form-input-paddingX);
|
padding: 0 1rem;
|
||||||
|
|
||||||
@media ${breakpoints.tablet} {
|
|
||||||
height: var(--form-input-height-mobile);
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.WithLabel = styled(WithLabel)`
|
Styled.WithLabel = styled(WithLabel)`
|
||||||
${formMixins.inputLabel}
|
--label-textColor: var(--color-text-0);
|
||||||
|
gap: 0.25rem;
|
||||||
|
|
||||||
label {
|
label {
|
||||||
font: var(--font-mini-book);
|
font: var(--font-mini-book);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
Styled.SearchSelectMenu = styled.div`
|
||||||
|
${layoutMixins.column}
|
||||||
|
`;
|
||||||
|
|
||||||
Styled.WithDetailsReceipt = styled(WithDetailsReceipt)`
|
Styled.WithDetailsReceipt = styled(WithDetailsReceipt)`
|
||||||
--withReceipt-backgroundColor: var(--color-layer-2);
|
--withReceipt-backgroundColor: var(--color-layer-2);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled, { css, type AnyStyledComponent } from 'styled-components';
|
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Root,
|
Root,
|
||||||
@@ -13,13 +13,11 @@ import {
|
|||||||
Portal,
|
Portal,
|
||||||
Icon as SelectIcon,
|
Icon as SelectIcon,
|
||||||
} from '@radix-ui/react-select';
|
} from '@radix-ui/react-select';
|
||||||
import { CheckIcon } from '@radix-ui/react-icons';
|
|
||||||
|
|
||||||
import { popoverMixins } from '@/styles/popoverMixins';
|
import { popoverMixins } from '@/styles/popoverMixins';
|
||||||
import { formMixins } from '@/styles/formMixins';
|
import { formMixins } from '@/styles/formMixins';
|
||||||
|
|
||||||
import { WithLabel } from '@/components/WithLabel';
|
import { WithLabel } from '@/components/WithLabel';
|
||||||
import { Icon, IconName } from './Icon';
|
|
||||||
|
|
||||||
export const SelectMenu = <T extends string>({
|
export const SelectMenu = <T extends string>({
|
||||||
children,
|
children,
|
||||||
@@ -46,9 +44,7 @@ export const SelectMenu = <T extends string>({
|
|||||||
) : (
|
) : (
|
||||||
<Value />
|
<Value />
|
||||||
)}
|
)}
|
||||||
{React.Children.toArray(children).length > 1 && (
|
{React.Children.toArray(children).length > 1 && <Styled.DropdownIcon />}
|
||||||
<Styled.TriggerIcon iconName={IconName.Triangle} />
|
|
||||||
)}
|
|
||||||
</Styled.Trigger>
|
</Styled.Trigger>
|
||||||
<Portal>
|
<Portal>
|
||||||
<Styled.Content className={className}>
|
<Styled.Content className={className}>
|
||||||
@@ -72,9 +68,7 @@ export const SelectItem = <T extends string>({
|
|||||||
}) => (
|
}) => (
|
||||||
<Styled.Item className={className} value={value}>
|
<Styled.Item className={className} value={value}>
|
||||||
<ItemText>{label}</ItemText>
|
<ItemText>{label}</ItemText>
|
||||||
<Styled.ItemIndicator>
|
<Styled.ItemIndicator>✔{/* <CheckIcon /> */}</Styled.ItemIndicator>
|
||||||
<CheckIcon />
|
|
||||||
</Styled.ItemIndicator>
|
|
||||||
</Styled.Item>
|
</Styled.Item>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -94,6 +88,12 @@ Styled.Trigger = styled(Trigger)<{ $withBlur?: boolean }>`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
Styled.DropdownIcon = styled(SelectIcon)`
|
||||||
|
font-size: 0.675em;
|
||||||
|
color: var(--color-text-1);
|
||||||
|
margin-left: auto;
|
||||||
|
`;
|
||||||
|
|
||||||
Styled.Content = styled(Content)`
|
Styled.Content = styled(Content)`
|
||||||
--select-menu-content-maxWidth: ;
|
--select-menu-content-maxWidth: ;
|
||||||
max-width: var(--select-menu-content-maxWidth);
|
max-width: var(--select-menu-content-maxWidth);
|
||||||
@@ -116,9 +116,3 @@ Styled.WithLabel = styled(WithLabel)`
|
|||||||
${formMixins.inputLabel}
|
${formMixins.inputLabel}
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.TriggerIcon = styled(Icon)`
|
|
||||||
width: 0.625rem;
|
|
||||||
height: 0.375rem;
|
|
||||||
color: var(--color-text-0);
|
|
||||||
`;
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ Styled.Root = styled(Root)`
|
|||||||
--switch-thumb-backgroundColor: var(--color-layer-6);
|
--switch-thumb-backgroundColor: var(--color-layer-6);
|
||||||
|
|
||||||
--switch-active-backgroundColor: var(--color-accent);
|
--switch-active-backgroundColor: var(--color-accent);
|
||||||
--switch-active-thumb-backgroundColor: ${({ theme }) => theme.switchThumbActiveBackground};
|
--switch-active-thumb-backgroundColor: var(--color-white);
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
width: var(--switch-width);
|
width: var(--switch-width);
|
||||||
|
|||||||
@@ -33,20 +33,16 @@ import {
|
|||||||
|
|
||||||
import { useAsyncList } from 'react-stately';
|
import { useAsyncList } from 'react-stately';
|
||||||
|
|
||||||
import { useBreakpoints, useStringGetter } from '@/hooks';
|
import { useBreakpoints } from '@/hooks';
|
||||||
import { MediaQueryKeys } from '@/hooks/useBreakpoints';
|
import { MediaQueryKeys } from '@/hooks/useBreakpoints';
|
||||||
|
|
||||||
|
import { Checkbox } from '@/components/Checkbox';
|
||||||
|
|
||||||
import { breakpoints } from '@/styles';
|
import { breakpoints } from '@/styles';
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
import { CaretIcon } from '@/icons';
|
|
||||||
|
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
|
||||||
|
|
||||||
import { MustBigNumber } from '@/lib/numbers';
|
|
||||||
|
|
||||||
import { Icon, IconName } from './Icon';
|
import { Icon, IconName } from './Icon';
|
||||||
import { Tag } from './Tag';
|
import { Tag } from './Tag';
|
||||||
import { Button } from './Button';
|
|
||||||
|
|
||||||
export { TableCell } from './Table/TableCell';
|
export { TableCell } from './Table/TableCell';
|
||||||
export { TableColumnHeader } from './Table/TableColumnHeader';
|
export { TableColumnHeader } from './Table/TableColumnHeader';
|
||||||
@@ -68,7 +64,7 @@ export type TableItem<TableRowData> = {
|
|||||||
onSelect?: (key: TableRowData) => void;
|
onSelect?: (key: TableRowData) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ColumnDef<TableRowData extends object> = {
|
export type ColumnDef<TableRowData extends object> = {
|
||||||
columnKey: string;
|
columnKey: string;
|
||||||
label: React.ReactNode;
|
label: React.ReactNode;
|
||||||
tag?: React.ReactNode;
|
tag?: React.ReactNode;
|
||||||
@@ -83,7 +79,7 @@ type ColumnDef<TableRowData extends object> = {
|
|||||||
width?: ColumnSize;
|
width?: ColumnSize;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ElementProps<TableRowData extends object | CustomRowConfig, TableRowKey extends Key> = {
|
type ElementProps<TableRowData extends object | CustomRowConfig, TableRowKey extends Key> = {
|
||||||
label?: string;
|
label?: string;
|
||||||
columns: ColumnDef<TableRowData>[];
|
columns: ColumnDef<TableRowData>[];
|
||||||
data: TableRowData[];
|
data: TableRowData[];
|
||||||
@@ -95,7 +91,6 @@ export type ElementProps<TableRowData extends object | CustomRowConfig, TableRow
|
|||||||
selectionBehavior?: 'replace' | 'toggle';
|
selectionBehavior?: 'replace' | 'toggle';
|
||||||
onRowAction?: (key: TableRowKey, row: TableRowData) => void;
|
onRowAction?: (key: TableRowKey, row: TableRowData) => void;
|
||||||
slotEmpty?: React.ReactNode;
|
slotEmpty?: React.ReactNode;
|
||||||
initialNumRowsToShow?: number;
|
|
||||||
// collection: TableCollection<string>;
|
// collection: TableCollection<string>;
|
||||||
// children: React.ReactNode;
|
// children: React.ReactNode;
|
||||||
};
|
};
|
||||||
@@ -125,7 +120,6 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
|
|||||||
selectionMode = 'single',
|
selectionMode = 'single',
|
||||||
selectionBehavior = 'toggle',
|
selectionBehavior = 'toggle',
|
||||||
slotEmpty,
|
slotEmpty,
|
||||||
initialNumRowsToShow,
|
|
||||||
// shouldRowRender,
|
// shouldRowRender,
|
||||||
|
|
||||||
// collection,
|
// collection,
|
||||||
@@ -141,8 +135,6 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
|
|||||||
style,
|
style,
|
||||||
}: ElementProps<TableRowData, TableRowKey> & StyleProps) => {
|
}: ElementProps<TableRowData, TableRowKey> & StyleProps) => {
|
||||||
const [selectedKeys, setSelectedKeys] = useState(new Set<TableRowKey>());
|
const [selectedKeys, setSelectedKeys] = useState(new Set<TableRowKey>());
|
||||||
const [numRowsToShow, setNumRowsToShow] = useState(initialNumRowsToShow);
|
|
||||||
const enableViewMore = numRowsToShow !== undefined;
|
|
||||||
|
|
||||||
const currentBreakpoints = useBreakpoints();
|
const currentBreakpoints = useBreakpoints();
|
||||||
const shownColumns = columns.filter(
|
const shownColumns = columns.filter(
|
||||||
@@ -169,7 +161,10 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
|
|||||||
? // String
|
? // String
|
||||||
collator.compare(first as string, second as string)
|
collator.compare(first as string, second as string)
|
||||||
: // Number
|
: // Number
|
||||||
MustBigNumber(first).comparedTo(MustBigNumber(second))) *
|
Math.sign(
|
||||||
|
(parseInt(first as string) || (first as number)) -
|
||||||
|
(parseInt(second as string) || (second as number))
|
||||||
|
)) *
|
||||||
// Flip the direction if descending order is specified.
|
// Flip the direction if descending order is specified.
|
||||||
(sortDirection === 'descending' ? -1 : 1)
|
(sortDirection === 'descending' ? -1 : 1)
|
||||||
);
|
);
|
||||||
@@ -216,12 +211,6 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
|
|||||||
onRowAction &&
|
onRowAction &&
|
||||||
((key: TableRowKey) => onRowAction(key, data.find((row) => getRowKey(row) === key)!))
|
((key: TableRowKey) => onRowAction(key, data.find((row) => getRowKey(row) === key)!))
|
||||||
}
|
}
|
||||||
numColumns={shownColumns.length}
|
|
||||||
onViewMoreClick={
|
|
||||||
enableViewMore && numRowsToShow < data.length
|
|
||||||
? () => setNumRowsToShow(data.length)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
// shouldRowRender={shouldRowRender}
|
// shouldRowRender={shouldRowRender}
|
||||||
hideHeader={hideHeader}
|
hideHeader={hideHeader}
|
||||||
withGradientCardRows={withGradientCardRows}
|
withGradientCardRows={withGradientCardRows}
|
||||||
@@ -246,7 +235,7 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
|
|||||||
)}
|
)}
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
|
|
||||||
<TableBody items={enableViewMore ? list.items.slice(0, numRowsToShow) : list.items}>
|
<TableBody items={list.items}>
|
||||||
{(item) => (
|
{(item) => (
|
||||||
<Row key={getRowKey(item)}>
|
<Row key={getRowKey(item)}>
|
||||||
{(columnKey) => (
|
{(columnKey) => (
|
||||||
@@ -280,8 +269,6 @@ const TableRoot = <TableRowData extends object | CustomRowConfig, TableRowKey ex
|
|||||||
onRowAction?: (key: TableRowKey) => void;
|
onRowAction?: (key: TableRowKey) => void;
|
||||||
// shouldRowRender?: (prevRowData: object, currentRowData: object) => boolean;
|
// shouldRowRender?: (prevRowData: object, currentRowData: object) => boolean;
|
||||||
children: CollectionChildren<TableRowData>;
|
children: CollectionChildren<TableRowData>;
|
||||||
numColumns: number;
|
|
||||||
onViewMoreClick?: () => void;
|
|
||||||
|
|
||||||
hideHeader?: boolean;
|
hideHeader?: boolean;
|
||||||
withGradientCardRows?: boolean;
|
withGradientCardRows?: boolean;
|
||||||
@@ -291,7 +278,7 @@ const TableRoot = <TableRowData extends object | CustomRowConfig, TableRowKey ex
|
|||||||
withScrollSnapColumns?: boolean;
|
withScrollSnapColumns?: boolean;
|
||||||
withScrollSnapRows?: boolean;
|
withScrollSnapRows?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const { selectionMode, selectionBehavior, numColumns, onViewMoreClick } = props;
|
const { selectionMode, selectionBehavior } = props;
|
||||||
|
|
||||||
const state = useTableState<TableRowData>({
|
const state = useTableState<TableRowData>({
|
||||||
...props,
|
...props,
|
||||||
@@ -352,7 +339,6 @@ const TableRoot = <TableRowData extends object | CustomRowConfig, TableRowKey ex
|
|||||||
<TableBodyRowGroup
|
<TableBodyRowGroup
|
||||||
withGradientCardRows={props.withGradientCardRows}
|
withGradientCardRows={props.withGradientCardRows}
|
||||||
withInnerBorders={props.withInnerBorders}
|
withInnerBorders={props.withInnerBorders}
|
||||||
withOuterBorder={props.withOuterBorder}
|
|
||||||
>
|
>
|
||||||
{/* {Array.from(collection.getChildren!(collection.body.key), (row) => */}
|
{/* {Array.from(collection.getChildren!(collection.body.key), (row) => */}
|
||||||
{[...collection.body.childNodes].map((row) =>
|
{[...collection.body.childNodes].map((row) =>
|
||||||
@@ -398,9 +384,6 @@ const TableRoot = <TableRowData extends object | CustomRowConfig, TableRowKey ex
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
{onViewMoreClick ? (
|
|
||||||
<ViewMoreRow colSpan={numColumns} onClick={onViewMoreClick} />
|
|
||||||
) : undefined}
|
|
||||||
</TableBodyRowGroup>
|
</TableBodyRowGroup>
|
||||||
</Styled.Table>
|
</Styled.Table>
|
||||||
);
|
);
|
||||||
@@ -434,7 +417,6 @@ const TableBodyRowGroup = ({
|
|||||||
children,
|
children,
|
||||||
withGradientCardRows,
|
withGradientCardRows,
|
||||||
withInnerBorders,
|
withInnerBorders,
|
||||||
withOuterBorder,
|
|
||||||
}: { children: React.ReactNode } & StyleProps) => {
|
}: { children: React.ReactNode } & StyleProps) => {
|
||||||
const { rowGroupProps } = useTableRowGroup();
|
const { rowGroupProps } = useTableRowGroup();
|
||||||
|
|
||||||
@@ -443,7 +425,6 @@ const TableBodyRowGroup = ({
|
|||||||
{...rowGroupProps}
|
{...rowGroupProps}
|
||||||
withGradientCardRows={withGradientCardRows}
|
withGradientCardRows={withGradientCardRows}
|
||||||
withInnerBorders={withInnerBorders}
|
withInnerBorders={withInnerBorders}
|
||||||
withOuterBorder={withOuterBorder}
|
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Styled.Tbody>
|
</Styled.Tbody>
|
||||||
@@ -510,23 +491,6 @@ const TableColumnHeader = <TableRowData extends object>({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ViewMoreRow = ({ colSpan, onClick }: { colSpan: number; onClick: () => void }) => {
|
|
||||||
const stringGetter = useStringGetter();
|
|
||||||
return (
|
|
||||||
<Styled.Tr key="viewmore">
|
|
||||||
<Styled.Td
|
|
||||||
colSpan={colSpan}
|
|
||||||
onMouseDown={(e: MouseEvent) => e.preventDefault()}
|
|
||||||
onPointerDown={(e: MouseEvent) => e.preventDefault()}
|
|
||||||
>
|
|
||||||
<Styled.ViewMoreButton slotRight={<CaretIcon />} onClick={onClick}>
|
|
||||||
{stringGetter({ key: STRING_KEYS.VIEW_MORE })}
|
|
||||||
</Styled.ViewMoreButton>
|
|
||||||
</Styled.Td>
|
|
||||||
</Styled.Tr>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const TableRow = <TableRowData extends object>({
|
export const TableRow = <TableRowData extends object>({
|
||||||
item,
|
item,
|
||||||
children,
|
children,
|
||||||
@@ -698,7 +662,7 @@ Styled.Empty = styled.div<{ withOuterBorder: boolean }>`
|
|||||||
|
|
||||||
justify-items: center;
|
justify-items: center;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
padding: 4rem;
|
padding: 2rem;
|
||||||
gap: 0.75em;
|
gap: 0.75em;
|
||||||
|
|
||||||
color: var(--color-text-0);
|
color: var(--color-text-0);
|
||||||
@@ -867,7 +831,6 @@ Styled.SortArrow = styled.span<{ sortDirection: 'ascending' | 'descending' }>`
|
|||||||
Styled.Thead = styled.thead<StyleProps>`
|
Styled.Thead = styled.thead<StyleProps>`
|
||||||
${layoutMixins.stickyHeader}
|
${layoutMixins.stickyHeader}
|
||||||
scroll-snap-align: none;
|
scroll-snap-align: none;
|
||||||
font: var(--font-mini-book);
|
|
||||||
|
|
||||||
> * {
|
> * {
|
||||||
height: var(--stickyArea-topHeight);
|
height: var(--stickyArea-topHeight);
|
||||||
@@ -876,17 +839,18 @@ Styled.Thead = styled.thead<StyleProps>`
|
|||||||
color: var(--tableHeader-textColor);
|
color: var(--tableHeader-textColor);
|
||||||
background-color: var(--tableHeader-backgroundColor);
|
background-color: var(--tableHeader-backgroundColor);
|
||||||
|
|
||||||
${({ withInnerBorders, withGradientCardRows }) =>
|
@media ${breakpoints.notTablet} {
|
||||||
withInnerBorders &&
|
${({ withInnerBorders, withGradientCardRows }) =>
|
||||||
!withGradientCardRows &&
|
withInnerBorders &&
|
||||||
css`
|
!withGradientCardRows &&
|
||||||
${layoutMixins.withInnerHorizontalBorders}
|
css`
|
||||||
`}
|
${layoutMixins.withInnerHorizontalBorders}
|
||||||
|
`}
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.Tbody = styled.tbody<StyleProps>`
|
Styled.Tbody = styled.tbody<StyleProps>`
|
||||||
${layoutMixins.stickyArea2}
|
${layoutMixins.stickyArea2}
|
||||||
font: var(--font-small-book);
|
|
||||||
|
|
||||||
// If <table> height is fixed with not enough rows to overflow, vertically center the rows
|
// If <table> height is fixed with not enough rows to overflow, vertically center the rows
|
||||||
&:before,
|
&:before,
|
||||||
@@ -905,22 +869,6 @@ Styled.Tbody = styled.tbody<StyleProps>`
|
|||||||
--stickyArea2-paddingBottom: var(--border-width);
|
--stickyArea2-paddingBottom: var(--border-width);
|
||||||
--stickyArea2-paddingLeft: var(--border-width);
|
--stickyArea2-paddingLeft: var(--border-width);
|
||||||
--stickyArea2-paddingRight: var(--border-width);
|
--stickyArea2-paddingRight: var(--border-width);
|
||||||
|
|
||||||
tr:first-of-type {
|
|
||||||
box-shadow: 0 calc(var(--border-width)) 0 0 var(--border-color);
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
|
|
||||||
${({ withOuterBorder }) =>
|
|
||||||
withOuterBorder &&
|
|
||||||
css`
|
|
||||||
tr:last-of-type:not(:only-of-type) {
|
|
||||||
box-shadow: 0 calc(-1 * var(--border-width)) 0 0 var(--border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
tr:first-of-type {
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
`}
|
`}
|
||||||
|
|
||||||
${({ withGradientCardRows }) =>
|
${({ withGradientCardRows }) =>
|
||||||
@@ -972,15 +920,3 @@ Styled.Row = styled.div`
|
|||||||
${layoutMixins.inlineRow}
|
${layoutMixins.inlineRow}
|
||||||
padding: var(--tableCell-padding);
|
padding: var(--tableCell-padding);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.ViewMoreButton = styled(Button)`
|
|
||||||
--button-backgroundColor: var(--color-layer-2);
|
|
||||||
--button-textColor: var(--color-text-1);
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
svg {
|
|
||||||
width: 0.675rem;
|
|
||||||
margin-left: 0.5ch;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ export type TabItem<TabItemsValue> = {
|
|||||||
content?: React.ReactNode;
|
content?: React.ReactNode;
|
||||||
subitems?: TabItem<TabItemsValue>[];
|
subitems?: TabItem<TabItemsValue>[];
|
||||||
customTrigger?: ReactNode;
|
customTrigger?: ReactNode;
|
||||||
asChild?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type ElementProps<TabItemsValue> = {
|
type ElementProps<TabItemsValue> = {
|
||||||
@@ -42,8 +41,6 @@ type StyleProps = {
|
|||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TabsProps<TabItemsValue> = ElementProps<TabItemsValue> & StyleProps;
|
|
||||||
|
|
||||||
export const Tabs = <TabItemsValue extends string>({
|
export const Tabs = <TabItemsValue extends string>({
|
||||||
defaultValue,
|
defaultValue,
|
||||||
value,
|
value,
|
||||||
@@ -110,10 +107,9 @@ export const Tabs = <TabItemsValue extends string>({
|
|||||||
sharedContent
|
sharedContent
|
||||||
) : (
|
) : (
|
||||||
<Styled.Stack>
|
<Styled.Stack>
|
||||||
{items.map(({ asChild, value, content, forceMount }) => (
|
{items.map(({ value, content, forceMount }) => (
|
||||||
<Styled.Content
|
<Styled.Content
|
||||||
key={value}
|
key={value}
|
||||||
asChild={asChild}
|
|
||||||
value={value}
|
value={value}
|
||||||
forceMount={forceMount}
|
forceMount={forceMount}
|
||||||
$hide={forceMount && currentItem?.value !== value}
|
$hide={forceMount && currentItem?.value !== value}
|
||||||
@@ -246,6 +242,7 @@ Styled.Trigger = styled(Trigger)<{ $withBorders?: boolean }>`
|
|||||||
|
|
||||||
Styled.Stack = styled.div`
|
Styled.Stack = styled.div`
|
||||||
${layoutMixins.stack}
|
${layoutMixins.stack}
|
||||||
|
${layoutMixins.perspectiveArea}
|
||||||
|
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,19 +1,24 @@
|
|||||||
import { type MouseEvent, useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import styled, { keyframes } from 'styled-components';
|
|
||||||
import { Root, Action, Close } from '@radix-ui/react-toast';
|
import { Root, Title, Description, Action, Close } from '@radix-ui/react-toast';
|
||||||
|
|
||||||
import { ButtonShape, ButtonSize } from '@/constants/buttons';
|
import { ButtonShape, ButtonSize } from '@/constants/buttons';
|
||||||
import { popoverMixins } from '@/styles/popoverMixins';
|
|
||||||
import { breakpoints } from '@/styles';
|
|
||||||
|
|
||||||
import { Notification, type NotificationProps } from '@/components/Notification';
|
|
||||||
|
|
||||||
import { IconButton } from './IconButton';
|
import { IconButton } from './IconButton';
|
||||||
import { IconName } from './Icon';
|
import { CloseIcon } from '@/icons';
|
||||||
|
|
||||||
|
import styled, { keyframes } from 'styled-components';
|
||||||
|
import { popoverMixins } from '@/styles/popoverMixins';
|
||||||
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
|
||||||
type ElementProps = {
|
type ElementProps = {
|
||||||
isOpen?: boolean;
|
isOpen?: boolean;
|
||||||
setIsOpen?: (isOpen: boolean, isClosedFromTimeout?: boolean) => void;
|
setIsOpen?: (isOpen: boolean, isClosedFromTimeout?: boolean) => void;
|
||||||
|
slotIcon?: React.ReactNode;
|
||||||
|
slotTitle?: React.ReactNode;
|
||||||
|
slotDescription?: React.ReactNode;
|
||||||
|
slotCustomContent?: React.ReactNode;
|
||||||
|
slotAction?: React.ReactNode;
|
||||||
actionDescription?: string;
|
actionDescription?: string;
|
||||||
actionAltText?: string;
|
actionAltText?: string;
|
||||||
sensitivity?: 'foreground' | 'background';
|
sensitivity?: 'foreground' | 'background';
|
||||||
@@ -21,22 +26,13 @@ type ElementProps = {
|
|||||||
lastUpdated?: number;
|
lastUpdated?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StyleProps = {
|
type StyleProps = {};
|
||||||
className?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ToastProps = NotificationProps & ElementProps & StyleProps;
|
|
||||||
|
|
||||||
export const Toast = ({
|
export const Toast = ({
|
||||||
className,
|
|
||||||
isOpen = true,
|
isOpen = true,
|
||||||
notification,
|
|
||||||
onClick,
|
|
||||||
setIsOpen,
|
setIsOpen,
|
||||||
slotIcon,
|
slotIcon,
|
||||||
slotTitle,
|
slotTitle,
|
||||||
slotTitleLeft,
|
|
||||||
slotTitleRight,
|
|
||||||
slotDescription,
|
slotDescription,
|
||||||
slotCustomContent,
|
slotCustomContent,
|
||||||
slotAction,
|
slotAction,
|
||||||
@@ -45,7 +41,7 @@ export const Toast = ({
|
|||||||
sensitivity = 'background',
|
sensitivity = 'background',
|
||||||
duration = Infinity,
|
duration = Infinity,
|
||||||
lastUpdated,
|
lastUpdated,
|
||||||
}: ToastProps) => {
|
}: ElementProps & StyleProps) => {
|
||||||
// Timeout
|
// Timeout
|
||||||
const timeout = useRef<number>();
|
const timeout = useRef<number>();
|
||||||
const [isPaused, setIsPaused] = useState(false);
|
const [isPaused, setIsPaused] = useState(false);
|
||||||
@@ -62,45 +58,37 @@ export const Toast = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<$Root
|
<$Root
|
||||||
className={className}
|
|
||||||
type={sensitivity}
|
type={sensitivity}
|
||||||
duration={Infinity}
|
duration={Infinity}
|
||||||
open={isOpen}
|
open={isOpen}
|
||||||
onOpenChange={setIsOpen}
|
onOpenChange={setIsOpen}
|
||||||
onPause={() => setIsPaused(true)}
|
onPause={() => setIsPaused(true)}
|
||||||
onResume={() => setIsPaused(false)}
|
onResume={() => setIsPaused(false)}
|
||||||
onClick={() => {
|
|
||||||
setIsOpen?.(false, false);
|
|
||||||
onClick?.();
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Close asChild>
|
<div>
|
||||||
<$CloseButton
|
<$Container>
|
||||||
iconName={IconName.Close}
|
<$Header>
|
||||||
shape={ButtonShape.Circle}
|
{slotIcon && <$Icon>{slotIcon}</$Icon>}
|
||||||
size={ButtonSize.XSmall}
|
|
||||||
onClick={(e: MouseEvent) => e.stopPropagation()}
|
|
||||||
/>
|
|
||||||
</Close>
|
|
||||||
|
|
||||||
{slotCustomContent ?? (
|
<Close asChild>
|
||||||
<$Notification
|
<$CloseButton
|
||||||
isToast
|
iconComponent={CloseIcon}
|
||||||
notification={notification}
|
shape={ButtonShape.Square}
|
||||||
slotIcon={slotIcon}
|
size={ButtonSize.XSmall}
|
||||||
slotTitle={slotTitle}
|
/>
|
||||||
slotTitleLeft={slotTitleLeft}
|
</Close>
|
||||||
slotTitleRight={slotTitleRight}
|
|
||||||
slotDescription={slotDescription}
|
<$Title>{slotTitle}</$Title>
|
||||||
slotAction={
|
</$Header>
|
||||||
actionDescription && (
|
{!slotCustomContent && <$Description>{slotDescription}</$Description>}
|
||||||
<$Action asChild altText={actionAltText}>
|
{slotCustomContent}
|
||||||
{slotAction}
|
{actionDescription && (
|
||||||
</$Action>
|
<$Action asChild altText={actionAltText}>
|
||||||
)
|
{slotAction}
|
||||||
}
|
</$Action>
|
||||||
/>
|
)}
|
||||||
)}
|
</$Container>
|
||||||
|
</div>
|
||||||
</$Root>
|
</$Root>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -126,7 +114,7 @@ const $Root = styled(Root)`
|
|||||||
translate: var(--x) var(--y);
|
translate: var(--x) var(--y);
|
||||||
will-change: translate, margin-top;
|
will-change: translate, margin-top;
|
||||||
|
|
||||||
/* margin-bottom: var(--toasts-gap); */
|
margin-bottom: var(--toasts-gap);
|
||||||
|
|
||||||
&[data-swipe-direction='right'] {
|
&[data-swipe-direction='right'] {
|
||||||
&[data-state='open'] {
|
&[data-state='open'] {
|
||||||
@@ -175,10 +163,6 @@ const $Root = styled(Root)`
|
|||||||
cursor: n-resize;
|
cursor: n-resize;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus:not([data-swipe='end']) & {
|
|
||||||
outline: var(--color-accent) 1px solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-swipe='move'] {
|
&[data-swipe='move'] {
|
||||||
transition-property: opacity;
|
transition-property: opacity;
|
||||||
opacity: 0.98;
|
opacity: 0.98;
|
||||||
@@ -187,10 +171,6 @@ const $Root = styled(Root)`
|
|||||||
* {
|
* {
|
||||||
cursor: inherit;
|
cursor: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > * {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-state='closed'],
|
&[data-state='closed'],
|
||||||
@@ -218,48 +198,69 @@ const $Root = styled(Root)`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const $Notification = styled(Notification)`
|
const $Container = styled.div`
|
||||||
// Params
|
// Params
|
||||||
--toast-icon-size: 1.75em;
|
--toast-icon-size: 1.75em;
|
||||||
|
|
||||||
// Rules
|
// Rules
|
||||||
${popoverMixins.popover}
|
${popoverMixins.popover}
|
||||||
overflow: visible;
|
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
box-shadow: 0 0 0 var(--border-width) var(--color-border),
|
box-shadow: 0 0 0 var(--border-width) var(--color-border),
|
||||||
// border
|
// border
|
||||||
0 0 0.5rem 0.1rem var(--color-layer-2); // shadow
|
0 0 0.5rem 0.1rem var(--color-layer-2); // shadow
|
||||||
|
|
||||||
|
${$Root}:focus:not([data-swipe='end']) & {
|
||||||
|
outline: var(--color-accent) 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
> * {
|
> * {
|
||||||
transition: opacity 0.2s;
|
transition: opacity 0.2s;
|
||||||
}
|
}
|
||||||
|
${$Root}[data-swipe='move'] & > * {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const $Header = styled.header`
|
||||||
|
display: block;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const $Icon = styled.div`
|
||||||
|
${layoutMixins.row}
|
||||||
|
|
||||||
|
float: left;
|
||||||
|
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
|
||||||
|
margin-right: 0.4em;
|
||||||
|
|
||||||
|
line-height: 1;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const $CloseButton = styled(IconButton)`
|
const $CloseButton = styled(IconButton)`
|
||||||
--button-textColor: var(--color-text-0);
|
--button-textColor: var(--color-text-0);
|
||||||
--button-border: none;
|
--button-border: none;
|
||||||
--button-icon-size: 0.85em;
|
--button-icon-size: 0.85em;
|
||||||
display: none;
|
|
||||||
|
|
||||||
/* float: right; */
|
float: right;
|
||||||
margin: -0.42rem -0.42rem -0.42rem 0.42rem;
|
margin: -0.42rem -0.42rem -0.42rem 0.42rem;
|
||||||
|
`;
|
||||||
|
|
||||||
// Absolute
|
const $Title = styled(Title)`
|
||||||
position: absolute;
|
flex: 1;
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
|
|
||||||
border: solid var(--border-width) var(--color-border);
|
font: var(--font-base-medium);
|
||||||
|
color: var(--color-text-2);
|
||||||
|
|
||||||
${$Root}:hover & {
|
overflow: hidden;
|
||||||
display: block;
|
text-overflow: ellipsis;
|
||||||
z-index: 2;
|
`;
|
||||||
}
|
|
||||||
|
|
||||||
@media ${breakpoints.mobile} {
|
const $Description = styled(Description)`
|
||||||
display: block;
|
margin-top: 0.5rem;
|
||||||
z-index: 2;
|
color: var(--color-text-0);
|
||||||
}
|
font: var(--font-small-book);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const $Action = styled(Action)`
|
const $Action = styled(Action)`
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
import { Provider, Viewport } from '@radix-ui/react-toast';
|
import { Provider, Viewport } from '@radix-ui/react-toast';
|
||||||
|
|
||||||
|
import styled from 'styled-components';
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
|
||||||
type ElementProps = {
|
type ElementProps = {
|
||||||
@@ -13,9 +12,7 @@ type StyleProps = {
|
|||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ToastAreaProps = ElementProps & StyleProps;
|
export const ToastArea = ({ swipeDirection, children, className }: ElementProps & StyleProps) => (
|
||||||
|
|
||||||
export const ToastArea = ({ swipeDirection, children, className }: ToastAreaProps) => (
|
|
||||||
<$ToastArea className={className}>
|
<$ToastArea className={className}>
|
||||||
<Provider swipeDirection={swipeDirection}>
|
<Provider swipeDirection={swipeDirection}>
|
||||||
{children}
|
{children}
|
||||||
@@ -29,6 +26,7 @@ const $ToastArea = styled.aside`
|
|||||||
--toasts-gap: 0.5rem;
|
--toasts-gap: 0.5rem;
|
||||||
|
|
||||||
// Rules
|
// Rules
|
||||||
|
${layoutMixins.scrollArea}
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
@@ -36,7 +34,6 @@ const $ToastArea = styled.aside`
|
|||||||
> * {
|
> * {
|
||||||
display: contents;
|
display: contents;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
position: relative;
|
|
||||||
|
|
||||||
> ol {
|
> ol {
|
||||||
/* display: block; */
|
/* display: block; */
|
||||||
@@ -46,7 +43,6 @@ const $ToastArea = styled.aside`
|
|||||||
// Desired display order: latest to earliest (¯\_(ツ)_/¯)
|
// Desired display order: latest to earliest (¯\_(ツ)_/¯)
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column-reverse;
|
flex-direction: column-reverse;
|
||||||
position: relative;
|
|
||||||
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
|
|||||||
@@ -52,19 +52,15 @@ const Styled: Record<string, AnyStyledComponent> = {};
|
|||||||
Styled.BaseButton = styled(BaseButton)`
|
Styled.BaseButton = styled(BaseButton)`
|
||||||
--button-toggle-off-backgroundColor: var(--color-layer-3);
|
--button-toggle-off-backgroundColor: var(--color-layer-3);
|
||||||
--button-toggle-off-textColor: var(--color-text-0);
|
--button-toggle-off-textColor: var(--color-text-0);
|
||||||
--button-toggle-off-border: solid var(--border-width) var(--border-color);
|
|
||||||
--button-toggle-on-backgroundColor: var(--color-layer-1);
|
--button-toggle-on-backgroundColor: var(--color-layer-1);
|
||||||
--button-toggle-on-textColor: var(--color-text-2);
|
--button-toggle-on-textColor: var(--color-text-2);
|
||||||
--button-toggle-on-border: solid var(--border-width) var(--border-color);
|
|
||||||
|
|
||||||
--button-backgroundColor: var(--button-toggle-off-backgroundColor);
|
--button-backgroundColor: var(--button-toggle-off-backgroundColor);
|
||||||
--button-textColor: var(--button-toggle-off-textColor);
|
--button-textColor: var(--button-toggle-off-textColor);
|
||||||
--button-border: var(--button-toggle-off-border);
|
|
||||||
|
|
||||||
&[data-state='on'],
|
&[data-state='on'],
|
||||||
&[data-state='active'] {
|
&[data-state='active'] {
|
||||||
--button-backgroundColor: var(--button-toggle-on-backgroundColor);
|
--button-backgroundColor: var(--button-toggle-on-backgroundColor);
|
||||||
--button-textColor: var(--button-toggle-on-textColor);
|
--button-textColor: var(--button-toggle-on-textColor);
|
||||||
--button-border: var(--button-toggle-on-border);
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -7,15 +7,13 @@ import { ButtonShape, ButtonSize } from '@/constants/buttons';
|
|||||||
import { useBreakpoints } from '@/hooks';
|
import { useBreakpoints } from '@/hooks';
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
|
||||||
import { type BaseButtonProps } from '@/components/BaseButton';
|
import { type BaseButtonProps } from '@/components/BaseButton';
|
||||||
import { ToggleButton } from '@/components/ToggleButton';
|
import { ToggleButton } from '@/components/ToggleButton';
|
||||||
|
|
||||||
type ElementProps<MenuItemValue extends string> = {
|
type ElementProps<MenuItemValue extends string> = {
|
||||||
items: MenuItem<MenuItemValue>[];
|
items: MenuItem<MenuItemValue>[];
|
||||||
value: MenuItemValue;
|
value: MenuItemValue;
|
||||||
onValueChange: (value: any) => void;
|
onValueChange: (value: MenuItemValue) => void;
|
||||||
onInteraction?: () => void;
|
|
||||||
ensureSelected?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type StyleProps = {
|
type StyleProps = {
|
||||||
@@ -27,14 +25,10 @@ export const ToggleGroup = forwardRef(
|
|||||||
{
|
{
|
||||||
items,
|
items,
|
||||||
value,
|
value,
|
||||||
ensureSelected = true,
|
|
||||||
onValueChange,
|
onValueChange,
|
||||||
onInteraction,
|
|
||||||
|
|
||||||
className,
|
className,
|
||||||
size,
|
size,
|
||||||
shape = ButtonShape.Pill,
|
shape = ButtonShape.Pill,
|
||||||
|
|
||||||
...buttonProps
|
...buttonProps
|
||||||
}: ElementProps<MenuItemValue> & StyleProps & BaseButtonProps,
|
}: ElementProps<MenuItemValue> & StyleProps & BaseButtonProps,
|
||||||
ref: Ref<HTMLDivElement>
|
ref: Ref<HTMLDivElement>
|
||||||
@@ -46,17 +40,12 @@ export const ToggleGroup = forwardRef(
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
type="single"
|
type="single"
|
||||||
value={value}
|
value={value}
|
||||||
onValueChange={(newValue: MenuItemValue) => {
|
onValueChange={onValueChange}
|
||||||
if ((ensureSelected && newValue) || !ensureSelected) {
|
|
||||||
onValueChange(newValue);
|
|
||||||
}
|
|
||||||
onInteraction?.();
|
|
||||||
}}
|
|
||||||
className={className}
|
className={className}
|
||||||
loop
|
loop
|
||||||
>
|
>
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
<Item key={item.value} value={item.value} asChild>
|
<Item key={item.value} value={item.value} aria-label={item.label} asChild>
|
||||||
<ToggleButton
|
<ToggleButton
|
||||||
size={size ? size : isTablet ? ButtonSize.Small : ButtonSize.XSmall}
|
size={size ? size : isTablet ? ButtonSize.Small : ButtonSize.XSmall}
|
||||||
shape={shape}
|
shape={shape}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import styled, { type AnyStyledComponent } from 'styled-components';
|
|||||||
|
|
||||||
import { type RiskLevels } from '@/constants/abacus';
|
import { type RiskLevels } from '@/constants/abacus';
|
||||||
|
|
||||||
import { abacusHelper } from '@/lib/abacus';
|
import { UsageColorFromRiskLevel } from '@/styles/colors';
|
||||||
import { UsageColorFromRiskLevel } from '@/lib/styles';
|
|
||||||
|
|
||||||
|
import { abacusHelper } from '@/lib/abacus';
|
||||||
type ElementProps = {
|
type ElementProps = {
|
||||||
value: number;
|
value: number;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,13 +2,11 @@ import type { ReactNode } from 'react';
|
|||||||
import styled, { AnyStyledComponent } from 'styled-components';
|
import styled, { AnyStyledComponent } from 'styled-components';
|
||||||
import { Content, Portal, Provider, Root, Trigger, Arrow } from '@radix-ui/react-tooltip';
|
import { Content, Portal, Provider, Root, Trigger, Arrow } from '@radix-ui/react-tooltip';
|
||||||
|
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
|
||||||
import { tooltipStrings } from '@/constants/tooltips';
|
import { tooltipStrings } from '@/constants/tooltips';
|
||||||
|
|
||||||
import { useStringGetter, useURLConfigs } from '@/hooks';
|
import { useStringGetter } from '@/hooks';
|
||||||
|
|
||||||
import { Icon, IconName } from '@/components/Icon';
|
import { Icon, IconName } from '@/components/Icon';
|
||||||
import { Link } from '@/components/Link';
|
|
||||||
|
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
import { popoverMixins } from '@/styles/popoverMixins';
|
import { popoverMixins } from '@/styles/popoverMixins';
|
||||||
@@ -40,24 +38,20 @@ export const WithTooltip = ({
|
|||||||
slotTooltip,
|
slotTooltip,
|
||||||
}: ElementProps & StyleProps) => {
|
}: ElementProps & StyleProps) => {
|
||||||
const stringGetter = useStringGetter();
|
const stringGetter = useStringGetter();
|
||||||
const urlConfigs = useURLConfigs();
|
|
||||||
|
|
||||||
const getTooltipStrings = tooltip && tooltipStrings[tooltip];
|
const getTooltipStrings = tooltip && tooltipStrings[tooltip];
|
||||||
if (!getTooltipStrings && !tooltipString && !slotTooltip) return <>{children}</>;
|
if (!getTooltipStrings && !tooltipString && !slotTooltip) return <>{children}</>;
|
||||||
|
|
||||||
let tooltipTitle;
|
let tooltipTitle;
|
||||||
let tooltipBody;
|
let tooltipBody;
|
||||||
let tooltipLearnMore;
|
|
||||||
|
|
||||||
if (getTooltipStrings) {
|
if (getTooltipStrings) {
|
||||||
const { title, body, learnMoreLink } = getTooltipStrings({
|
const { title, body } = getTooltipStrings({
|
||||||
stringGetter,
|
stringGetter,
|
||||||
stringParams,
|
stringParams,
|
||||||
urlConfigs,
|
|
||||||
});
|
});
|
||||||
tooltipTitle = title;
|
tooltipTitle = title;
|
||||||
tooltipBody = body;
|
tooltipBody = body;
|
||||||
tooltipLearnMore = learnMoreLink;
|
|
||||||
} else {
|
} else {
|
||||||
tooltipBody = tooltipString;
|
tooltipBody = tooltipString;
|
||||||
}
|
}
|
||||||
@@ -78,13 +72,6 @@ export const WithTooltip = ({
|
|||||||
<dl>
|
<dl>
|
||||||
{tooltipTitle && <dt>{tooltipTitle}</dt>}
|
{tooltipTitle && <dt>{tooltipTitle}</dt>}
|
||||||
{tooltipBody && <dd>{tooltipBody}</dd>}
|
{tooltipBody && <dd>{tooltipBody}</dd>}
|
||||||
{tooltipLearnMore && (
|
|
||||||
<dd>
|
|
||||||
<Styled.LearnMore href={tooltipLearnMore}>
|
|
||||||
{stringGetter({ key: STRING_KEYS.LEARN_MORE })} →
|
|
||||||
</Styled.LearnMore>
|
|
||||||
</dd>
|
|
||||||
)}
|
|
||||||
<Styled.Arrow />
|
<Styled.Arrow />
|
||||||
</dl>
|
</dl>
|
||||||
)}
|
)}
|
||||||
@@ -110,7 +97,12 @@ Styled.Abbr = styled.abbr`
|
|||||||
|
|
||||||
Styled.Content = styled(Content)`
|
Styled.Content = styled(Content)`
|
||||||
--tooltip-backgroundColor: var(--color-layer-4);
|
--tooltip-backgroundColor: var(--color-layer-4);
|
||||||
--tooltip-backgroundColor: ${({ theme }) => theme.tooltipBackground};
|
--tooltip-backgroundColor: hsl(
|
||||||
|
var(--layer-base-hue),
|
||||||
|
var(--layer-base-saturation),
|
||||||
|
calc(var(--layer-base-lightness) + 4%),
|
||||||
|
0.66
|
||||||
|
);
|
||||||
|
|
||||||
${popoverMixins.popover}
|
${popoverMixins.popover}
|
||||||
--popover-backgroundColor: var(--tooltip-backgroundColor);
|
--popover-backgroundColor: var(--tooltip-backgroundColor);
|
||||||
@@ -150,7 +142,3 @@ Styled.Arrow = styled(Arrow)`
|
|||||||
Styled.Icon = styled(Icon)`
|
Styled.Icon = styled(Icon)`
|
||||||
color: var(--color-text-0);
|
color: var(--color-text-0);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.LearnMore = styled(Link)`
|
|
||||||
--link-color: var(--color-accent);
|
|
||||||
`;
|
|
||||||
|
|||||||
@@ -27,7 +27,12 @@ Styled.TooltipContent = styled.aside<{ accentColor?: string }>`
|
|||||||
|
|
||||||
${popoverMixins.popover}
|
${popoverMixins.popover}
|
||||||
--popover-radius: 0.5rem;
|
--popover-radius: 0.5rem;
|
||||||
--popover-background-color: ${({ theme }) => theme.popoverBackground};
|
--popover-background-color: hsl(
|
||||||
|
var(--layer-base-hue),
|
||||||
|
var(--layer-base-saturation),
|
||||||
|
calc(var(--layer-base-lightness)),
|
||||||
|
0.9
|
||||||
|
);
|
||||||
--popover-backdrop-filter: saturate(120%) blur(12px);
|
--popover-backdrop-filter: saturate(120%) blur(12px);
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|||||||
@@ -1,33 +1,26 @@
|
|||||||
import React, { PropsWithChildren, useContext, useState } from 'react';
|
import React, { useContext, useState } from 'react';
|
||||||
|
|
||||||
import { Point } from '@visx/point';
|
import { Point } from '@visx/point';
|
||||||
import { localPoint } from '@visx/event';
|
import { localPoint } from '@visx/event';
|
||||||
import { XYChart, DataContext, type EventHandlerParams } from '@visx/xychart';
|
import { XYChart, DataContext } from '@visx/xychart';
|
||||||
import { getScaleBandwidth } from '@/components/visx/getScaleBandwidth';
|
import { getScaleBandwidth } from '@/components/visx/getScaleBandwidth';
|
||||||
|
|
||||||
export const XYChartWithPointerEvents = ({
|
export const XYChartWithPointerEvents = ({
|
||||||
onPointerMove,
|
onPointerMove, onPointerUp, onPointerPressedChange, ...props
|
||||||
onPointerUp,
|
|
||||||
onPointerPressedChange,
|
|
||||||
...props
|
|
||||||
}: {
|
}: {
|
||||||
onPointerMove?: (point: Point | EventHandlerParams<object>) => void;
|
onPointerMove?: (point: Point) => void;
|
||||||
onPointerUp?: (point: Point | EventHandlerParams<object>) => void;
|
onPointerUp?: (point: Point) => void;
|
||||||
onPointerPressedChange?: (isPointerPressed: boolean) => void;
|
onPointerPressedChange?: (isPointerPressed: boolean) => void;
|
||||||
} & PropsWithChildren<Parameters<typeof XYChart>[0]>) => {
|
} & React.PropsWithChildren<Parameters<typeof XYChart>>) => {
|
||||||
const { xScale, yScale } = useContext(DataContext);
|
const { xScale, yScale } = useContext(DataContext);
|
||||||
|
|
||||||
const [lastPointerMoveEvent, setLastPointerMoveEvent] = useState<React.PointerEvent>();
|
const [lastPointerMoveEvent, setLastPointerMoveEvent] = useState<React.PointerEvent>();
|
||||||
|
|
||||||
const pointerContainerPosition = lastPointerMoveEvent ? localPoint(lastPointerMoveEvent) : null;
|
const pointerContainerPosition = lastPointerMoveEvent ? localPoint(lastPointerMoveEvent) : null;
|
||||||
|
|
||||||
const pointerChartPosition =
|
const pointerChartPosition = xScale && yScale && pointerContainerPosition &&
|
||||||
xScale &&
|
|
||||||
yScale &&
|
|
||||||
pointerContainerPosition &&
|
|
||||||
new Point({
|
new Point({
|
||||||
// @ts-expect-error invert supposedly doesn't exist on AxisScale
|
|
||||||
x: xScale.invert(pointerContainerPosition?.x - getScaleBandwidth(xScale) / 2),
|
x: xScale.invert(pointerContainerPosition?.x - getScaleBandwidth(xScale) / 2),
|
||||||
// @ts-expect-error invert supposedly doesn't exist on AxisScale
|
|
||||||
y: yScale.invert(pointerContainerPosition?.y - getScaleBandwidth(yScale) / 2),
|
y: yScale.invert(pointerContainerPosition?.y - getScaleBandwidth(yScale) / 2),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -36,13 +29,15 @@ export const XYChartWithPointerEvents = ({
|
|||||||
{...props}
|
{...props}
|
||||||
onPointerMove={({ event }) => {
|
onPointerMove={({ event }) => {
|
||||||
setLastPointerMoveEvent(event as React.PointerEvent);
|
setLastPointerMoveEvent(event as React.PointerEvent);
|
||||||
if (pointerChartPosition) onPointerMove?.(pointerChartPosition);
|
if (pointerChartPosition)
|
||||||
|
onPointerMove?.(pointerChartPosition);
|
||||||
}}
|
}}
|
||||||
onPointerOut={() => setLastPointerMoveEvent(undefined)}
|
onPointerOut={() => setLastPointerMoveEvent(undefined)}
|
||||||
onPointerDown={() => onPointerPressedChange?.(true)}
|
onPointerDown={() => onPointerPressedChange?.(true)}
|
||||||
onPointerUp={() => {
|
onPointerUp={() => {
|
||||||
onPointerPressedChange?.(false);
|
onPointerPressedChange?.(false);
|
||||||
if (pointerChartPosition) onPointerUp?.(pointerChartPosition);
|
if (pointerChartPosition)
|
||||||
|
onPointerUp?.(pointerChartPosition);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
|
|||||||
@@ -124,8 +124,6 @@ export type SubaccountPosition = Abacus.exchange.dydx.abacus.output.SubaccountPo
|
|||||||
export type SubaccountOrder = Abacus.exchange.dydx.abacus.output.SubaccountOrder;
|
export type SubaccountOrder = Abacus.exchange.dydx.abacus.output.SubaccountOrder;
|
||||||
export type OrderStatus = Abacus.exchange.dydx.abacus.output.input.OrderStatus;
|
export type OrderStatus = Abacus.exchange.dydx.abacus.output.input.OrderStatus;
|
||||||
export const AbacusOrderStatus = Abacus.exchange.dydx.abacus.output.input.OrderStatus;
|
export const AbacusOrderStatus = Abacus.exchange.dydx.abacus.output.input.OrderStatus;
|
||||||
const abacusOrderStatuses = [...AbacusOrderStatus.values()] as const;
|
|
||||||
export type AbacusOrderStatuses = (typeof abacusOrderStatuses)[number];
|
|
||||||
export type SubaccountFills = Abacus.exchange.dydx.abacus.output.SubaccountFill[];
|
export type SubaccountFills = Abacus.exchange.dydx.abacus.output.SubaccountFill[];
|
||||||
export type SubaccountFill = Abacus.exchange.dydx.abacus.output.SubaccountFill;
|
export type SubaccountFill = Abacus.exchange.dydx.abacus.output.SubaccountFill;
|
||||||
export type SubaccountFundingPayment = Abacus.exchange.dydx.abacus.output.SubaccountFundingPayment;
|
export type SubaccountFundingPayment = Abacus.exchange.dydx.abacus.output.SubaccountFundingPayment;
|
||||||
@@ -142,7 +140,7 @@ const historicalPnlPeriod = [...HistoricalPnlPeriod.values()] as const;
|
|||||||
export type HistoricalPnlPeriods = (typeof historicalPnlPeriod)[number];
|
export type HistoricalPnlPeriods = (typeof historicalPnlPeriod)[number];
|
||||||
|
|
||||||
// ------ Transfer Items ------ //
|
// ------ Transfer Items ------ //
|
||||||
export const TransferInputField = Abacus.exchange.dydx.abacus.state.model.TransferInputField;
|
export const TransferInputField = Abacus.exchange.dydx.abacus.state.modal.TransferInputField;
|
||||||
const transferInputFields = [...TransferInputField.values()] as const;
|
const transferInputFields = [...TransferInputField.values()] as const;
|
||||||
export type TransferInputFields = (typeof transferInputFields)[number];
|
export type TransferInputFields = (typeof transferInputFields)[number];
|
||||||
|
|
||||||
@@ -151,7 +149,7 @@ const transferTypes = [...TransferType.values()] as const;
|
|||||||
export type TransferTypes = (typeof transferTypes)[number];
|
export type TransferTypes = (typeof transferTypes)[number];
|
||||||
|
|
||||||
// ------ Trade Items ------ //
|
// ------ Trade Items ------ //
|
||||||
export const TradeInputField = Abacus.exchange.dydx.abacus.state.model.TradeInputField;
|
export const TradeInputField = Abacus.exchange.dydx.abacus.state.modal.TradeInputField;
|
||||||
const tradeInputFields = [...TradeInputField.values()] as const;
|
const tradeInputFields = [...TradeInputField.values()] as const;
|
||||||
export type TradeInputFields = (typeof tradeInputFields)[number];
|
export type TradeInputFields = (typeof tradeInputFields)[number];
|
||||||
|
|
||||||
@@ -162,7 +160,7 @@ export type TradeState<T> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const ClosePositionInputField =
|
export const ClosePositionInputField =
|
||||||
Abacus.exchange.dydx.abacus.state.model.ClosePositionInputField;
|
Abacus.exchange.dydx.abacus.state.modal.ClosePositionInputField;
|
||||||
|
|
||||||
const closePositionInputFields = [...ClosePositionInputField.values()] as const;
|
const closePositionInputFields = [...ClosePositionInputField.values()] as const;
|
||||||
export type ClosePositionInputFields = (typeof closePositionInputFields)[number];
|
export type ClosePositionInputFields = (typeof closePositionInputFields)[number];
|
||||||
@@ -211,13 +209,11 @@ type ReadonlyKeysOf<T> = {
|
|||||||
[K in keyof T]: IfEquals<{ [_ in K]: T[K] }, { readonly [_ in K]: T[K] }, K, never>;
|
[K in keyof T]: IfEquals<{ [_ in K]: T[K] }, { readonly [_ in K]: T[K] }, K, never>;
|
||||||
}[keyof T];
|
}[keyof T];
|
||||||
|
|
||||||
export type KotlinIrEnumValues<T> = Exclude<ReadonlyKeysOf<T>, 'Companion' | '$serializer'>;
|
type KotlinIrEnumValues<T> = Exclude<ReadonlyKeysOf<T>, 'Companion' | '$serializer'>;
|
||||||
|
|
||||||
export const ORDER_SIDES: Record<KotlinIrEnumValues<typeof AbacusOrderSide>, OrderSide> = {
|
export const ORDER_SIDES: Record<KotlinIrEnumValues<typeof AbacusOrderSide>, OrderSide> = {
|
||||||
[AbacusOrderSide.buy.name]: OrderSide.BUY,
|
[AbacusOrderSide.buy.name]: OrderSide.BUY,
|
||||||
[AbacusOrderSide.sell.name]: OrderSide.SELL,
|
[AbacusOrderSide.sell.name]: OrderSide.SELL,
|
||||||
[AbacusOrderSide.buy.rawValue]: OrderSide.BUY,
|
|
||||||
[AbacusOrderSide.sell.rawValue]: OrderSide.SELL,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const POSITION_SIDES: Record<KotlinIrEnumValues<typeof AbacusPositionSide>, PositionSide> = {
|
export const POSITION_SIDES: Record<KotlinIrEnumValues<typeof AbacusPositionSide>, PositionSide> = {
|
||||||
@@ -238,25 +234,12 @@ export const HISTORICAL_PNL_PERIODS: Record<
|
|||||||
|
|
||||||
export const ORDER_STATUS_STRINGS: Record<KotlinIrEnumValues<typeof AbacusOrderStatus>, string> = {
|
export const ORDER_STATUS_STRINGS: Record<KotlinIrEnumValues<typeof AbacusOrderStatus>, string> = {
|
||||||
[AbacusOrderStatus.open.name]: STRING_KEYS.OPEN_STATUS,
|
[AbacusOrderStatus.open.name]: STRING_KEYS.OPEN_STATUS,
|
||||||
[AbacusOrderStatus.open.rawValue]: STRING_KEYS.OPEN_STATUS,
|
|
||||||
|
|
||||||
[AbacusOrderStatus.partiallyFilled.name]: STRING_KEYS.PARTIALLY_FILLED,
|
[AbacusOrderStatus.partiallyFilled.name]: STRING_KEYS.PARTIALLY_FILLED,
|
||||||
[AbacusOrderStatus.partiallyFilled.rawValue]: STRING_KEYS.PARTIALLY_FILLED,
|
|
||||||
|
|
||||||
[AbacusOrderStatus.filled.name]: STRING_KEYS.ORDER_FILLED,
|
[AbacusOrderStatus.filled.name]: STRING_KEYS.ORDER_FILLED,
|
||||||
[AbacusOrderStatus.filled.rawValue]: STRING_KEYS.ORDER_FILLED,
|
|
||||||
|
|
||||||
[AbacusOrderStatus.cancelled.name]: STRING_KEYS.CANCELED,
|
[AbacusOrderStatus.cancelled.name]: STRING_KEYS.CANCELED,
|
||||||
[AbacusOrderStatus.cancelled.rawValue]: STRING_KEYS.CANCELED,
|
|
||||||
|
|
||||||
[AbacusOrderStatus.canceling.name]: STRING_KEYS.CANCELING,
|
[AbacusOrderStatus.canceling.name]: STRING_KEYS.CANCELING,
|
||||||
[AbacusOrderStatus.canceling.rawValue]: STRING_KEYS.CANCELING,
|
|
||||||
|
|
||||||
[AbacusOrderStatus.pending.name]: STRING_KEYS.PENDING,
|
[AbacusOrderStatus.pending.name]: STRING_KEYS.PENDING,
|
||||||
[AbacusOrderStatus.pending.rawValue]: STRING_KEYS.PENDING,
|
|
||||||
|
|
||||||
[AbacusOrderStatus.untriggered.name]: STRING_KEYS.UNTRIGGERED,
|
[AbacusOrderStatus.untriggered.name]: STRING_KEYS.UNTRIGGERED,
|
||||||
[AbacusOrderStatus.untriggered.rawValue]: STRING_KEYS.UNTRIGGERED,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TRADE_TYPES: Record<
|
export const TRADE_TYPES: Record<
|
||||||
@@ -264,40 +247,15 @@ export const TRADE_TYPES: Record<
|
|||||||
Nullable<TradeTypes>
|
Nullable<TradeTypes>
|
||||||
> = {
|
> = {
|
||||||
[AbacusOrderType.limit.name]: TradeTypes.LIMIT,
|
[AbacusOrderType.limit.name]: TradeTypes.LIMIT,
|
||||||
[AbacusOrderType.limit.rawValue]: TradeTypes.LIMIT,
|
|
||||||
|
|
||||||
[AbacusOrderType.market.name]: TradeTypes.MARKET,
|
[AbacusOrderType.market.name]: TradeTypes.MARKET,
|
||||||
[AbacusOrderType.market.rawValue]: TradeTypes.MARKET,
|
|
||||||
|
|
||||||
[AbacusOrderType.stopLimit.name]: TradeTypes.STOP_LIMIT,
|
[AbacusOrderType.stopLimit.name]: TradeTypes.STOP_LIMIT,
|
||||||
[AbacusOrderType.stopLimit.rawValue]: TradeTypes.STOP_LIMIT,
|
|
||||||
|
|
||||||
[AbacusOrderType.stopMarket.name]: TradeTypes.STOP_MARKET,
|
[AbacusOrderType.stopMarket.name]: TradeTypes.STOP_MARKET,
|
||||||
[AbacusOrderType.stopMarket.rawValue]: TradeTypes.STOP_MARKET,
|
|
||||||
|
|
||||||
[AbacusOrderType.takeProfitLimit.name]: TradeTypes.TAKE_PROFIT,
|
[AbacusOrderType.takeProfitLimit.name]: TradeTypes.TAKE_PROFIT,
|
||||||
[AbacusOrderType.takeProfitLimit.rawValue]: TradeTypes.TAKE_PROFIT,
|
|
||||||
|
|
||||||
[AbacusOrderType.takeProfitMarket.name]: TradeTypes.TAKE_PROFIT_MARKET,
|
[AbacusOrderType.takeProfitMarket.name]: TradeTypes.TAKE_PROFIT_MARKET,
|
||||||
[AbacusOrderType.takeProfitMarket.rawValue]: TradeTypes.TAKE_PROFIT_MARKET,
|
|
||||||
|
|
||||||
[AbacusOrderType.liquidated.name]: null,
|
[AbacusOrderType.liquidated.name]: null,
|
||||||
[AbacusOrderType.liquidated.rawValue]: null,
|
|
||||||
|
|
||||||
[AbacusOrderType.liquidation.name]: null,
|
[AbacusOrderType.liquidation.name]: null,
|
||||||
[AbacusOrderType.liquidation.rawValue]: null,
|
|
||||||
|
|
||||||
[AbacusOrderType.trailingStop.name]: null,
|
[AbacusOrderType.trailingStop.name]: null,
|
||||||
[AbacusOrderType.trailingStop.rawValue]: null,
|
|
||||||
|
|
||||||
[AbacusOrderType.offsetting.name]: null,
|
|
||||||
[AbacusOrderType.offsetting.rawValue]: null,
|
|
||||||
|
|
||||||
[AbacusOrderType.deleveraged.name]: null,
|
|
||||||
[AbacusOrderType.deleveraged.rawValue]: null,
|
|
||||||
|
|
||||||
[AbacusOrderType.finalSettlement.name]: null,
|
|
||||||
[AbacusOrderType.finalSettlement.rawValue]: null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Custom types involving Abacus
|
// Custom types involving Abacus
|
||||||
@@ -316,9 +274,3 @@ export type NetworkConfig = Partial<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type ConnectNetworkEvent = CustomEvent<Partial<NetworkConfig>>;
|
export type ConnectNetworkEvent = CustomEvent<Partial<NetworkConfig>>;
|
||||||
|
|
||||||
export type PerpetualMarketOrderbookLevel = OrderbookLine & {
|
|
||||||
side?: 'ask' | 'bid';
|
|
||||||
mine: number;
|
|
||||||
key: string;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -96,7 +96,6 @@ export type AnalyticsEventData<T extends AnalyticsEvent> =
|
|||||||
elapsedTime?: number;
|
elapsedTime?: number;
|
||||||
blockHeight?: number;
|
blockHeight?: number;
|
||||||
indexerBlockHeight?: number;
|
indexerBlockHeight?: number;
|
||||||
trailingBlocks?: number;
|
|
||||||
}
|
}
|
||||||
: // Navigation
|
: // Navigation
|
||||||
T extends AnalyticsEvent.NavigatePage
|
T extends AnalyticsEvent.NavigatePage
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
import { OrderSide } from '@dydxprotocol/v4-client-js';
|
|
||||||
import { FundingDirection } from './markets';
|
|
||||||
|
|
||||||
// ------ Depth Chart ------ //
|
|
||||||
export enum DepthChartSeries {
|
|
||||||
Asks = 'Asks',
|
|
||||||
Bids = 'Bids',
|
|
||||||
MidMarket = 'MidMarket',
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DepthChartDatum = {
|
|
||||||
size: number;
|
|
||||||
price: number;
|
|
||||||
depth: number;
|
|
||||||
seriesKey: DepthChartSeries;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type DepthChartPoint = {
|
|
||||||
side: OrderSide;
|
|
||||||
price: number;
|
|
||||||
size: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SERIES_KEY_FOR_ORDER_SIDE = {
|
|
||||||
[OrderSide.BUY]: DepthChartSeries.Bids,
|
|
||||||
[OrderSide.SELL]: DepthChartSeries.Asks,
|
|
||||||
};
|
|
||||||
|
|
||||||
// ------ Funding Chart ------ //
|
|
||||||
export enum FundingRateResolution {
|
|
||||||
OneHour = 'OneHour',
|
|
||||||
EightHour = 'EightHour',
|
|
||||||
Annualized = 'Annualized',
|
|
||||||
}
|
|
||||||
|
|
||||||
export type FundingChartDatum = {
|
|
||||||
time: number;
|
|
||||||
fundingRate: number;
|
|
||||||
direction: FundingDirection;
|
|
||||||
};
|
|
||||||
@@ -3,7 +3,6 @@ export enum DialogTypes {
|
|||||||
Deposit = 'Deposit',
|
Deposit = 'Deposit',
|
||||||
DisconnectWallet = 'DisconnectWallet',
|
DisconnectWallet = 'DisconnectWallet',
|
||||||
ExchangeOffline = 'ExchangeOffline',
|
ExchangeOffline = 'ExchangeOffline',
|
||||||
ExternalLink = 'ExternalLink',
|
|
||||||
FillDetails = 'FillDetails',
|
FillDetails = 'FillDetails',
|
||||||
Help = 'Help',
|
Help = 'Help',
|
||||||
ExternalNavKeplr = 'ExternalNavKeplr',
|
ExternalNavKeplr = 'ExternalNavKeplr',
|
||||||
@@ -11,14 +10,13 @@ export enum DialogTypes {
|
|||||||
MobileSignIn = 'MobileSignIn',
|
MobileSignIn = 'MobileSignIn',
|
||||||
Onboarding = 'Onboarding',
|
Onboarding = 'Onboarding',
|
||||||
OrderDetails = 'OrderDetails',
|
OrderDetails = 'OrderDetails',
|
||||||
Preferences = 'Preferences',
|
|
||||||
RateLimit = 'RateLimit',
|
RateLimit = 'RateLimit',
|
||||||
|
Receive = 'Receive',
|
||||||
RestrictedGeo = 'RestrictedGeo',
|
RestrictedGeo = 'RestrictedGeo',
|
||||||
RestrictedWallet = 'RestrictedWallet',
|
RestrictedWallet = 'RestrictedWallet',
|
||||||
Trade = 'Trade',
|
Trade = 'Trade',
|
||||||
Transfer = 'Transfer',
|
Transfer = 'Transfer',
|
||||||
Withdraw = 'Withdraw',
|
Withdraw = 'Withdraw',
|
||||||
ManageFunds = 'ManageFunds',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TradeBoxDialogTypes {
|
export enum TradeBoxDialogTypes {
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ export enum LocalStorageKey {
|
|||||||
PushNotificationsEnabled = 'dydx.PushNotificationsEnabled',
|
PushNotificationsEnabled = 'dydx.PushNotificationsEnabled',
|
||||||
PushNotificationsLastUpdated = 'dydx.PushNotificationsLastUpdated',
|
PushNotificationsLastUpdated = 'dydx.PushNotificationsLastUpdated',
|
||||||
TransferNotifications = 'dydx.TransferNotifications',
|
TransferNotifications = 'dydx.TransferNotifications',
|
||||||
NotificationPreferences = 'dydx.NotificationPreferences',
|
|
||||||
|
|
||||||
// UI State
|
// UI State
|
||||||
LastViewedMarket = 'dydx.LastViewedMarket',
|
LastViewedMarket = 'dydx.LastViewedMarket',
|
||||||
@@ -22,13 +21,9 @@ export enum LocalStorageKey {
|
|||||||
SelectedTheme = 'dydx.SelectedTheme',
|
SelectedTheme = 'dydx.SelectedTheme',
|
||||||
SelectedTradeLayout = 'dydx.SelectedTradeLayout',
|
SelectedTradeLayout = 'dydx.SelectedTradeLayout',
|
||||||
TradingViewChartConfig = 'dydx.TradingViewChartConfig',
|
TradingViewChartConfig = 'dydx.TradingViewChartConfig',
|
||||||
HasSeenLaunchIncentives = 'dydx.HasSeenLaunchIncentives',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LOCAL_STORAGE_VERSIONS = {
|
export const LOCAL_STORAGE_VERSIONS = {
|
||||||
[LocalStorageKey.EvmDerivedAddresses]: 'v2',
|
[LocalStorageKey.EvmDerivedAddresses]: 'v2',
|
||||||
[LocalStorageKey.NotificationPreferences]: 'v1',
|
|
||||||
[LocalStorageKey.TransferNotifications]: 'v1',
|
|
||||||
[LocalStorageKey.Notifications]: 'v1',
|
|
||||||
// TODO: version all localStorage keys
|
// TODO: version all localStorage keys
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,12 +6,8 @@ import {
|
|||||||
LOCALE_DATA,
|
LOCALE_DATA,
|
||||||
TOOLTIPS,
|
TOOLTIPS,
|
||||||
WARNINGS_STRING_KEYS,
|
WARNINGS_STRING_KEYS,
|
||||||
NOTIFICATIONS_STRING_KEYS,
|
|
||||||
NOTIFICATIONS,
|
|
||||||
} from '@dydxprotocol/v4-localization';
|
} from '@dydxprotocol/v4-localization';
|
||||||
|
|
||||||
import { type LinksConfigs } from '@/hooks/useURLConfigs';
|
|
||||||
|
|
||||||
export { TOOLTIP_STRING_KEYS } from '@dydxprotocol/v4-localization';
|
export { TOOLTIP_STRING_KEYS } from '@dydxprotocol/v4-localization';
|
||||||
|
|
||||||
export enum SupportedLocales {
|
export enum SupportedLocales {
|
||||||
@@ -29,7 +25,6 @@ export enum SupportedLocales {
|
|||||||
|
|
||||||
export const EN_LOCALE_DATA = {
|
export const EN_LOCALE_DATA = {
|
||||||
...LOCALE_DATA[SupportedLocales.EN],
|
...LOCALE_DATA[SupportedLocales.EN],
|
||||||
...NOTIFICATIONS[SupportedLocales.EN],
|
|
||||||
TOOLTIPS: TOOLTIPS[SupportedLocales.EN],
|
TOOLTIPS: TOOLTIPS[SupportedLocales.EN],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -37,13 +32,8 @@ export const STRING_KEYS = {
|
|||||||
...APP_STRING_KEYS,
|
...APP_STRING_KEYS,
|
||||||
...ERRORS_STRING_KEYS,
|
...ERRORS_STRING_KEYS,
|
||||||
...WARNINGS_STRING_KEYS,
|
...WARNINGS_STRING_KEYS,
|
||||||
...NOTIFICATIONS_STRING_KEYS,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const STRING_KEY_VALUES = Object.fromEntries(
|
|
||||||
Object.values(STRING_KEYS).map((key) => [key, key])
|
|
||||||
);
|
|
||||||
|
|
||||||
export type StringKey = keyof typeof STRING_KEYS;
|
export type StringKey = keyof typeof STRING_KEYS;
|
||||||
|
|
||||||
export type LocaleData = typeof EN_LOCALE_DATA;
|
export type LocaleData = typeof EN_LOCALE_DATA;
|
||||||
@@ -89,11 +79,9 @@ export type TooltipStrings = {
|
|||||||
[key: string]: ({
|
[key: string]: ({
|
||||||
stringGetter,
|
stringGetter,
|
||||||
stringParams,
|
stringParams,
|
||||||
urlConfigs,
|
|
||||||
}: {
|
}: {
|
||||||
stringGetter: StringGetterFunction;
|
stringGetter: StringGetterFunction;
|
||||||
stringParams?: any;
|
stringParams?: any;
|
||||||
urlConfigs?: LinksConfigs;
|
|
||||||
}) => {
|
}) => {
|
||||||
title: string;
|
title: string;
|
||||||
body: string;
|
body: string;
|
||||||
|
|||||||
@@ -25,5 +25,4 @@ export const DEFAULT_MARKETID = 'ETH-USD';
|
|||||||
export enum FundingDirection {
|
export enum FundingDirection {
|
||||||
ToShort = 'ToShort',
|
ToShort = 'ToShort',
|
||||||
ToLong = 'ToLong',
|
ToLong = 'ToLong',
|
||||||
None = 'None',
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ export type MenuItem<MenuItemValue, MenuItemTypes = string> = {
|
|||||||
tag?: React.ReactNode;
|
tag?: React.ReactNode;
|
||||||
slotAfter?: React.ReactNode;
|
slotAfter?: React.ReactNode;
|
||||||
description?: string;
|
description?: string;
|
||||||
slotCustomContent?: React.ReactNode;
|
|
||||||
|
|
||||||
href?: string;
|
href?: string;
|
||||||
onSelect?: (key: MenuItemValue) => void;
|
onSelect?: (key: MenuItemValue) => void;
|
||||||
|
|||||||
@@ -1,18 +1,12 @@
|
|||||||
import environments from '../../public/configs/env.json';
|
import environments from '../../public/configs/env.json';
|
||||||
|
|
||||||
const CURRENT_MODE = ({
|
// TODO: Update for Mainnet deployment
|
||||||
production: 'MAINNET',
|
export const AVAILABLE_ENVIRONMENTS =
|
||||||
testnet: 'TESTNET',
|
import.meta.env.MODE === 'production'
|
||||||
staging: 'DEV',
|
? environments.deployments.TESTNET
|
||||||
development: 'DEV',
|
: environments.deployments.DEV;
|
||||||
}[import.meta.env.MODE] ?? 'MAINNET') as 'MAINNET' | 'TESTNET' | 'DEV';
|
|
||||||
|
|
||||||
export const isMainnet = CURRENT_MODE === 'MAINNET';
|
export const CURRENT_ABACUS_DEPLOYMENT = import.meta.env.MODE === 'production' ? 'TESTNET' : 'DEV';
|
||||||
export const isTestnet = CURRENT_MODE === 'TESTNET';
|
|
||||||
export const isDev = CURRENT_MODE === 'DEV';
|
|
||||||
|
|
||||||
export const AVAILABLE_ENVIRONMENTS = environments.deployments[CURRENT_MODE];
|
|
||||||
export const CURRENT_ABACUS_DEPLOYMENT = CURRENT_MODE;
|
|
||||||
export const ENVIRONMENT_CONFIG_MAP = environments.environments;
|
export const ENVIRONMENT_CONFIG_MAP = environments.environments;
|
||||||
export type DydxNetwork = keyof typeof ENVIRONMENT_CONFIG_MAP;
|
export type DydxNetwork = keyof typeof ENVIRONMENT_CONFIG_MAP;
|
||||||
export const DEFAULT_APP_ENVIRONMENT = AVAILABLE_ENVIRONMENTS.default as DydxNetwork;
|
export const DEFAULT_APP_ENVIRONMENT = AVAILABLE_ENVIRONMENTS.default as DydxNetwork;
|
||||||
|
|||||||
@@ -2,9 +2,8 @@ import { StatusResponse } from '@0xsquid/sdk';
|
|||||||
|
|
||||||
/** implemented in useNotificationTypes */
|
/** implemented in useNotificationTypes */
|
||||||
export enum NotificationType {
|
export enum NotificationType {
|
||||||
AbacusGenerated = 'AbacusGenerated',
|
OrderStatusChanged = 'OrderStatusChanged',
|
||||||
SquidTransfer = 'SquidTransfer',
|
SquidTransfer = 'SquidTransfer',
|
||||||
ReleaseUpdates = 'ReleaseUpdates',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum NotificationComponentType {}
|
export enum NotificationComponentType {}
|
||||||
@@ -60,7 +59,7 @@ export enum NotificationStatus {
|
|||||||
/** Toast or NotificationsMenu item interacted with or dismissed. "Seen" in NotificationsMenu. */
|
/** Toast or NotificationsMenu item interacted with or dismissed. "Seen" in NotificationsMenu. */
|
||||||
Seen,
|
Seen,
|
||||||
|
|
||||||
/** Notification marked for deletion. Hidden in NotificationsMenu. */
|
/** Notification marked for deletion. "Archived" in NotificationsMenu. */
|
||||||
Cleared,
|
Cleared,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,22 +80,14 @@ export type Notifications = Record<NotificationId, Notification<any>>;
|
|||||||
/** Notification display data derived from app state at runtime. */
|
/** Notification display data derived from app state at runtime. */
|
||||||
export type NotificationDisplayData = {
|
export type NotificationDisplayData = {
|
||||||
icon?: React.ReactNode;
|
icon?: React.ReactNode;
|
||||||
title: string; // Title for Toast, Notification, and Push Notification
|
|
||||||
body?: string; // Description body for Toast, Notification, and Push Notification
|
|
||||||
|
|
||||||
slotTitleLeft?: React.ReactNode;
|
title?: string;
|
||||||
slotTitleRight?: React.ReactNode;
|
|
||||||
|
|
||||||
groupKey: string; // Grouping key toast notification stacking
|
description?: React.ReactNode;
|
||||||
|
|
||||||
// Overrides title/body for Notification in NotificationMenu
|
customContent?: React.ReactNode;
|
||||||
renderCustomBody?: ({
|
|
||||||
isToast,
|
customMenuContent?: React.ReactNode;
|
||||||
notification,
|
|
||||||
}: {
|
|
||||||
isToast?: boolean;
|
|
||||||
notification: Notification;
|
|
||||||
}) => React.ReactNode; // Custom Notification
|
|
||||||
|
|
||||||
actionDescription?: string;
|
actionDescription?: string;
|
||||||
|
|
||||||
@@ -124,29 +115,12 @@ export type NotificationDisplayData = {
|
|||||||
toastDuration?: number;
|
toastDuration?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum TransferNotificationTypes {
|
|
||||||
Withdrawal = 'withdrawal',
|
|
||||||
Deposit = 'deposit',
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notification types
|
// Notification types
|
||||||
export type TransferNotifcation = {
|
export type TransferNotifcation = {
|
||||||
txHash: string;
|
txHash: string;
|
||||||
type?: TransferNotificationTypes;
|
|
||||||
toChainId?: string;
|
toChainId?: string;
|
||||||
fromChainId?: string;
|
fromChainId?: string;
|
||||||
toAmount?: number;
|
toAmount?: number;
|
||||||
triggeredAt?: number;
|
triggeredAt?: number;
|
||||||
isCctp?: boolean;
|
|
||||||
errorCount?: number;
|
|
||||||
status?: StatusResponse;
|
status?: StatusResponse;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Struct to store whether a NotificationType should be triggered
|
|
||||||
*/
|
|
||||||
export type NotificationPreferences = {
|
|
||||||
[key in NotificationType]: boolean;
|
|
||||||
} & { version: string };
|
|
||||||
|
|
||||||
export const DEFAULT_TOAST_AUTO_CLOSE_MS = 5000;
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ export const LEVERAGE_DECIMALS = 2;
|
|||||||
export const TOKEN_DECIMALS = 4;
|
export const TOKEN_DECIMALS = 4;
|
||||||
export const LARGE_TOKEN_DECIMALS = 2;
|
export const LARGE_TOKEN_DECIMALS = 2;
|
||||||
export const FEE_DECIMALS = 3;
|
export const FEE_DECIMALS = 3;
|
||||||
export const FUNDING_DECIMALS = 6;
|
|
||||||
|
|
||||||
export const QUANTUM_MULTIPLIER = 1_000_000;
|
export const QUANTUM_MULTIPLIER = 1_000_000;
|
||||||
|
|
||||||
@@ -19,7 +18,3 @@ export enum NumberSign {
|
|||||||
Negative = 'Negative',
|
Negative = 'Negative',
|
||||||
Neutral = 'Neutral',
|
Neutral = 'Neutral',
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deposit/Withdraw
|
|
||||||
export const MAX_CCTP_TRANSFER_AMOUNT = 1_000_000;
|
|
||||||
export const MAX_PRICE_IMPACT = 0.02; // 2%
|
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
/**
|
|
||||||
* @description Orderbook display constants
|
|
||||||
*/
|
|
||||||
export const ORDERBOOK_MAX_ROWS_PER_SIDE = 30;
|
|
||||||
export const ORDERBOOK_ANIMATION_DURATION = 400;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Orderbook pixel constants
|
|
||||||
* @note ORDERBOOK_ROW_HEIGHT should be a divisor of ORDERBOOK_HEIGHT so that we do not have a partial row at the bottom
|
|
||||||
* @note To change the Orderbook width, --orderbook-trades-width and ORDERBOOK_WIDTH must be changed to the same value
|
|
||||||
*/
|
|
||||||
export const ORDERBOOK_HEIGHT = 800;
|
|
||||||
export const ORDERBOOK_WIDTH = 300;
|
|
||||||
export const ORDERBOOK_ROW_HEIGHT = 24;
|
|
||||||
export const ORDERBOOK_ROW_PADDING_RIGHT = 8;
|
|
||||||
@@ -37,3 +37,7 @@ export const HISTORY_ROUTE = `${AppRoute.Portfolio}/${PortfolioRoute.History}/:s
|
|||||||
export const DEFAULT_TRADE_ROUTE = `${AppRoute.Trade}/${DEFAULT_MARKETID}`;
|
export const DEFAULT_TRADE_ROUTE = `${AppRoute.Trade}/${DEFAULT_MARKETID}`;
|
||||||
export const SETTINGS_ROUTE = `${AppRoute.Settings}/*`;
|
export const SETTINGS_ROUTE = `${AppRoute.Settings}/*`;
|
||||||
export const DEFAULT_DOCUMENT_TITLE = 'dYdX';
|
export const DEFAULT_DOCUMENT_TITLE = 'dYdX';
|
||||||
|
|
||||||
|
export enum ExternalLink {
|
||||||
|
Foundation = 'https://dydx.foundation',
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
export enum ColorToken {
|
|
||||||
Black = '#000000',
|
|
||||||
White = '#FFFFFF',
|
|
||||||
|
|
||||||
LightGray0 = '#FAFAFA',
|
|
||||||
LightGray1 = '#F7F8FA',
|
|
||||||
LightGray2 = '#FAFAFD',
|
|
||||||
LightGray3 = '#F5F5F5',
|
|
||||||
LightGray4 = '#EDF0F2',
|
|
||||||
LightGray5 = '#EDEDED',
|
|
||||||
LightGray6 = '#E9ECEE',
|
|
||||||
LightGray7 = '#E6E6E6',
|
|
||||||
LightGray8 = '#DBDBDB',
|
|
||||||
LightGray9 = '#D5DADF',
|
|
||||||
LightGray10 = '#D1D9E0',
|
|
||||||
|
|
||||||
MediumGray0 = '#CACACE',
|
|
||||||
MediumGray1 = '#C2CBD3',
|
|
||||||
|
|
||||||
DarkGray0 = '#888891',
|
|
||||||
DarkGray1 = '#737373',
|
|
||||||
DarkGray2 = '#56565C',
|
|
||||||
DarkGray3 = '#4B4B4B',
|
|
||||||
DarkGray4 = '#494950',
|
|
||||||
DarkGray5 = '#3B3B3F',
|
|
||||||
DarkGray6 = '#303033',
|
|
||||||
DarkGray7 = '#313135',
|
|
||||||
DarkGray8 = '#292929',
|
|
||||||
DarkGray9 = '#23232F',
|
|
||||||
DarkGray10 = '#212124',
|
|
||||||
DarkGray11 = '#18181B',
|
|
||||||
DarkGray12 = '#181818',
|
|
||||||
DarkGray13 = '#0C0C0D',
|
|
||||||
|
|
||||||
GrayBlue0 = '#464659',
|
|
||||||
GrayBlue1 = '#38384D',
|
|
||||||
GrayBlue2 = '#303045',
|
|
||||||
GrayBlue3 = '#29293D',
|
|
||||||
GrayBlue4 = '#212131',
|
|
||||||
GrayBlue5 = '#181825',
|
|
||||||
GrayBlue6 = '#101018',
|
|
||||||
GrayBlue7 = '#08080C',
|
|
||||||
|
|
||||||
GrayPurple0 = '#DFDADF',
|
|
||||||
GrayPurple1 = '#C8C7D8',
|
|
||||||
GrayPurple2 = '#807E98',
|
|
||||||
|
|
||||||
Purple0 = '#7774FF',
|
|
||||||
Purple1 = '#6966FF',
|
|
||||||
|
|
||||||
Green0 = '#1AFFB9',
|
|
||||||
Green1 = '#3ED9A4',
|
|
||||||
Green2 = '#2CCC98',
|
|
||||||
Green3 = '#3EB68A',
|
|
||||||
|
|
||||||
Yellow0 = '#FFCC48',
|
|
||||||
Yellow1 = '#FFB647',
|
|
||||||
|
|
||||||
Red0 = '#FF5C5C',
|
|
||||||
Red1 = '#E76565',
|
|
||||||
Red2 = '#E45555',
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Maps opacity to corresponding hex value */
|
|
||||||
export enum OpacityToken {
|
|
||||||
Opacity16 = '29',
|
|
||||||
Opacity20 = '33',
|
|
||||||
Opacity66 = 'A8',
|
|
||||||
Opacity90 = 'E6',
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
export type ThemeColors = LayerColors &
|
|
||||||
BorderColors &
|
|
||||||
TextColors &
|
|
||||||
GradientColors &
|
|
||||||
AccentColors &
|
|
||||||
StatusColors &
|
|
||||||
DirectionalColors &
|
|
||||||
RiskColors &
|
|
||||||
IconColors &
|
|
||||||
ComponentColors;
|
|
||||||
|
|
||||||
type LayerColors = {
|
|
||||||
layer0: string;
|
|
||||||
layer1: string;
|
|
||||||
layer2: string;
|
|
||||||
layer3: string;
|
|
||||||
layer4: string;
|
|
||||||
layer5: string;
|
|
||||||
layer6: string;
|
|
||||||
layer7: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type BorderColors = {
|
|
||||||
borderDefault: string;
|
|
||||||
borderDestructive: string;
|
|
||||||
borderButton: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type TextColors = {
|
|
||||||
textPrimary: string;
|
|
||||||
textSecondary: string;
|
|
||||||
textTertiary: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type GradientColors = {
|
|
||||||
gradientBase0: string;
|
|
||||||
gradientBase1: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type AccentColors = {
|
|
||||||
accent: string;
|
|
||||||
accentFaded: string;
|
|
||||||
favorite: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type StatusColors = {
|
|
||||||
success: string;
|
|
||||||
warning: string;
|
|
||||||
error: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type DirectionalColors = {
|
|
||||||
positive: string;
|
|
||||||
negative: string;
|
|
||||||
positiveFaded: string;
|
|
||||||
negativeFaded: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type RiskColors = {
|
|
||||||
riskLow: string;
|
|
||||||
riskMedium: string;
|
|
||||||
riskHigh: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type IconColors = {
|
|
||||||
logoFill: string;
|
|
||||||
profileYellow: string;
|
|
||||||
profileRed: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ComponentColors = {
|
|
||||||
inputBackground: string;
|
|
||||||
popoverBackground: string;
|
|
||||||
switchThumbActiveBackground: string;
|
|
||||||
toggleBackground: string;
|
|
||||||
tooltipBackground: string;
|
|
||||||
};
|
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
import { type TooltipStrings, TOOLTIP_STRING_KEYS } from '@/constants/localization';
|
import { type TooltipStrings, TOOLTIP_STRING_KEYS } from '@/constants/localization';
|
||||||
|
|
||||||
export const depositTooltips: TooltipStrings = {
|
export const depositTooltips: TooltipStrings = {
|
||||||
'minimum-deposit-amount': ({ stringGetter }) => ({
|
|
||||||
title: stringGetter({ key: TOOLTIP_STRING_KEYS.MINIMUM_DEPOSIT_AMOUNT_TITLE }),
|
|
||||||
body: stringGetter({ key: TOOLTIP_STRING_KEYS.MINIMUM_DEPOSIT_AMOUNT_BODY }),
|
|
||||||
}),
|
|
||||||
swap: ({ stringGetter }) => ({
|
swap: ({ stringGetter }) => ({
|
||||||
title: stringGetter({ key: TOOLTIP_STRING_KEYS.SWAP_TITLE }),
|
title: stringGetter({ key: TOOLTIP_STRING_KEYS.SWAP_TITLE }),
|
||||||
body: stringGetter({ key: TOOLTIP_STRING_KEYS.SWAP_BODY }),
|
body: stringGetter({ key: TOOLTIP_STRING_KEYS.SWAP_BODY }),
|
||||||
|
|||||||
@@ -57,10 +57,10 @@ export const tradeTooltips: TooltipStrings = {
|
|||||||
title: stringGetter({ key: TOOLTIP_STRING_KEYS.INDEX_PRICE_TITLE }),
|
title: stringGetter({ key: TOOLTIP_STRING_KEYS.INDEX_PRICE_TITLE }),
|
||||||
body: stringGetter({ key: TOOLTIP_STRING_KEYS.INDEX_PRICE_BODY }),
|
body: stringGetter({ key: TOOLTIP_STRING_KEYS.INDEX_PRICE_BODY }),
|
||||||
}),
|
}),
|
||||||
'initial-margin-fraction': ({ stringGetter, urlConfigs }) => ({
|
'initial-margin-fraction': ({ stringGetter }) => ({
|
||||||
title: stringGetter({ key: TOOLTIP_STRING_KEYS.INITIAL_MARGIN_FRACTION_TITLE }),
|
title: stringGetter({ key: TOOLTIP_STRING_KEYS.INITIAL_MARGIN_FRACTION_TITLE }),
|
||||||
body: stringGetter({ key: TOOLTIP_STRING_KEYS.INITIAL_MARGIN_FRACTION_BODY }),
|
body: stringGetter({ key: TOOLTIP_STRING_KEYS.INITIAL_MARGIN_FRACTION_BODY }),
|
||||||
learnMoreLink: urlConfigs?.initialMarginFractionLearnMore,
|
learnMoreLink: 'https://help.dydx.exchange/articles/5232637-maximum-position-sizes',
|
||||||
}),
|
}),
|
||||||
'initial-stop': ({ stringGetter }) => ({
|
'initial-stop': ({ stringGetter }) => ({
|
||||||
title: stringGetter({ key: TOOLTIP_STRING_KEYS.INITIAL_STOP_TITLE }),
|
title: stringGetter({ key: TOOLTIP_STRING_KEYS.INITIAL_STOP_TITLE }),
|
||||||
@@ -138,10 +138,6 @@ export const tradeTooltips: TooltipStrings = {
|
|||||||
title: stringGetter({ key: TOOLTIP_STRING_KEYS.POST_ONLY_TITLE }),
|
title: stringGetter({ key: TOOLTIP_STRING_KEYS.POST_ONLY_TITLE }),
|
||||||
body: stringGetter({ key: TOOLTIP_STRING_KEYS.POST_ONLY_BODY }),
|
body: stringGetter({ key: TOOLTIP_STRING_KEYS.POST_ONLY_BODY }),
|
||||||
}),
|
}),
|
||||||
'post-only-timeinforce-gtt': ({ stringGetter }) => ({
|
|
||||||
title: stringGetter({ key: TOOLTIP_STRING_KEYS.POST_ONLY_TIMEINFORCE_GTT_TITLE }),
|
|
||||||
body: stringGetter({ key: TOOLTIP_STRING_KEYS.POST_ONLY_TIMEINFORCE_GTT_BODY }),
|
|
||||||
}),
|
|
||||||
'price-impact': ({ stringGetter }) => ({
|
'price-impact': ({ stringGetter }) => ({
|
||||||
title: stringGetter({ key: TOOLTIP_STRING_KEYS.PRICE_IMPACT_TITLE }),
|
title: stringGetter({ key: TOOLTIP_STRING_KEYS.PRICE_IMPACT_TITLE }),
|
||||||
body: stringGetter({ key: TOOLTIP_STRING_KEYS.PRICE_IMPACT_BODY }),
|
body: stringGetter({ key: TOOLTIP_STRING_KEYS.PRICE_IMPACT_BODY }),
|
||||||
@@ -150,18 +146,10 @@ export const tradeTooltips: TooltipStrings = {
|
|||||||
title: stringGetter({ key: TOOLTIP_STRING_KEYS.REALIZED_PNL_TITLE }),
|
title: stringGetter({ key: TOOLTIP_STRING_KEYS.REALIZED_PNL_TITLE }),
|
||||||
body: stringGetter({ key: TOOLTIP_STRING_KEYS.REALIZED_PNL_BODY }),
|
body: stringGetter({ key: TOOLTIP_STRING_KEYS.REALIZED_PNL_BODY }),
|
||||||
}),
|
}),
|
||||||
'reduce-only': ({ stringGetter, urlConfigs }) => ({
|
'reduce-only': ({ stringGetter }) => ({
|
||||||
title: stringGetter({ key: TOOLTIP_STRING_KEYS.REDUCE_ONLY_TITLE }),
|
title: stringGetter({ key: TOOLTIP_STRING_KEYS.REDUCE_ONLY_TITLE }),
|
||||||
body: stringGetter({ key: TOOLTIP_STRING_KEYS.REDUCE_ONLY_BODY }),
|
body: stringGetter({ key: TOOLTIP_STRING_KEYS.REDUCE_ONLY_BODY }),
|
||||||
learnMoreLink: urlConfigs?.reduceOnlyLearnMore,
|
learnMoreLink: 'https://help.dydx.exchange/articles/6345793-reduce-only-orders',
|
||||||
}),
|
|
||||||
'reduce-only-execution-ioc-fok': ({ stringGetter }) => ({
|
|
||||||
title: stringGetter({ key: TOOLTIP_STRING_KEYS.REDUCE_ONLY_EXECUTION_IOC_FOK_TITLE }),
|
|
||||||
body: stringGetter({ key: TOOLTIP_STRING_KEYS.REDUCE_ONLY_EXECUTION_IOC_FOK_BODY }),
|
|
||||||
}),
|
|
||||||
'reduce-only-timeinforce-ioc-fok': ({ stringGetter }) => ({
|
|
||||||
title: stringGetter({ key: TOOLTIP_STRING_KEYS.REDUCE_ONLY_TIMEINFORCE_IOC_FOK_TITLE }),
|
|
||||||
body: stringGetter({ key: TOOLTIP_STRING_KEYS.REDUCE_ONLY_TIMEINFORCE_IOC_FOK_BODY }),
|
|
||||||
}),
|
}),
|
||||||
spread: () => ({
|
spread: () => ({
|
||||||
title: 'Spread',
|
title: 'Spread',
|
||||||
|
|||||||
@@ -5,10 +5,6 @@ export const withdrawTooltips: TooltipStrings = {
|
|||||||
title: stringGetter({ key: TOOLTIP_STRING_KEYS.FAST_WITHDRAW_FEE_TITLE }),
|
title: stringGetter({ key: TOOLTIP_STRING_KEYS.FAST_WITHDRAW_FEE_TITLE }),
|
||||||
body: stringGetter({ key: TOOLTIP_STRING_KEYS.FAST_WITHDRAW_FEE_BODY }),
|
body: stringGetter({ key: TOOLTIP_STRING_KEYS.FAST_WITHDRAW_FEE_BODY }),
|
||||||
}),
|
}),
|
||||||
'minimum-amount-received': ({ stringGetter }) => ({
|
|
||||||
title: stringGetter({ key: TOOLTIP_STRING_KEYS.MINIMUM_AMOUNT_RECEIVED_TITLE }),
|
|
||||||
body: stringGetter({ key: TOOLTIP_STRING_KEYS.MINIMUM_AMOUNT_RECEIVED_BODY }),
|
|
||||||
}),
|
|
||||||
'withdraw-types': ({ stringGetter }) => ({
|
'withdraw-types': ({ stringGetter }) => ({
|
||||||
title: stringGetter({ key: TOOLTIP_STRING_KEYS.WITHDRAW_TYPES_TITLE }),
|
title: stringGetter({ key: TOOLTIP_STRING_KEYS.WITHDRAW_TYPES_TITLE }),
|
||||||
body: stringGetter({ key: TOOLTIP_STRING_KEYS.WITHDRAW_TYPES_BODY }),
|
body: stringGetter({ key: TOOLTIP_STRING_KEYS.WITHDRAW_TYPES_BODY }),
|
||||||
|
|||||||
@@ -116,15 +116,3 @@ export enum MobilePlaceOrderSteps {
|
|||||||
PlacingOrder = 'PlacingOrder',
|
PlacingOrder = 'PlacingOrder',
|
||||||
Confirmation = 'Confirmation',
|
Confirmation = 'Confirmation',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CLEARED_TRADE_INPUTS = {
|
|
||||||
limitPriceInput: '',
|
|
||||||
triggerPriceInput: '',
|
|
||||||
trailingPercentInput: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CLEARED_SIZE_INPUTS = {
|
|
||||||
amountInput: '',
|
|
||||||
usdAmountInput: '',
|
|
||||||
leverageInput: '',
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
export * from './useCenterOrderbook';
|
|
||||||
export * from './useSpreadRowScrollListener';
|
|
||||||
export * from './useDrawOrderbook';
|
|
||||||
export * from './useOrderbookValues';
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { useEffect, type RefObject } from 'react';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
marketId: string;
|
|
||||||
orderbookRef: RefObject<HTMLDivElement>;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Center Orderbook on load and market change
|
|
||||||
* Assumed that the two sides are the same height
|
|
||||||
*/
|
|
||||||
export const useCenterOrderbook = ({ marketId, orderbookRef }: Props) => {
|
|
||||||
const orderbookEl = orderbookRef.current;
|
|
||||||
const { clientHeight, scrollHeight } = orderbookEl ?? {};
|
|
||||||
const shouldScroll = scrollHeight && clientHeight && scrollHeight > clientHeight;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (orderbookEl && shouldScroll) {
|
|
||||||
orderbookEl.scrollTo({ top: (scrollHeight - clientHeight) / 2 });
|
|
||||||
}
|
|
||||||
}, [shouldScroll, marketId]);
|
|
||||||
};
|
|
||||||
@@ -1,373 +0,0 @@
|
|||||||
import { useEffect, useMemo, useRef } from 'react';
|
|
||||||
import { shallowEqual, useSelector } from 'react-redux';
|
|
||||||
|
|
||||||
import type { PerpetualMarketOrderbookLevel } from '@/constants/abacus';
|
|
||||||
import { SMALL_USD_DECIMALS, TOKEN_DECIMALS } from '@/constants/numbers';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ORDERBOOK_ANIMATION_DURATION,
|
|
||||||
ORDERBOOK_HEIGHT,
|
|
||||||
ORDERBOOK_ROW_HEIGHT,
|
|
||||||
ORDERBOOK_ROW_PADDING_RIGHT,
|
|
||||||
ORDERBOOK_WIDTH,
|
|
||||||
} from '@/constants/orderbook';
|
|
||||||
|
|
||||||
import { getCurrentMarketConfig, getCurrentMarketOrderbookMap } from '@/state/perpetualsSelectors';
|
|
||||||
import { getAppTheme } from '@/state/configsSelectors';
|
|
||||||
|
|
||||||
import { MustBigNumber } from '@/lib/numbers';
|
|
||||||
|
|
||||||
import {
|
|
||||||
getHistogramXValues,
|
|
||||||
getRektFromIdx,
|
|
||||||
getXByColumn,
|
|
||||||
getYForElements,
|
|
||||||
} from '@/lib/orderbookHelpers';
|
|
||||||
import { useAppThemeContext } from '../useAppTheme';
|
|
||||||
|
|
||||||
type ElementProps = {
|
|
||||||
data: Array<PerpetualMarketOrderbookLevel | undefined>;
|
|
||||||
histogramRange: number;
|
|
||||||
side: PerpetualMarketOrderbookLevel['side'];
|
|
||||||
};
|
|
||||||
|
|
||||||
type StyleProps = {
|
|
||||||
histogramSide: 'left' | 'right';
|
|
||||||
};
|
|
||||||
|
|
||||||
enum OrderbookRowAnimationType {
|
|
||||||
REMOVE,
|
|
||||||
NEW,
|
|
||||||
NONE,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useDrawOrderbook = ({
|
|
||||||
data,
|
|
||||||
histogramRange,
|
|
||||||
histogramSide,
|
|
||||||
side,
|
|
||||||
}: ElementProps & StyleProps) => {
|
|
||||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
||||||
const canvas = canvasRef.current;
|
|
||||||
const currentOrderbookMap = useSelector(getCurrentMarketOrderbookMap, shallowEqual);
|
|
||||||
const { stepSizeDecimals = TOKEN_DECIMALS, tickSizeDecimals = SMALL_USD_DECIMALS } =
|
|
||||||
useSelector(getCurrentMarketConfig, shallowEqual) || {};
|
|
||||||
const prevData = useRef<typeof data>(data);
|
|
||||||
const theme = useAppThemeContext();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scale canvas using device pixel ratio to unblur drawn text
|
|
||||||
* @url https://stackoverflow.com/questions/15661339/how-do-i-fix-blurry-text-in-my-html5-canvas/65124939#65124939
|
|
||||||
* @returns adjusted canvas width/height/rowHeight used in coordinates for drawing
|
|
||||||
**/
|
|
||||||
const { canvasWidth, canvasHeight, rowHeight } = useMemo(() => {
|
|
||||||
const ratio = window.devicePixelRatio || 1;
|
|
||||||
|
|
||||||
if (!canvas) {
|
|
||||||
return {
|
|
||||||
canvasWidth: ORDERBOOK_WIDTH / devicePixelRatio,
|
|
||||||
canvasHeight: ORDERBOOK_HEIGHT / devicePixelRatio,
|
|
||||||
rowHeight: ORDERBOOK_ROW_HEIGHT / devicePixelRatio,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
canvas.width = canvas.offsetWidth * ratio;
|
|
||||||
canvas.height = canvas.offsetHeight * ratio;
|
|
||||||
|
|
||||||
if (ctx) {
|
|
||||||
ctx.scale(ratio, ratio);
|
|
||||||
ctx.font = '12px Satoshi';
|
|
||||||
ctx.textAlign = 'right';
|
|
||||||
ctx.textBaseline = 'middle';
|
|
||||||
ctx.imageSmoothingQuality = 'high';
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
canvasWidth: canvas.width / ratio,
|
|
||||||
canvasHeight: canvas.height / ratio,
|
|
||||||
rowHeight: ORDERBOOK_ROW_HEIGHT,
|
|
||||||
};
|
|
||||||
}, [canvas]);
|
|
||||||
|
|
||||||
const drawBars = ({
|
|
||||||
barType,
|
|
||||||
ctx,
|
|
||||||
depthOrSizeValue,
|
|
||||||
gradientMultiplier,
|
|
||||||
histogramAccentColor,
|
|
||||||
histogramSide,
|
|
||||||
idx,
|
|
||||||
}: {
|
|
||||||
barType: 'depth' | 'size';
|
|
||||||
ctx: CanvasRenderingContext2D;
|
|
||||||
depthOrSizeValue: number;
|
|
||||||
gradientMultiplier: number;
|
|
||||||
histogramAccentColor: string;
|
|
||||||
histogramSide: 'left' | 'right';
|
|
||||||
idx: number;
|
|
||||||
}) => {
|
|
||||||
const { x1, x2, y1, y2 } = getRektFromIdx({
|
|
||||||
idx,
|
|
||||||
rowHeight,
|
|
||||||
canvasWidth,
|
|
||||||
canvasHeight,
|
|
||||||
side,
|
|
||||||
});
|
|
||||||
|
|
||||||
// X values
|
|
||||||
const maxHistogramBarWidth = x2 - x1 - (barType === 'size' ? 8 : 2);
|
|
||||||
const barWidth = depthOrSizeValue
|
|
||||||
? Math.min((depthOrSizeValue / histogramRange) * maxHistogramBarWidth, maxHistogramBarWidth)
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
const { gradient, bar } = getHistogramXValues({
|
|
||||||
barWidth,
|
|
||||||
canvasWidth,
|
|
||||||
gradientMultiplier,
|
|
||||||
histogramSide,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Gradient
|
|
||||||
let linearGradient;
|
|
||||||
|
|
||||||
try {
|
|
||||||
linearGradient = ctx.createLinearGradient(gradient.x1, y1, gradient.x2, y2);
|
|
||||||
linearGradient.addColorStop(0, histogramAccentColor);
|
|
||||||
linearGradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
|
|
||||||
ctx.fillStyle = linearGradient;
|
|
||||||
} catch (err) {
|
|
||||||
ctx.fillStyle = 'rgba(0, 0, 0, 0)';
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.beginPath();
|
|
||||||
|
|
||||||
// Bar
|
|
||||||
const { bar: y } = getYForElements({ y: y1, rowHeight });
|
|
||||||
|
|
||||||
if (ctx.roundRect) {
|
|
||||||
ctx.roundRect(
|
|
||||||
bar.x1,
|
|
||||||
y,
|
|
||||||
bar.x2,
|
|
||||||
rowHeight - 2,
|
|
||||||
histogramSide === 'right' ? [2, 0, 0, 2] : [0, 2, 2, 0]
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
ctx.rect(bar.x1, y, bar.x2, rowHeight - 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.fill();
|
|
||||||
};
|
|
||||||
|
|
||||||
const drawText = ({
|
|
||||||
animationType = OrderbookRowAnimationType.NONE,
|
|
||||||
ctx,
|
|
||||||
idx,
|
|
||||||
mine,
|
|
||||||
price,
|
|
||||||
size,
|
|
||||||
}: {
|
|
||||||
animationType?: OrderbookRowAnimationType;
|
|
||||||
ctx: CanvasRenderingContext2D;
|
|
||||||
idx: number;
|
|
||||||
mine?: number;
|
|
||||||
price?: number;
|
|
||||||
size?: number;
|
|
||||||
}) => {
|
|
||||||
const { y1 } = getRektFromIdx({
|
|
||||||
idx,
|
|
||||||
rowHeight,
|
|
||||||
canvasWidth,
|
|
||||||
canvasHeight,
|
|
||||||
side,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { text: y } = getYForElements({ y: y1, rowHeight });
|
|
||||||
|
|
||||||
let textColor: string;
|
|
||||||
let updatedTextColor: string | undefined;
|
|
||||||
|
|
||||||
switch (animationType) {
|
|
||||||
case OrderbookRowAnimationType.REMOVE: {
|
|
||||||
textColor = theme.textSecondary;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case OrderbookRowAnimationType.NEW: {
|
|
||||||
updatedTextColor = side === 'bid' ? theme.positive : theme.negative;
|
|
||||||
textColor = theme.textPrimary;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default: {
|
|
||||||
textColor = theme.textPrimary;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size text
|
|
||||||
if (size) {
|
|
||||||
ctx.fillStyle = updatedTextColor ?? textColor;
|
|
||||||
ctx.fillText(
|
|
||||||
MustBigNumber(size).toFixed(stepSizeDecimals ?? TOKEN_DECIMALS),
|
|
||||||
getXByColumn({ canvasWidth, colIdx: 0 }) - ORDERBOOK_ROW_PADDING_RIGHT,
|
|
||||||
y
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Price text
|
|
||||||
if (price) {
|
|
||||||
ctx.fillStyle = textColor;
|
|
||||||
ctx.fillText(
|
|
||||||
MustBigNumber(price).toFixed(tickSizeDecimals ?? SMALL_USD_DECIMALS),
|
|
||||||
getXByColumn({ canvasWidth, colIdx: 1 }) - ORDERBOOK_ROW_PADDING_RIGHT,
|
|
||||||
y
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mine text
|
|
||||||
if (mine) {
|
|
||||||
ctx.fillStyle = textColor;
|
|
||||||
ctx.fillText(
|
|
||||||
MustBigNumber(mine).toFixed(stepSizeDecimals ?? TOKEN_DECIMALS),
|
|
||||||
getXByColumn({ canvasWidth, colIdx: 2 }) - ORDERBOOK_ROW_PADDING_RIGHT,
|
|
||||||
y
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const drawOrderbookRow = ({
|
|
||||||
ctx,
|
|
||||||
idx,
|
|
||||||
rowToRender,
|
|
||||||
animationType = OrderbookRowAnimationType.NONE,
|
|
||||||
}: {
|
|
||||||
ctx: CanvasRenderingContext2D;
|
|
||||||
idx: number;
|
|
||||||
rowToRender?: PerpetualMarketOrderbookLevel;
|
|
||||||
animationType?: OrderbookRowAnimationType;
|
|
||||||
}) => {
|
|
||||||
if (!rowToRender) return;
|
|
||||||
const { depth, mine, price, size } = rowToRender;
|
|
||||||
const histogramAccentColor = side === 'bid' ? theme.positiveFaded : theme.negativeFaded;
|
|
||||||
|
|
||||||
// Depth Bar
|
|
||||||
if (depth) {
|
|
||||||
drawBars({
|
|
||||||
barType: 'depth',
|
|
||||||
ctx,
|
|
||||||
depthOrSizeValue: depth,
|
|
||||||
gradientMultiplier: 1.3,
|
|
||||||
histogramAccentColor,
|
|
||||||
histogramSide,
|
|
||||||
idx,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size Bar
|
|
||||||
drawBars({
|
|
||||||
barType: 'size',
|
|
||||||
ctx,
|
|
||||||
depthOrSizeValue: size,
|
|
||||||
gradientMultiplier: 5,
|
|
||||||
histogramAccentColor,
|
|
||||||
histogramSide,
|
|
||||||
idx,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Size, Price, Mine
|
|
||||||
drawText({
|
|
||||||
animationType,
|
|
||||||
ctx,
|
|
||||||
idx,
|
|
||||||
mine,
|
|
||||||
price,
|
|
||||||
size,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Update histograms and row contents on data change
|
|
||||||
useEffect(() => {
|
|
||||||
const ctx = canvas?.getContext('2d');
|
|
||||||
|
|
||||||
if (!canvas || !ctx) return;
|
|
||||||
|
|
||||||
// Clear canvas before redraw
|
|
||||||
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
||||||
|
|
||||||
// Animate row removal and update
|
|
||||||
const mapOfOrderbookPriceLevels =
|
|
||||||
side && currentOrderbookMap?.[side === 'ask' ? 'asks' : 'bids'];
|
|
||||||
const empty: number[] = [];
|
|
||||||
const removed: number[] = [];
|
|
||||||
const updated: number[] = [];
|
|
||||||
|
|
||||||
prevData.current.forEach((row, idx) => {
|
|
||||||
if (!row) {
|
|
||||||
empty.push(idx);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mapOfOrderbookPriceLevels?.[row.price] === 0) {
|
|
||||||
removed.push(idx);
|
|
||||||
drawOrderbookRow({
|
|
||||||
ctx,
|
|
||||||
idx,
|
|
||||||
rowToRender: row,
|
|
||||||
animationType: OrderbookRowAnimationType.REMOVE,
|
|
||||||
});
|
|
||||||
} else if (mapOfOrderbookPriceLevels?.[row.price] === row?.size) {
|
|
||||||
drawOrderbookRow({
|
|
||||||
ctx,
|
|
||||||
idx,
|
|
||||||
rowToRender: data[idx],
|
|
||||||
animationType: OrderbookRowAnimationType.NONE,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
updated.push(idx);
|
|
||||||
drawOrderbookRow({
|
|
||||||
ctx,
|
|
||||||
idx,
|
|
||||||
rowToRender: row,
|
|
||||||
animationType: OrderbookRowAnimationType.NEW,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
[...empty, ...removed, ...updated].forEach((idx) => {
|
|
||||||
const { x1, y1, x2, y2 } = getRektFromIdx({
|
|
||||||
idx,
|
|
||||||
rowHeight,
|
|
||||||
canvasWidth,
|
|
||||||
canvasHeight,
|
|
||||||
side,
|
|
||||||
});
|
|
||||||
|
|
||||||
ctx.clearRect(x1, y1, x2 - x1, y2 - y1);
|
|
||||||
drawOrderbookRow({
|
|
||||||
ctx,
|
|
||||||
idx,
|
|
||||||
rowToRender: data[idx],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, ORDERBOOK_ANIMATION_DURATION);
|
|
||||||
|
|
||||||
prevData.current = data;
|
|
||||||
}, [
|
|
||||||
canvasHeight,
|
|
||||||
canvasWidth,
|
|
||||||
rowHeight,
|
|
||||||
data,
|
|
||||||
histogramRange,
|
|
||||||
stepSizeDecimals,
|
|
||||||
tickSizeDecimals,
|
|
||||||
histogramSide,
|
|
||||||
side,
|
|
||||||
theme,
|
|
||||||
currentOrderbookMap,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return { canvasRef };
|
|
||||||
};
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
import { useMemo } from 'react';
|
|
||||||
import { shallowEqual, useSelector } from 'react-redux';
|
|
||||||
import { OrderSide } from '@dydxprotocol/v4-client-js';
|
|
||||||
|
|
||||||
import { type PerpetualMarketOrderbookLevel } from '@/constants/abacus';
|
|
||||||
import { DepthChartSeries, DepthChartDatum } from '@/constants/charts';
|
|
||||||
|
|
||||||
import { getCurrentMarketOrderbook } from '@/state/perpetualsSelectors';
|
|
||||||
import { getSubaccountOpenOrdersBySideAndPrice } from '@/state/accountSelectors';
|
|
||||||
|
|
||||||
import { MustBigNumber } from '@/lib/numbers';
|
|
||||||
|
|
||||||
export const useCalculateOrderbookData = ({ maxRowsPerSide }: { maxRowsPerSide: number }) => {
|
|
||||||
const orderbook = useSelector(getCurrentMarketOrderbook, shallowEqual);
|
|
||||||
|
|
||||||
const openOrdersBySideAndPrice =
|
|
||||||
useSelector(getSubaccountOpenOrdersBySideAndPrice, shallowEqual) || {};
|
|
||||||
|
|
||||||
return useMemo(() => {
|
|
||||||
const asks: Array<PerpetualMarketOrderbookLevel | undefined> = (
|
|
||||||
orderbook?.asks?.toArray() ?? []
|
|
||||||
)
|
|
||||||
.map(
|
|
||||||
(row, idx: number) =>
|
|
||||||
({
|
|
||||||
key: `ask-${idx}`,
|
|
||||||
side: 'ask',
|
|
||||||
mine: openOrdersBySideAndPrice[OrderSide.SELL]?.[row.price]?.size,
|
|
||||||
...row,
|
|
||||||
} as PerpetualMarketOrderbookLevel)
|
|
||||||
)
|
|
||||||
.slice(0, maxRowsPerSide);
|
|
||||||
|
|
||||||
const bids: Array<PerpetualMarketOrderbookLevel | undefined> = (
|
|
||||||
orderbook?.bids?.toArray() ?? []
|
|
||||||
)
|
|
||||||
.map(
|
|
||||||
(row, idx: number) =>
|
|
||||||
({
|
|
||||||
key: `bid-${idx}`,
|
|
||||||
side: 'bid',
|
|
||||||
mine: openOrdersBySideAndPrice[OrderSide.BUY]?.[row.price]?.size,
|
|
||||||
...row,
|
|
||||||
} as PerpetualMarketOrderbookLevel)
|
|
||||||
)
|
|
||||||
.slice(0, maxRowsPerSide);
|
|
||||||
|
|
||||||
// Prevent the bid/ask sides from crossing by using the offsets.
|
|
||||||
// While the books are crossing...
|
|
||||||
while (asks[0] && bids[0] && bids[0]!.price >= asks[0].price) {
|
|
||||||
// Drop the order on the side with the lower offset.
|
|
||||||
// The offset of the other side is higher and so supercedes.
|
|
||||||
if (bids[0]!.offset === asks[0].offset) {
|
|
||||||
// If offsets are the same, give precedence to the larger size. In this case,
|
|
||||||
// one of the sizes *should* be zero, but we simply check for the larger size.
|
|
||||||
if (bids[0]!.size > asks[0].size) {
|
|
||||||
asks.shift();
|
|
||||||
} else {
|
|
||||||
bids.pop();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Offsets are not equal. Give precedence to the larger offset.
|
|
||||||
if (bids[0]!.offset > asks[0].offset) {
|
|
||||||
asks.shift();
|
|
||||||
} else {
|
|
||||||
bids.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const spread =
|
|
||||||
asks[0]?.price && bids[0]?.price ? MustBigNumber(asks[0].price).minus(bids[0].price) : null;
|
|
||||||
|
|
||||||
const spreadPercent = orderbook?.spreadPercent;
|
|
||||||
|
|
||||||
const histogramRange = Math.max(
|
|
||||||
isNaN(Number(bids[bids.length - 1]?.depth)) ? 0 : Number(bids[bids.length - 1]?.depth),
|
|
||||||
isNaN(Number(asks[asks.length - 1]?.depth)) ? 0 : Number(asks[asks.length - 1]?.depth)
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
asks,
|
|
||||||
bids,
|
|
||||||
spread,
|
|
||||||
spreadPercent,
|
|
||||||
histogramRange,
|
|
||||||
hasOrderbook: !!orderbook,
|
|
||||||
};
|
|
||||||
}, [orderbook, openOrdersBySideAndPrice]);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useOrderbookValuesForDepthChart = () => {
|
|
||||||
const orderbook = useSelector(getCurrentMarketOrderbook, shallowEqual);
|
|
||||||
|
|
||||||
return useMemo(() => {
|
|
||||||
const bids = (orderbook?.bids?.toArray() ?? [])
|
|
||||||
.filter(Boolean)
|
|
||||||
.map((datum) => ({ ...datum, seriesKey: DepthChartSeries.Bids } as DepthChartDatum));
|
|
||||||
|
|
||||||
const asks = (orderbook?.asks?.toArray() ?? [])
|
|
||||||
.filter(Boolean)
|
|
||||||
.map((datum) => ({ ...datum, seriesKey: DepthChartSeries.Asks } as DepthChartDatum));
|
|
||||||
|
|
||||||
const lowestBid = bids[bids.length - 1];
|
|
||||||
const highestBid = bids[0];
|
|
||||||
const lowestAsk = asks[0];
|
|
||||||
const highestAsk = asks[asks.length - 1];
|
|
||||||
|
|
||||||
const midMarketPrice = orderbook?.midPrice;
|
|
||||||
const spread = MustBigNumber(lowestAsk?.price ?? 0).minus(highestBid?.price ?? 0);
|
|
||||||
const spreadPercent = orderbook?.spreadPercent;
|
|
||||||
|
|
||||||
return {
|
|
||||||
bids,
|
|
||||||
asks,
|
|
||||||
lowestBid,
|
|
||||||
highestBid,
|
|
||||||
lowestAsk,
|
|
||||||
highestAsk,
|
|
||||||
midMarketPrice,
|
|
||||||
spread,
|
|
||||||
spreadPercent,
|
|
||||||
orderbook,
|
|
||||||
};
|
|
||||||
}, [orderbook]);
|
|
||||||
};
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import { type RefObject, useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
export const useSpreadRowScrollListener = ({
|
|
||||||
orderbookRef,
|
|
||||||
spreadRowRef,
|
|
||||||
}: {
|
|
||||||
orderbookRef: RefObject<HTMLDivElement>;
|
|
||||||
spreadRowRef: RefObject<HTMLDivElement>;
|
|
||||||
}) => {
|
|
||||||
const [displaySide, setDisplaySide] = useState<string>();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const onScroll = () => {
|
|
||||||
if (spreadRowRef.current && orderbookRef.current) {
|
|
||||||
const { clientHeight } = orderbookRef.current;
|
|
||||||
const parent = orderbookRef.current.getBoundingClientRect();
|
|
||||||
const spread = spreadRowRef.current.getBoundingClientRect();
|
|
||||||
const spreadTop = spread.top - parent.top;
|
|
||||||
const spreadBottom = spread.bottom - parent.top;
|
|
||||||
|
|
||||||
if (spreadBottom > clientHeight) {
|
|
||||||
setDisplaySide('bottom');
|
|
||||||
} else if (spreadTop < 0) {
|
|
||||||
setDisplaySide('top');
|
|
||||||
} else {
|
|
||||||
setDisplaySide(undefined);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
orderbookRef.current?.addEventListener('scroll', onScroll, false);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
orderbookRef.current?.removeEventListener('scroll', onScroll, false);
|
|
||||||
};
|
|
||||||
}, [orderbookRef.current, spreadRowRef.current]);
|
|
||||||
|
|
||||||
return displaySide;
|
|
||||||
};
|
|
||||||
@@ -22,8 +22,6 @@ import { useShouldShowFooter } from './useShouldShowFooter';
|
|||||||
import { useSelectedNetwork } from './useSelectedNetwork';
|
import { useSelectedNetwork } from './useSelectedNetwork';
|
||||||
import { useStringGetter } from './useStringGetter';
|
import { useStringGetter } from './useStringGetter';
|
||||||
import { useSubaccount } from './useSubaccount';
|
import { useSubaccount } from './useSubaccount';
|
||||||
import { useTradeFormInputs } from './useTradeFormInputs';
|
|
||||||
import { useURLConfigs } from './useURLConfigs';
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
useApiState,
|
useApiState,
|
||||||
@@ -50,6 +48,4 @@ export {
|
|||||||
useSelectedNetwork,
|
useSelectedNetwork,
|
||||||
useStringGetter,
|
useStringGetter,
|
||||||
useSubaccount,
|
useSubaccount,
|
||||||
useTradeFormInputs,
|
|
||||||
useURLConfigs,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export const useTradingView = ({
|
|||||||
const hasMarkets = marketIds.length > 0;
|
const hasMarkets = marketIds.length > 0;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (hasMarkets && isClientConnected && marketId) {
|
if (hasMarkets && isClientConnected) {
|
||||||
const widgetOptions = getWidgetOptions();
|
const widgetOptions = getWidgetOptions();
|
||||||
const widgetOverrides = getWidgetOverrides(appTheme);
|
const widgetOverrides = getWidgetOverrides(appTheme);
|
||||||
const options = {
|
const options = {
|
||||||
@@ -75,7 +75,7 @@ export const useTradingView = ({
|
|||||||
tvWidgetRef.current = null;
|
tvWidgetRef.current = null;
|
||||||
setIsChartReady(false);
|
setIsChartReady(false);
|
||||||
};
|
};
|
||||||
}, [getCandlesForDatafeed, isClientConnected, hasMarkets, selectedLocale, selectedNetwork, !!marketId]);
|
}, [getCandlesForDatafeed, isClientConnected, hasMarkets, selectedLocale, selectedNetwork]);
|
||||||
|
|
||||||
return { savedResolution };
|
return { savedResolution };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useSelector } from 'react-redux';
|
|||||||
import type { IChartingLibraryWidget, ThemeName } from 'public/tradingview/charting_library';
|
import type { IChartingLibraryWidget, ThemeName } from 'public/tradingview/charting_library';
|
||||||
|
|
||||||
import { AppTheme } from '@/state/configs';
|
import { AppTheme } from '@/state/configs';
|
||||||
|
|
||||||
import { getAppTheme } from '@/state/configsSelectors';
|
import { getAppTheme } from '@/state/configsSelectors';
|
||||||
|
|
||||||
import { getWidgetOverrides } from '@/lib/tradingView/utils';
|
import { getWidgetOverrides } from '@/lib/tradingView/utils';
|
||||||
@@ -28,7 +29,7 @@ export const useTradingViewTheme = ({
|
|||||||
tvWidget: (IChartingLibraryWidget & { _id?: string; _ready?: boolean }) | null;
|
tvWidget: (IChartingLibraryWidget & { _id?: string; _ready?: boolean }) | null;
|
||||||
isWidgetReady?: boolean;
|
isWidgetReady?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const appTheme: AppTheme = useSelector(getAppTheme);
|
const appTheme = useSelector(getAppTheme);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (tvWidget && isWidgetReady) {
|
if (tvWidget && isWidgetReady) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { useQuery } from 'react-query';
|
|||||||
import { formatUnits } from 'viem';
|
import { formatUnits } from 'viem';
|
||||||
|
|
||||||
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
||||||
|
import { QUANTUM_MULTIPLIER } from '@/constants/numbers';
|
||||||
import { EvmAddress } from '@/constants/wallets';
|
import { EvmAddress } from '@/constants/wallets';
|
||||||
|
|
||||||
import { convertBech32Address } from '@/lib/addressUtils';
|
import { convertBech32Address } from '@/lib/addressUtils';
|
||||||
@@ -20,6 +21,7 @@ import { useTokenConfigs } from './useTokenConfigs';
|
|||||||
type UseAccountBalanceProps = {
|
type UseAccountBalanceProps = {
|
||||||
// Token Items
|
// Token Items
|
||||||
addressOrDenom?: string;
|
addressOrDenom?: string;
|
||||||
|
assetSymbol?: string;
|
||||||
decimals?: number;
|
decimals?: number;
|
||||||
|
|
||||||
// Chain Items
|
// Chain Items
|
||||||
@@ -38,6 +40,7 @@ export const CHAIN_DEFAULT_TOKEN_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeee
|
|||||||
|
|
||||||
export const useAccountBalance = ({
|
export const useAccountBalance = ({
|
||||||
addressOrDenom,
|
addressOrDenom,
|
||||||
|
assetSymbol,
|
||||||
bech32AddrPrefix,
|
bech32AddrPrefix,
|
||||||
chainId,
|
chainId,
|
||||||
decimals = 0,
|
decimals = 0,
|
||||||
@@ -88,7 +91,7 @@ export const useAccountBalance = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { formatted: evmBalance } = evmQuery.data || {};
|
const { formatted: evmBalance } = evmQuery.data || {};
|
||||||
const balance = isCosmosChain ? cosmosQuery.data : evmBalance;
|
const balance = !assetSymbol ? '0' : isCosmosChain ? cosmosQuery.data : evmBalance;
|
||||||
|
|
||||||
const nativeTokenCoinBalance = balances?.[chainTokenDenom];
|
const nativeTokenCoinBalance = balances?.[chainTokenDenom];
|
||||||
const nativeTokenBalance = MustBigNumber(nativeTokenCoinBalance?.amount);
|
const nativeTokenBalance = MustBigNumber(nativeTokenCoinBalance?.amount);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useCallback, useContext, createContext, useEffect, useState, useMemo }
|
|||||||
|
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { AES, enc } from 'crypto-js';
|
import { AES, enc } from 'crypto-js';
|
||||||
import { NOBLE_BECH32_PREFIX, LocalWallet, type Subaccount } from '@dydxprotocol/v4-client-js';
|
import { LocalWallet, type Subaccount } from '@dydxprotocol/v4-client-js';
|
||||||
|
|
||||||
import { OnboardingGuard, OnboardingState, type EvmDerivedAddresses } from '@/constants/account';
|
import { OnboardingGuard, OnboardingState, type EvmDerivedAddresses } from '@/constants/account';
|
||||||
import { DialogTypes } from '@/constants/dialogs';
|
import { DialogTypes } from '@/constants/dialogs';
|
||||||
@@ -42,7 +42,6 @@ const useAccountsContext = () => {
|
|||||||
selectedWalletError,
|
selectedWalletError,
|
||||||
evmAddress,
|
evmAddress,
|
||||||
signerWagmi,
|
signerWagmi,
|
||||||
publicClientWagmi,
|
|
||||||
dydxAddress: connectedDydxAddress,
|
dydxAddress: connectedDydxAddress,
|
||||||
signerGraz,
|
signerGraz,
|
||||||
} = useWalletConnection();
|
} = useWalletConnection();
|
||||||
@@ -136,8 +135,8 @@ const useAccountsContext = () => {
|
|||||||
getSubaccounts: async ({ dydxAddress }: { dydxAddress: DydxAddress }) => {
|
getSubaccounts: async ({ dydxAddress }: { dydxAddress: DydxAddress }) => {
|
||||||
try {
|
try {
|
||||||
const response = await compositeClient?.indexerClient.account.getSubaccounts(dydxAddress);
|
const response = await compositeClient?.indexerClient.account.getSubaccounts(dydxAddress);
|
||||||
setDydxSubaccounts(response?.subaccounts);
|
setDydxSubaccounts(response.subaccounts);
|
||||||
return response?.subaccounts ?? [];
|
return response.subaccounts;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 404 is expected if the user has no subaccounts
|
// 404 is expected if the user has no subaccounts
|
||||||
if (error.status === 404) {
|
if (error.status === 404) {
|
||||||
@@ -219,16 +218,6 @@ const useAccountsContext = () => {
|
|||||||
else abacusStateManager.attemptDisconnectAccount();
|
else abacusStateManager.attemptDisconnectAccount();
|
||||||
}, [localDydxWallet]);
|
}, [localDydxWallet]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const setNobleWallet = async () => {
|
|
||||||
if (hdKey?.mnemonic) {
|
|
||||||
const nobleWallet = await LocalWallet.fromMnemonic(hdKey.mnemonic, NOBLE_BECH32_PREFIX);
|
|
||||||
abacusStateManager.setNobleWallet(nobleWallet);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
setNobleWallet();
|
|
||||||
}, [hdKey?.mnemonic]);
|
|
||||||
|
|
||||||
// clear subaccounts when no dydxAddress is set
|
// clear subaccounts when no dydxAddress is set
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
@@ -307,7 +296,6 @@ const useAccountsContext = () => {
|
|||||||
// Wallet connection (EVM)
|
// Wallet connection (EVM)
|
||||||
evmAddress,
|
evmAddress,
|
||||||
signerWagmi,
|
signerWagmi,
|
||||||
publicClientWagmi,
|
|
||||||
|
|
||||||
// Wallet connection (Cosmos)
|
// Wallet connection (Cosmos)
|
||||||
signerGraz,
|
signerGraz,
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ export const useAnalytics = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// AnalyticsEvent.NetworkStatus
|
// AnalyticsEvent.NetworkStatus
|
||||||
const { height, indexerHeight, status, trailingBlocks} = useApiState();
|
const { height, indexerHeight, status } = useApiState();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (status) {
|
if (status) {
|
||||||
@@ -114,7 +114,6 @@ export const useAnalytics = () => {
|
|||||||
elapsedTime: lastSuccessfulIndexerRpcQuery && Date.now() - lastSuccessfulIndexerRpcQuery,
|
elapsedTime: lastSuccessfulIndexerRpcQuery && Date.now() - lastSuccessfulIndexerRpcQuery,
|
||||||
blockHeight: height ?? undefined,
|
blockHeight: height ?? undefined,
|
||||||
indexerBlockHeight: indexerHeight ?? undefined,
|
indexerBlockHeight: indexerHeight ?? undefined,
|
||||||
trailingBlocks: trailingBlocks ?? undefined
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [status]);
|
}, [status]);
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export const getIndexerHeight = (apiState: Nullable<AbacusApiState>) => {
|
|||||||
export const useApiState = () => {
|
export const useApiState = () => {
|
||||||
const stringGetter = useStringGetter();
|
const stringGetter = useStringGetter();
|
||||||
const apiState = useSelector(getApiState, shallowEqual);
|
const apiState = useSelector(getApiState, shallowEqual);
|
||||||
const { haltedBlock, height, status, trailingBlocks} = apiState ?? {};
|
const { haltedBlock, height, status, trailingBlocks } = apiState ?? {};
|
||||||
const statusErrorMessage = getStatusErrorMessage({ apiState, stringGetter });
|
const statusErrorMessage = getStatusErrorMessage({ apiState, stringGetter });
|
||||||
const indexerHeight = getIndexerHeight(apiState);
|
const indexerHeight = getIndexerHeight(apiState);
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
import { useSelector } from 'react-redux';
|
|
||||||
import { ThemeProvider } from 'styled-components';
|
|
||||||
|
|
||||||
import { AppTheme } from '@/state/configs';
|
|
||||||
import { getAppTheme } from '@/state/configsSelectors';
|
|
||||||
|
|
||||||
import { Themes } from '@/styles/themes';
|
|
||||||
|
|
||||||
export const AppThemeProvider = ({ ...props }) => {
|
|
||||||
return <ThemeProvider theme={useAppThemeContext()} {...props} />
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useAppThemeContext = () => {
|
|
||||||
const theme: AppTheme = useSelector(getAppTheme);
|
|
||||||
return Themes[theme];
|
|
||||||
}
|
|
||||||
@@ -5,15 +5,16 @@ import { useMatch, useNavigate } from 'react-router-dom';
|
|||||||
import { LocalStorageKey } from '@/constants/localStorage';
|
import { LocalStorageKey } from '@/constants/localStorage';
|
||||||
import { DEFAULT_MARKETID } from '@/constants/markets';
|
import { DEFAULT_MARKETID } from '@/constants/markets';
|
||||||
import { AppRoute } from '@/constants/routes';
|
import { AppRoute } from '@/constants/routes';
|
||||||
import { useLocalStorage } from '@/hooks/useLocalStorage';
|
|
||||||
|
|
||||||
import { getSelectedNetwork } from '@/state/appSelectors';
|
import { getSelectedNetwork } from '@/state/appSelectors';
|
||||||
import { closeDialogInTradeBox } from '@/state/dialogs';
|
import { closeDialogInTradeBox } from '@/state/dialogs';
|
||||||
import { setCurrentMarketId } from '@/state/perpetuals';
|
import { setCurrentMarketId } from '@/state/perpetuals';
|
||||||
import { getMarketIds } from '@/state/perpetualsSelectors';
|
|
||||||
|
|
||||||
import abacusStateManager from '@/lib/abacus';
|
import abacusStateManager from '@/lib/abacus';
|
||||||
|
|
||||||
|
import { useLocalStorage } from './useLocalStorage';
|
||||||
|
import { getMarketIds } from '@/state/perpetualsSelectors';
|
||||||
|
|
||||||
export const useCurrentMarketId = () => {
|
export const useCurrentMarketId = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const match = useMatch(`/${AppRoute.Trade}/:marketId`);
|
const match = useMatch(`/${AppRoute.Trade}/:marketId`);
|
||||||
@@ -21,8 +22,6 @@ export const useCurrentMarketId = () => {
|
|||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const selectedNetwork = useSelector(getSelectedNetwork);
|
const selectedNetwork = useSelector(getSelectedNetwork);
|
||||||
const marketIds = useSelector(getMarketIds, shallowEqual);
|
const marketIds = useSelector(getMarketIds, shallowEqual);
|
||||||
const hasMarketIds = marketIds.length > 0;
|
|
||||||
|
|
||||||
const [lastViewedMarket, setLastViewedMarket] = useLocalStorage({
|
const [lastViewedMarket, setLastViewedMarket] = useLocalStorage({
|
||||||
key: LocalStorageKey.LastViewedMarket,
|
key: LocalStorageKey.LastViewedMarket,
|
||||||
defaultValue: DEFAULT_MARKETID,
|
defaultValue: DEFAULT_MARKETID,
|
||||||
@@ -32,40 +31,22 @@ export const useCurrentMarketId = () => {
|
|||||||
if (marketIds.length === 0) return marketId ?? lastViewedMarket;
|
if (marketIds.length === 0) return marketId ?? lastViewedMarket;
|
||||||
if (!marketIds.includes(marketId ?? lastViewedMarket)) return DEFAULT_MARKETID;
|
if (!marketIds.includes(marketId ?? lastViewedMarket)) return DEFAULT_MARKETID;
|
||||||
return marketId ?? lastViewedMarket;
|
return marketId ?? lastViewedMarket;
|
||||||
}, [hasMarketIds, marketId]);
|
}, [marketIds, marketId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// If v4_markets has not been subscribed to yet or marketId is not specified, default to validId
|
setLastViewedMarket(validId);
|
||||||
if (!hasMarketIds || !marketId) {
|
dispatch(setCurrentMarketId(validId));
|
||||||
setLastViewedMarket(validId);
|
dispatch(closeDialogInTradeBox());
|
||||||
dispatch(setCurrentMarketId(validId));
|
|
||||||
dispatch(closeDialogInTradeBox());
|
|
||||||
|
|
||||||
if (validId !== marketId) {
|
navigate(`${AppRoute.Trade}/${validId}`, {
|
||||||
navigate(`${AppRoute.Trade}/${validId}`, {
|
replace: true,
|
||||||
replace: true,
|
});
|
||||||
});
|
}, [validId]);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If v4_markets has been subscribed to, check if marketId is valid
|
|
||||||
if (!marketIds.includes(marketId)) {
|
|
||||||
// If marketId is not valid (i.e. final settlement), navigate to markets page
|
|
||||||
navigate(AppRoute.Markets, {
|
|
||||||
replace: true,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// If marketId is valid, set currentMarketId
|
|
||||||
setLastViewedMarket(marketId);
|
|
||||||
dispatch(setCurrentMarketId(marketId));
|
|
||||||
dispatch(closeDialogInTradeBox());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [hasMarketIds, marketId]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Check for marketIds otherwise Abacus will silently fail its isMarketValid check
|
// Check for marketIds otherwise Abacus will silently fail its isMarketValid check
|
||||||
if (marketIds) {
|
if (marketIds) {
|
||||||
abacusStateManager.setMarket(marketId ?? DEFAULT_MARKETID);
|
abacusStateManager.setMarket(marketId ?? DEFAULT_MARKETID);
|
||||||
}
|
}
|
||||||
}, [selectedNetwork, hasMarketIds, marketId]);
|
}, [selectedNetwork, marketIds, marketId]);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { isDev } from '@/constants/networks';
|
|
||||||
import { WalletType } from '@/constants/wallets';
|
import { WalletType } from '@/constants/wallets';
|
||||||
|
|
||||||
import { isTruthy } from '@/lib/isTruthy';
|
import { isTruthy } from '@/lib/isTruthy';
|
||||||
@@ -7,7 +6,7 @@ export const useDisplayedWallets = () => {
|
|||||||
return [
|
return [
|
||||||
WalletType.MetaMask,
|
WalletType.MetaMask,
|
||||||
|
|
||||||
isDev && WalletType.Keplr,
|
import.meta.env.MODE !== 'production' && WalletType.Keplr,
|
||||||
|
|
||||||
WalletType.WalletConnect2,
|
WalletType.WalletConnect2,
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { createContext, useContext, useCallback, useEffect, useMemo } from 'react';
|
import { createContext, useContext, useCallback, useEffect, useMemo } from 'react';
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
|
import type { StatusResponse } from '@0xsquid/sdk';
|
||||||
|
|
||||||
import { LOCAL_STORAGE_VERSIONS, LocalStorageKey } from '@/constants/localStorage';
|
import { LocalStorageKey } from '@/constants/localStorage';
|
||||||
import { type TransferNotifcation } from '@/constants/notifications';
|
import { type TransferNotifcation } from '@/constants/notifications';
|
||||||
|
|
||||||
import { useAccounts } from '@/hooks/useAccounts';
|
import { useAccounts } from '@/hooks/useAccounts';
|
||||||
|
import { STATUS_ERROR_GRACE_PERIOD, useSquid } from '@/hooks/useSquid';
|
||||||
import { fetchSquidStatus, STATUS_ERROR_GRACE_PERIOD } from '@/lib/squid';
|
|
||||||
|
|
||||||
import { useLocalStorage } from './useLocalStorage';
|
import { useLocalStorage } from './useLocalStorage';
|
||||||
|
|
||||||
const LocalNotificationsContext = createContext<
|
const LocalNotificationsContext = createContext<
|
||||||
@@ -22,32 +22,16 @@ export const LocalNotificationsProvider = ({ ...props }) => (
|
|||||||
export const useLocalNotifications = () => useContext(LocalNotificationsContext)!;
|
export const useLocalNotifications = () => useContext(LocalNotificationsContext)!;
|
||||||
|
|
||||||
const TRANSFER_STATUS_FETCH_INTERVAL = 10_000;
|
const TRANSFER_STATUS_FETCH_INTERVAL = 10_000;
|
||||||
const ERROR_COUNT_THRESHOLD = 3;
|
|
||||||
|
|
||||||
const useLocalNotificationsContext = () => {
|
const useLocalNotificationsContext = () => {
|
||||||
// transfer notifications
|
// transfer notifications
|
||||||
const [allTransferNotifications, setAllTransferNotifications] = useLocalStorage<{
|
const [allTransferNotifications, setAllTransferNotifications] = useLocalStorage<{
|
||||||
[key: `dydx${string}`]: TransferNotifcation[];
|
[key: `dydx${string}`]: TransferNotifcation[];
|
||||||
version: string;
|
|
||||||
}>({
|
}>({
|
||||||
key: LocalStorageKey.TransferNotifications,
|
key: LocalStorageKey.TransferNotifications,
|
||||||
defaultValue: {
|
defaultValue: {},
|
||||||
version: LOCAL_STORAGE_VERSIONS[LocalStorageKey.TransferNotifications],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ensure version matches, otherwise wipe
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
allTransferNotifications?.version !==
|
|
||||||
LOCAL_STORAGE_VERSIONS[LocalStorageKey.TransferNotifications]
|
|
||||||
) {
|
|
||||||
setAllTransferNotifications({
|
|
||||||
version: LOCAL_STORAGE_VERSIONS[LocalStorageKey.TransferNotifications],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [allTransferNotifications]);
|
|
||||||
|
|
||||||
const { dydxAddress } = useAccounts();
|
const { dydxAddress } = useAccounts();
|
||||||
|
|
||||||
const transferNotifications = dydxAddress ? allTransferNotifications[dydxAddress] || [] : [];
|
const transferNotifications = dydxAddress ? allTransferNotifications[dydxAddress] || [] : [];
|
||||||
@@ -68,65 +52,49 @@ const useLocalNotificationsContext = () => {
|
|||||||
[transferNotifications]
|
[transferNotifications]
|
||||||
);
|
);
|
||||||
|
|
||||||
useQuery({
|
const squid = useSquid();
|
||||||
queryKey: 'getTransactionStatus',
|
|
||||||
|
const { data: transferStatuses } = useQuery({
|
||||||
|
queryKey: ['getTransactionStatus', transferNotifications],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const processTransferNotifications = async (transferNotifications: TransferNotifcation[]) => {
|
const statuses: { [key: string]: StatusResponse } = {};
|
||||||
const newTransferNotifications = await Promise.all(
|
for (const {
|
||||||
transferNotifications.map(async (transferNotification) => {
|
txHash,
|
||||||
const {
|
toChainId,
|
||||||
txHash,
|
fromChainId,
|
||||||
toChainId,
|
triggeredAt,
|
||||||
fromChainId,
|
status: currentStatus,
|
||||||
triggeredAt,
|
} of transferNotifications) {
|
||||||
isCctp,
|
try {
|
||||||
errorCount,
|
if (currentStatus && currentStatus?.squidTransactionStatus !== 'ongoing') continue;
|
||||||
status: currentStatus,
|
|
||||||
} = transferNotification;
|
|
||||||
|
|
||||||
// @ts-ignore status.errors is not in the type definition but can be returned
|
const status = await squid?.getStatus({ transactionId: txHash, toChainId, fromChainId });
|
||||||
// also error can some time come back as an empty object so we need to ignore for that
|
if (status) statuses[txHash] = status;
|
||||||
const hasErrors = !!currentStatus?.errors ||
|
} catch (error) {
|
||||||
(currentStatus?.error && Object.keys(currentStatus.error).length !== 0);
|
// ignore errors for the first 120s since the status might not be available yet
|
||||||
|
if (!triggeredAt || Date.now() - triggeredAt > STATUS_ERROR_GRACE_PERIOD) {
|
||||||
if (
|
statuses[txHash] = error;
|
||||||
!hasErrors &&
|
}
|
||||||
(!currentStatus?.squidTransactionStatus ||
|
}
|
||||||
currentStatus?.squidTransactionStatus === 'ongoing')
|
}
|
||||||
) {
|
return statuses;
|
||||||
try {
|
|
||||||
const status = await fetchSquidStatus({
|
|
||||||
transactionId: txHash,
|
|
||||||
toChainId,
|
|
||||||
fromChainId,
|
|
||||||
}, isCctp);
|
|
||||||
|
|
||||||
if (status) {
|
|
||||||
transferNotification.status = status;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (!triggeredAt || Date.now() - triggeredAt > STATUS_ERROR_GRACE_PERIOD) {
|
|
||||||
if (errorCount && errorCount > ERROR_COUNT_THRESHOLD) {
|
|
||||||
transferNotification.status = error;
|
|
||||||
} else {
|
|
||||||
transferNotification.errorCount = errorCount ? errorCount + 1 : 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return transferNotification;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return newTransferNotifications;
|
|
||||||
};
|
|
||||||
const newTransferNotifications = await processTransferNotifications(transferNotifications);
|
|
||||||
setTransferNotifications(newTransferNotifications);
|
|
||||||
},
|
},
|
||||||
refetchInterval: TRANSFER_STATUS_FETCH_INTERVAL,
|
refetchInterval: TRANSFER_STATUS_FETCH_INTERVAL,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!transferStatuses) return;
|
||||||
|
const newTransferNotifications = transferNotifications.map((notification) => {
|
||||||
|
const status = transferStatuses[notification.txHash];
|
||||||
|
if (!status) return notification;
|
||||||
|
return {
|
||||||
|
...notification,
|
||||||
|
status,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
setTransferNotifications(newTransferNotifications);
|
||||||
|
}, [transferStatuses]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
transferNotifications,
|
transferNotifications,
|
||||||
addTransferNotification,
|
addTransferNotification,
|
||||||
|
|||||||
@@ -6,10 +6,8 @@ import { MarketFilters, MARKET_FILTER_LABELS, type MarketData } from '@/constant
|
|||||||
import { getAssets } from '@/state/assetsSelectors';
|
import { getAssets } from '@/state/assetsSelectors';
|
||||||
import { getPerpetualMarkets } from '@/state/perpetualsSelectors';
|
import { getPerpetualMarkets } from '@/state/perpetualsSelectors';
|
||||||
|
|
||||||
import { isTruthy } from '@/lib/isTruthy';
|
|
||||||
|
|
||||||
const filterFunctions = {
|
const filterFunctions = {
|
||||||
[MarketFilters.ALL]: () => true,
|
[MarketFilters.ALL]: (market: MarketData) => true,
|
||||||
[MarketFilters.LAYER_1]: (market: MarketData) => {
|
[MarketFilters.LAYER_1]: (market: MarketData) => {
|
||||||
return market.asset.tags?.toArray().includes('Layer 1');
|
return market.asset.tags?.toArray().includes('Layer 1');
|
||||||
},
|
},
|
||||||
@@ -30,15 +28,13 @@ export const useMarketsData = (
|
|||||||
const allAssets = useSelector(getAssets, shallowEqual) || {};
|
const allAssets = useSelector(getAssets, shallowEqual) || {};
|
||||||
|
|
||||||
const markets = useMemo(() => {
|
const markets = useMemo(() => {
|
||||||
return Object.values(allPerpetualMarkets)
|
return Object.values(allPerpetualMarkets).map((marketData) => ({
|
||||||
.filter(isTruthy)
|
asset: allAssets[marketData.assetId],
|
||||||
.map((marketData) => ({
|
tickSizeDecimals: marketData.configs?.tickSizeDecimals,
|
||||||
asset: allAssets[marketData.assetId],
|
...marketData,
|
||||||
tickSizeDecimals: marketData.configs?.tickSizeDecimals,
|
...marketData.perpetual,
|
||||||
...marketData,
|
...marketData.configs,
|
||||||
...marketData.perpetual,
|
})) as MarketData[];
|
||||||
...marketData.configs,
|
|
||||||
})) as MarketData[];
|
|
||||||
}, [allPerpetualMarkets, allAssets]);
|
}, [allPerpetualMarkets, allAssets]);
|
||||||
|
|
||||||
const filteredMarkets = useMemo(() => {
|
const filteredMarkets = useMemo(() => {
|
||||||
|
|||||||