Merge branch 'master' into task/token-flow-tests

This commit is contained in:
AndyWhiteVega 2022-06-24 10:25:36 +01:00
commit ef6b79e1f8
100 changed files with 1479 additions and 694 deletions

View File

@ -36,7 +36,12 @@ export const JumpTo = ({
placeholder={placeholder} placeholder={placeholder}
className="max-w-[200px]" className="max-w-[200px]"
/> />
<Button data-testid="go-submit" variant="secondary" type="submit"> <Button
data-testid="go-submit"
variant="secondary"
boxShadow={false}
type="submit"
>
{t('Go')} {t('Go')}
</Button> </Button>
</div> </div>

View File

@ -75,7 +75,12 @@ export const Search = () => {
</InputError> </InputError>
)} )}
</FormGroup> </FormGroup>
<Button type="submit" variant="secondary" data-testid="search-button"> <Button
type="submit"
boxShadow={false}
variant="secondary"
data-testid="search-button"
>
{t('Search')} {t('Search')}
</Button> </Button>
</form> </form>

View File

@ -35,7 +35,7 @@ export const DrawerToggle = ({
}, [variant]); }, [variant]);
return ( return (
<Button variant="inline" className={classes} onClick={onToggle}> <Button variant="inline-link" className={classes} onClick={onToggle}>
<Icon name={iconName as IconName} /> <Icon name={iconName as IconName} />
</Button> </Button>
); );

View File

@ -101,7 +101,7 @@ const SimpleMarketList = () => {
<div className="absolute right-16 top-1/2 -translate-y-1/2"> <div className="absolute right-16 top-1/2 -translate-y-1/2">
<Button <Button
onClick={() => onClick(market.id)} onClick={() => onClick(market.id)}
variant="inline" variant="inline-link"
prependIconName="chevron-right" prependIconName="chevron-right"
/> />
</div> </div>

View File

@ -70,7 +70,7 @@ export default ({ steps }: StepperProps) => {
{index === steps.length - 1 ? 'Finish' : 'Continue'} {index === steps.length - 1 ? 'Finish' : 'Continue'}
</Button> </Button>
<Button <Button
variant="inline" variant="inline-link"
disabled={index === 0} disabled={index === 0}
onClick={handleBack} onClick={handleBack}
> >

View File

@ -38,7 +38,7 @@
"tranche_end": "2023-12-05T00:00:00.000Z", "tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "129999.45", "total_added": "129999.45",
"total_removed": "0", "total_removed": "0",
"locked_amount": "125550.67163847386329389", "locked_amount": "125432.15784547661146416",
"deposits": [ "deposits": [
{ {
"amount": "129999.45", "amount": "129999.45",
@ -488,7 +488,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z", "tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "97499.58", "total_added": "97499.58",
"total_removed": "0", "total_removed": "0",
"locked_amount": "66375.714926165134519854", "locked_amount": "66259.463935294023094392",
"deposits": [ "deposits": [
{ {
"amount": "97499.58", "amount": "97499.58",
@ -521,7 +521,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z", "tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "135173.4239508", "total_added": "135173.4239508",
"total_removed": "0", "total_removed": "0",
"locked_amount": "90724.1430149405949586301724", "locked_amount": "90565.24798031609389317802476",
"deposits": [ "deposits": [
{ {
"amount": "135173.4239508", "amount": "135173.4239508",
@ -554,7 +554,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z", "tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "32499.86", "total_added": "32499.86",
"total_removed": "0", "total_removed": "0",
"locked_amount": "27923.117022151803344928", "locked_amount": "27874.21223783955177767",
"deposits": [ "deposits": [
{ {
"amount": "32499.86", "amount": "32499.86",
@ -587,7 +587,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z", "tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "10833.29", "total_added": "10833.29",
"total_removed": "0", "total_removed": "0",
"locked_amount": "9088.703631196554874002", "locked_amount": "9072.785598463712161582",
"deposits": [ "deposits": [
{ {
"amount": "10833.29", "amount": "10833.29",
@ -675,7 +675,7 @@
"tranche_end": "2022-11-01T00:00:00.000Z", "tranche_end": "2022-11-01T00:00:00.000Z",
"total_added": "22500", "total_added": "22500",
"total_removed": "0", "total_removed": "0",
"locked_amount": "15926.89509737318925", "locked_amount": "15865.8047441123175",
"deposits": [ "deposits": [
{ {
"amount": "15000", "amount": "15000",
@ -761,7 +761,7 @@
"tranche_end": "2023-06-02T00:00:00.000Z", "tranche_end": "2023-06-02T00:00:00.000Z",
"total_added": "1939928.38", "total_added": "1939928.38",
"total_removed": "0", "total_removed": "0",
"locked_amount": "1824311.884226555593124554", "locked_amount": "1821656.662619683475351818",
"deposits": [ "deposits": [
{ {
"amount": "1852091.69", "amount": "1852091.69",
@ -1777,7 +1777,7 @@
"tranche_end": "2022-09-30T00:00:00.000Z", "tranche_end": "2022-09-30T00:00:00.000Z",
"total_added": "60916.66666633337", "total_added": "60916.66666633337",
"total_removed": "18323.723696937179372649", "total_removed": "18323.723696937179372649",
"locked_amount": "15345.7844080554792796843344487108", "locked_amount": "15267.7511993664485111836221926715",
"deposits": [ "deposits": [
{ {
"amount": "2833.333333", "amount": "2833.333333",
@ -5195,7 +5195,7 @@
"tranche_end": "2022-09-03T00:00:00.000Z", "tranche_end": "2022-09-03T00:00:00.000Z",
"total_added": "19455.000000000000000003", "total_added": "19455.000000000000000003",
"total_removed": "5052.45813105178", "total_removed": "5052.45813105178",
"locked_amount": "3797.54184693683445435058558856544901071", "locked_amount": "3770.9133709094361165005814824010654489",
"deposits": [ "deposits": [
{ {
"amount": "75", "amount": "75",
@ -14079,7 +14079,7 @@
"tranche_end": "2023-06-05T00:00:00.000Z", "tranche_end": "2023-06-05T00:00:00.000Z",
"total_added": "3732368.4671", "total_added": "3732368.4671",
"total_removed": "74162.9780761646031", "total_removed": "74162.9780761646031",
"locked_amount": "2827833.53284815265111086312", "locked_amount": "2823753.38168836704877442084",
"deposits": [ "deposits": [
{ {
"amount": "1998.95815", "amount": "1998.95815",
@ -14792,7 +14792,7 @@
"tranche_end": "2023-12-05T00:00:00.000Z", "tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "15788853.065470999700000001", "total_added": "15788853.065470999700000001",
"total_removed": "6529.308282907170975", "total_removed": "6529.308282907170975",
"locked_amount": "15248534.5651159366987063864185024232049202", "locked_amount": "15234140.6821890069690086304937381947864288",
"deposits": [ "deposits": [
{ {
"amount": "16249.93", "amount": "16249.93",
@ -16844,7 +16844,7 @@
"tranche_end": "2023-05-05T00:00:00.000Z", "tranche_end": "2023-05-05T00:00:00.000Z",
"total_added": "14597706.0446472999", "total_added": "14597706.0446472999",
"total_removed": "2069544.949743920512285522", "total_removed": "2069544.949743920512285522",
"locked_amount": "8428346.7523447924808560906348842", "locked_amount": "8414990.02955211055837547157567297",
"deposits": [ "deposits": [
{ {
"amount": "129284.449", "amount": "129284.449",
@ -20892,7 +20892,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z", "tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "5778205.3912159303", "total_added": "5778205.3912159303",
"total_removed": "1390546.591547348229906227", "total_removed": "1390546.591547348229906227",
"locked_amount": "3013187.37252800835890024298674749", "locked_amount": "3007910.05057183280994625221544021",
"deposits": [ "deposits": [
{ {
"amount": "552496.6455", "amount": "552496.6455",
@ -22015,7 +22015,7 @@
"tranche_end": "2023-06-05T00:00:00.000Z", "tranche_end": "2023-06-05T00:00:00.000Z",
"total_added": "472355.6199999996", "total_added": "472355.6199999996",
"total_removed": "0", "total_removed": "0",
"locked_amount": "448086.38786540236946778559309992", "locked_amount": "447439.86459099593698764817757484",
"deposits": [ "deposits": [
{ {
"amount": "3000", "amount": "3000",
@ -47668,7 +47668,7 @@
"tranche_start": "2021-12-05T00:00:00.000Z", "tranche_start": "2021-12-05T00:00:00.000Z",
"tranche_end": "2022-06-05T00:00:00.000Z", "tranche_end": "2022-06-05T00:00:00.000Z",
"total_added": "171288.42", "total_added": "171288.42",
"total_removed": "29685.4825162206377", "total_removed": "30935.4825162206377",
"locked_amount": "0", "locked_amount": "0",
"deposits": [ "deposits": [
{ {
@ -51913,6 +51913,31 @@
"user": "0xcad0D46627628A8bAF524BF202669e8B9f04250f", "user": "0xcad0D46627628A8bAF524BF202669e8B9f04250f",
"tx": "0xa403d8af5a940c4abd4aadcc579e279218ae73b9c7fc3d7e4216d6aab5ba70eb" "tx": "0xa403d8af5a940c4abd4aadcc579e279218ae73b9c7fc3d7e4216d6aab5ba70eb"
}, },
{
"amount": "250",
"user": "0xEc925Fd72B46E2101D20686800343220c12Dedde",
"tx": "0xeaf8173b80d3ea3eb5c440a6edcc4fbf6d8624fa437203b66f6acb72d7462b5e"
},
{
"amount": "250",
"user": "0xbC10086CB362caE3071f2e09DF2aEE5265FdA238",
"tx": "0x1ea368a1c1f000d36d93627ccda127da12e4beade77d869aab7cadb7edfa354f"
},
{
"amount": "250",
"user": "0xDA83Ff6862ed0579248CEd14422C8B782732233b",
"tx": "0x7eb0ec3a5e3d8792b341881083f9046d550fdf0e92429b53bbdddfe439e83983"
},
{
"amount": "250",
"user": "0x2b28Fd98804e19cFe8d46f2d729C2A5195339fA4",
"tx": "0xd8c87e89222f0fea8933a403cd39756c6721d1e3d86a0bb7747b428f50c7f790"
},
{
"amount": "250",
"user": "0x0941FC05E3DA8d9dD29bCBB15F9FF3A16F342612",
"tx": "0xb3cffc52e62679835e65be5472f80bcfa949f7eb0c4c9948efc3271cca02fdae"
},
{ {
"amount": "183.6335597275", "amount": "183.6335597275",
"user": "0x690Fc36d52eD3f198F0eBDea1557333a1766f786", "user": "0x690Fc36d52eD3f198F0eBDea1557333a1766f786",
@ -59380,10 +59405,17 @@
"tx": "0x96a22c369645e8945b4047374a05332f4054b50a492c43092e8bb8e974a006b9" "tx": "0x96a22c369645e8945b4047374a05332f4054b50a492c43092e8bb8e974a006b9"
} }
], ],
"withdrawals": [], "withdrawals": [
{
"amount": "250",
"user": "0xEc925Fd72B46E2101D20686800343220c12Dedde",
"tranche_id": 6,
"tx": "0xeaf8173b80d3ea3eb5c440a6edcc4fbf6d8624fa437203b66f6acb72d7462b5e"
}
],
"total_tokens": "250", "total_tokens": "250",
"withdrawn_tokens": "0", "withdrawn_tokens": "250",
"remaining_tokens": "250" "remaining_tokens": "0"
}, },
{ {
"address": "0x08e9b80fCa090CB3E50d77964dc8777cD828253D", "address": "0x08e9b80fCa090CB3E50d77964dc8777cD828253D",
@ -59620,10 +59652,17 @@
"tx": "0x96a22c369645e8945b4047374a05332f4054b50a492c43092e8bb8e974a006b9" "tx": "0x96a22c369645e8945b4047374a05332f4054b50a492c43092e8bb8e974a006b9"
} }
], ],
"withdrawals": [], "withdrawals": [
{
"amount": "250",
"user": "0x2b28Fd98804e19cFe8d46f2d729C2A5195339fA4",
"tranche_id": 6,
"tx": "0xd8c87e89222f0fea8933a403cd39756c6721d1e3d86a0bb7747b428f50c7f790"
}
],
"total_tokens": "250", "total_tokens": "250",
"withdrawn_tokens": "0", "withdrawn_tokens": "250",
"remaining_tokens": "250" "remaining_tokens": "0"
}, },
{ {
"address": "0x61f908D9ee13a68983e5FECc8D9467232F5E9cB4", "address": "0x61f908D9ee13a68983e5FECc8D9467232F5E9cB4",
@ -59680,10 +59719,17 @@
"tx": "0x96a22c369645e8945b4047374a05332f4054b50a492c43092e8bb8e974a006b9" "tx": "0x96a22c369645e8945b4047374a05332f4054b50a492c43092e8bb8e974a006b9"
} }
], ],
"withdrawals": [], "withdrawals": [
{
"amount": "250",
"user": "0xDA83Ff6862ed0579248CEd14422C8B782732233b",
"tranche_id": 6,
"tx": "0x7eb0ec3a5e3d8792b341881083f9046d550fdf0e92429b53bbdddfe439e83983"
}
],
"total_tokens": "250", "total_tokens": "250",
"withdrawn_tokens": "0", "withdrawn_tokens": "250",
"remaining_tokens": "250" "remaining_tokens": "0"
}, },
{ {
"address": "0xbC10086CB362caE3071f2e09DF2aEE5265FdA238", "address": "0xbC10086CB362caE3071f2e09DF2aEE5265FdA238",
@ -59695,10 +59741,17 @@
"tx": "0x96a22c369645e8945b4047374a05332f4054b50a492c43092e8bb8e974a006b9" "tx": "0x96a22c369645e8945b4047374a05332f4054b50a492c43092e8bb8e974a006b9"
} }
], ],
"withdrawals": [], "withdrawals": [
{
"amount": "250",
"user": "0xbC10086CB362caE3071f2e09DF2aEE5265FdA238",
"tranche_id": 6,
"tx": "0x1ea368a1c1f000d36d93627ccda127da12e4beade77d869aab7cadb7edfa354f"
}
],
"total_tokens": "250", "total_tokens": "250",
"withdrawn_tokens": "0", "withdrawn_tokens": "250",
"remaining_tokens": "250" "remaining_tokens": "0"
}, },
{ {
"address": "0xEe3183EcE9ee7d73Fb7bA7F4eB262A2dE68C42B0", "address": "0xEe3183EcE9ee7d73Fb7bA7F4eB262A2dE68C42B0",
@ -62281,10 +62334,17 @@
"tx": "0x9f916cf09e8a3c4ade0ffce5190db464d0a2b1dadba78e1ee7ba5d6e751d6148" "tx": "0x9f916cf09e8a3c4ade0ffce5190db464d0a2b1dadba78e1ee7ba5d6e751d6148"
} }
], ],
"withdrawals": [], "withdrawals": [
{
"amount": "250",
"user": "0x0941FC05E3DA8d9dD29bCBB15F9FF3A16F342612",
"tranche_id": 6,
"tx": "0xb3cffc52e62679835e65be5472f80bcfa949f7eb0c4c9948efc3271cca02fdae"
}
],
"total_tokens": "250", "total_tokens": "250",
"withdrawn_tokens": "0", "withdrawn_tokens": "250",
"remaining_tokens": "250" "remaining_tokens": "0"
}, },
{ {
"address": "0x0a5688ed5b0b804db2ee18767476bb60598EC398", "address": "0x0a5688ed5b0b804db2ee18767476bb60598EC398",

View File

@ -38,7 +38,7 @@
"tranche_end": "2022-11-26T13:48:10.000Z", "tranche_end": "2022-11-26T13:48:10.000Z",
"total_added": "100", "total_added": "100",
"total_removed": "0", "total_removed": "0",
"locked_amount": "42.69090246067986", "locked_amount": "42.554014459665146",
"deposits": [ "deposits": [
{ {
"amount": "100", "amount": "100",
@ -242,7 +242,7 @@
"tranche_end": "2022-10-12T00:53:20.000Z", "tranche_end": "2022-10-12T00:53:20.000Z",
"total_added": "1100", "total_added": "1100",
"total_removed": "673.04388635", "total_removed": "673.04388635",
"locked_amount": "332.36188165905626", "locked_amount": "330.85611364789445",
"deposits": [ "deposits": [
{ {
"amount": "1000", "amount": "1000",

View File

@ -69,7 +69,7 @@
"tranche_end": "2022-10-12T00:53:20.000Z", "tranche_end": "2022-10-12T00:53:20.000Z",
"total_added": "1010.000000000000000001", "total_added": "1010.000000000000000001",
"total_removed": "668.4622323651", "total_removed": "668.4622323651",
"locked_amount": "305.16870084982244640030214722856418064", "locked_amount": "303.7861000126839150003007783168442415",
"deposits": [ "deposits": [
{ {
"amount": "1000", "amount": "1000",

View File

@ -1,29 +1,17 @@
# React Environment Variables
# https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables#expanding-environment-variables-in-env
# Netlify Environment Variables
# https://www.netlify.com/docs/continuous-deployment/#environment-variables
REACT_APP_VERSION=$npm_package_version
REACT_APP_REPOSITORY_URL=$REPOSITORY_URL
REACT_APP_BRANCH=$BRANCH
REACT_APP_PULL_REQUEST=$PULL_REQUEST
REACT_APP_HEAD=$HEAD
REACT_APP_COMMIT_REF=$COMMIT_REF
REACT_APP_CONTEXT=$CONTEXT
REACT_APP_REVIEW_ID=$REVIEW_ID
REACT_APP_INCOMING_HOOK_TITLE=$INCOMING_HOOK_TITLE
REACT_APP_INCOMING_HOOK_URL=$INCOMING_HOOK_URL
REACT_APP_INCOMING_HOOK_BODY=$INCOMING_HOOK_BODY
REACT_APP_URL=$URL
REACT_APP_DEPLOY_URL=$DEPLOY_URL
REACT_APP_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL
# App configuration variables # App configuration variables
NX_VEGA_ENV=TESTNET NX_VEGA_ENV=TESTNET
NX_VEGA_URL=https://lb.testnet.vega.xyz/query NX_ETHEREUM_PROVIDER_URL=http://localhost:8545
NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://ropsten.etherscan.io NX_ETHERSCAN_URL=https://ropsten.etherscan.io
NX_FAIRGROUND=false NX_FAIRGROUND=false
NX_IS_NEW_BRIDGE_CONTRACT=true
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET":"https://dev.token.vega.xyz","STAGNET2":"staging2.token.vega.xyz","TESTNET":"token.fairground.wtf","MAINNET":"token.vega.xyz"}'
NX_VEGA_URL=http://localhost:3028/query
NX_VEGA_REST=http://localhost:3029
NX_ETHEREUM_CHAIN_ID=1440
NX_ETH_URL_CONNECT=1
NX_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit supply green clown session
NX_LOCAL_PROVIDER_URL=http://localhost:8545/
#Test configuration variables #Test configuration variables
CYPRESS_FAIRGROUND=false CYPRESS_FAIRGROUND=false

View File

@ -1,23 +1,3 @@
# React Environment Variables
# https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables#expanding-environment-variables-in-env
# Netlify Environment Variables
# https://www.netlify.com/docs/continuous-deployment/#environment-variables
REACT_APP_VERSION=$npm_package_version
REACT_APP_REPOSITORY_URL=$REPOSITORY_URL
REACT_APP_BRANCH=$BRANCH
REACT_APP_PULL_REQUEST=$PULL_REQUEST
REACT_APP_HEAD=$HEAD
REACT_APP_COMMIT_REF=$COMMIT_REF
REACT_APP_CONTEXT=$CONTEXT
REACT_APP_REVIEW_ID=$REVIEW_ID
REACT_APP_INCOMING_HOOK_TITLE=$INCOMING_HOOK_TITLE
REACT_APP_INCOMING_HOOK_URL=$INCOMING_HOOK_URL
REACT_APP_INCOMING_HOOK_BODY=$INCOMING_HOOK_BODY
REACT_APP_URL=$URL
REACT_APP_DEPLOY_URL=$DEPLOY_URL
REACT_APP_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL
# App configuration variables # App configuration variables
NX_VEGA_ENV=TESTNET NX_VEGA_ENV=TESTNET
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/testnet-network.json NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/testnet-network.json

17
apps/token/.env.capsule Normal file
View File

@ -0,0 +1,17 @@
# App configuration variables
NX_VEGA_ENV=TESTNET
NX_ETHEREUM_PROVIDER_URL=http://localhost:8545
NX_ETHERSCAN_URL=https://ropsten.etherscan.io
NX_FAIRGROUND=false
NX_IS_NEW_BRIDGE_CONTRACT=true
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET":"https://dev.token.vega.xyz","STAGNET2":"staging2.token.vega.xyz","TESTNET":"token.fairground.wtf","MAINNET":"token.vega.xyz"}'
NX_VEGA_URL=http://localhost:3028/query
NX_VEGA_REST=http://localhost:3029
NX_ETHEREUM_CHAIN_ID=1440
NX_ETH_URL_CONNECT=1
NX_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit supply green clown session
NX_LOCAL_PROVIDER_URL=http://localhost:8545/
#Test configuration variables
CYPRESS_FAIRGROUND=false

View File

@ -38,23 +38,19 @@ yarn nx run token:serve --env={env} # e.g. stagnet1
There are a few different configuration options offered for this app: There are a few different configuration options offered for this app:
| **Flag** | **Purpose** | | **Flag** | **Purpose** |
| ------------------------------ | ---------------------------------------------------------------------------------------------------- | | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `NX_APP_SENTRY_DSN` | The sentry endpoint to report to. Should be off in dev but set in live. | | `NX_APP_SENTRY_DSN` | The sentry endpoint to report to. Should be off in dev but set in live. |
| `NX_APP_CHAIN` | The ETH chain for the app to work on. Should be mainnet for live, but ropsten for preview deploys. | | `NX_APP_CHAIN` | The ETH chain for the app to work on. Should be mainnet for live, but ropsten for preview deploys. |
| `NX_APP_VEGA_URL` | The GraphQL query endpoint of a [Vega data node](https://github.com/vegaprotocol/networks#data-node) | | `NX_APP_VEGA_URL` | The GraphQL query endpoint of a [Vega data node](https://github.com/vegaprotocol/networks#data-node) |
| `NX_APP_DEX_STAKING_DISABLED` | Disable the dex liquidity page an show a coming soon message | | `NX_APP_DEX_STAKING_DISABLED` | Disable the dex liquidity page an show a coming soon message |
| `NX_APP_FAIRGROUND` | Change styling to be themed as the fairground version of the website | | `NX_APP_FAIRGROUND` | Change styling to be themed as the fairground version of the website |
| `NX_APP_INFURA_ID` | Infura fallback for if the user does not have a web3 compatible browser | | `NX_APP_INFURA_ID` | Infura fallback for if the user does not have a web3 compatible browser |
| `NX_APP_HOSTED_WALLET_ENABLED` | If the hosted wallet is enabled or not. If so then allow users to login using the hosted wallet | | `NX_APP_HOSTED_WALLET_ENABLED` | If the hosted wallet is enabled or not. If so then allow users to login using the hosted wallet |
| `NX_APP_ENV` | Change network to connect to. When set to CUSTOM use CUSTOM\_\* vars for network parameters | | `NX_APP_ENV` | Change network to connect to. When set to CUSTOM use CUSTOM\_\* vars for network parameters |
| `NX_CUSTOM_URLS` | When NX_APP_ENV=CUSTOM use these Data Node REST URLs, optional if CUSTOM_URLS_WITH_GRAPHQL is used. | | `NX_ETH_URL_CONNECT` (optional) | If set to true the below two must also be set. This allows siging transactions in brower to allow to connect to a local ganache node through cypress |
| `NX_CUSTOM_URLS_WITH_GRAPHQL` | When NX_APP_ENV=CUSTOM use these Data Node GraphQL URLs, optional if CUSTOM_URLS is used. | | `NX_ETH_WALLET_MNEMONIC` (optional) | The mnemonic to be used to sign transactions with in browser |
| `NX_CUSTOM_TOKEN_ADDRESS` | When NX_APP_ENV=CUSTOM specify Vega token address. | | `NX_LOCAL_PROVIDER_URL` (optional) | The local node to use to send transaction to when signing in browser |
| `NX_CUSTOM_CLAIM_ADDRESS` | When NX_APP_ENV=CUSTOM specify Vega claim address. |
| `NX_CUSTOM_LOCKED_ADDRESS` | When NX_APP_ENV=CUSTOM specify Vega locked address. |
| `NX_CUSTOM_VESTING_ADDRESS` | When NX_APP_ENV=CUSTOM specify Vega vesting address. |
| `NX_CUSTOM_STAKING_BRIDGE` | When NX_APP_ENV=CUSTOM specify Vega staking bridge address. |
## Example configs: ## Example configs:

View File

@ -2,6 +2,7 @@ import { useWeb3React } from '@web3-react/core';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { getButtonClasses, Button } from '@vegaprotocol/ui-toolkit';
import { import {
AppStateActionType, AppStateActionType,
@ -70,7 +71,7 @@ const AssociatedAmounts = ({
rightLabel={t('notAssociated')} rightLabel={t('notAssociated')}
leftColor={Colors.white.DEFAULT} leftColor={Colors.white.DEFAULT}
rightColor={Colors.black.DEFAULT} rightColor={Colors.black.DEFAULT}
light={true} light={false}
/> />
{vestingAssociationByVegaKey.length ? ( {vestingAssociationByVegaKey.length ? (
<> <>
@ -129,6 +130,7 @@ const ConnectedKey = () => {
name="VEGA" name="VEGA"
symbol="In vesting contract" symbol="In vesting contract"
balance={totalInVestingContract} balance={totalInVestingContract}
dark={true}
/> />
<LockedProgress <LockedProgress
locked={totalLockedBalance} locked={totalLockedBalance}
@ -136,7 +138,7 @@ const ConnectedKey = () => {
total={totalVestedBalance.plus(totalLockedBalance)} total={totalVestedBalance.plus(totalLockedBalance)}
leftLabel={t('Locked')} leftLabel={t('Locked')}
rightLabel={t('Unlocked')} rightLabel={t('Unlocked')}
light={true} light={false}
/> />
</> </>
)} )}
@ -153,6 +155,7 @@ const ConnectedKey = () => {
name="VEGA" name="VEGA"
symbol="In Wallet" symbol="In Wallet"
balance={walletWithAssociations} balance={walletWithAssociations}
dark={true}
/> />
{!Object.keys( {!Object.keys(
appState.associationBreakdown.stakingAssociations appState.associationBreakdown.stakingAssociations
@ -163,15 +166,17 @@ const ConnectedKey = () => {
/> />
)} )}
<WalletCardActions> <WalletCardActions>
<Link className="flex-1" to={`${Routes.STAKING}/associate`}> <Link
<span className="flex items-center justify-center w-full text-center px-28 border h-28"> className={getButtonClasses('flex-1 mr-4', 'secondary')}
{t('associate')} to={`${Routes.STAKING}/associate`}
</span> >
{t('associate')}
</Link> </Link>
<Link className="flex-1" to={`${Routes.STAKING}/disassociate`}> <Link
<span className="flex items-center justify-center w-full px-28 border h-28"> className={getButtonClasses('flex-1 ml-4', 'secondary')}
{t('disassociate')} to={`${Routes.STAKING}/disassociate`}
</span> >
{t('disassociate')}
</Link> </Link>
</WalletCardActions> </WalletCardActions>
</> </>
@ -185,7 +190,7 @@ export const EthWallet = () => {
const pendingTxs = usePendingTransactions(); const pendingTxs = usePendingTransactions();
return ( return (
<WalletCard> <WalletCard dark={true}>
<WalletCardHeader> <WalletCardHeader>
<h1 className="text-h3 uppercase">{t('ethereumKey')}</h1> <h1 className="text-h3 uppercase">{t('ethereumKey')}</h1>
{account && ( {account && (
@ -203,7 +208,7 @@ export const EthWallet = () => {
}) })
} }
> >
<Loader size="small" forceTheme="light" /> <Loader size="small" forceTheme="dark" />
{t('pendingTransactions')} {t('pendingTransactions')}
</button> </button>
</div> </div>
@ -215,7 +220,8 @@ export const EthWallet = () => {
{account ? ( {account ? (
<ConnectedKey /> <ConnectedKey />
) : ( ) : (
<button <Button
variant={'secondary'}
className="w-full px-28 border h-28" className="w-full px-28 border h-28"
onClick={() => onClick={() =>
appDispatch({ appDispatch({
@ -226,7 +232,7 @@ export const EthWallet = () => {
data-test-id="connect-to-eth-wallet-button" data-test-id="connect-to-eth-wallet-button"
> >
{t('connectEthWalletToAssociate')} {t('connectEthWalletToAssociate')}
</button> </Button>
)} )}
{account && ( {account && (
<WalletCardActions> <WalletCardActions>

View File

@ -1,8 +1,8 @@
import React from 'react'; import React from 'react';
import { formatNumber } from '../../lib/format-number'; import { formatNumber } from '../../lib/format-number';
import type { BigNumber } from '../../lib/bignumber'; import type { BigNumber } from '../../lib/bignumber';
import { theme } from '@vegaprotocol/tailwindcss-config'; import { theme } from '@vegaprotocol/tailwindcss-config';
import classnames from 'classnames';
const Colors = theme.colors; const Colors = theme.colors;
@ -14,9 +14,10 @@ const ProgressContents = ({
children: React.ReactNode; children: React.ReactNode;
}) => ( }) => (
<div <div
className={`flex justify-between py-2 font-mono ${ className={classnames('flex justify-between py-2 font-mono', {
light ? 'gap-0 px-0 text-black' : 'gap-y-0 gap-x-4 px-4' 'gap-0 px-0 text-black': light,
}`} 'gap-y-0 gap-x-4': !light,
})}
> >
{children} {children}
</div> </div>
@ -25,17 +26,22 @@ const ProgressContents = ({
const ProgressIndicator = ({ const ProgressIndicator = ({
bgColor, bgColor,
side, side,
light,
}: { }: {
bgColor: string; bgColor: string;
side: 'left' | 'right'; side: 'left' | 'right';
light: boolean;
}) => ( }) => (
<span <span
style={{ style={{
backgroundColor: bgColor, backgroundColor: bgColor,
}} }}
className={`inline-block w-12 h-12 border border-black ${ className={classnames('inline-block w-12 h-12 border', {
side === 'left' ? 'mr-8' : 'ml-8' 'mr-8': side === 'left',
}`} 'ml-8': side === 'right',
'border-black': light,
'border-white': !light,
})}
/> />
); );
@ -64,6 +70,7 @@ export interface LockedProgressProps {
leftColor?: string; leftColor?: string;
rightColor?: string; rightColor?: string;
light?: boolean; light?: boolean;
decimals?: number;
} }
export const LockedProgress = ({ export const LockedProgress = ({
@ -73,8 +80,9 @@ export const LockedProgress = ({
leftLabel, leftLabel,
rightLabel, rightLabel,
leftColor = Colors.vega.pink, leftColor = Colors.vega.pink,
rightColor = Colors.green.DEFAULT, rightColor = Colors.vega.green,
light = false, light = false,
decimals = 2,
}: LockedProgressProps) => { }: LockedProgressProps) => {
const lockedPercentage = React.useMemo(() => { const lockedPercentage = React.useMemo(() => {
return locked.div(total).times(100); return locked.div(total).times(100);
@ -85,26 +93,35 @@ export const LockedProgress = ({
}, [total, unlocked]); }, [total, unlocked]);
return ( return (
<div className="border-x border-x-white"> <>
<div className={`flex ${light && 'border border-black'}`}> <div
className={classnames('flex border', {
'border-black': light,
'border-white': !light,
})}
>
<ProgressBar percentage={lockedPercentage} bgColor={leftColor} /> <ProgressBar percentage={lockedPercentage} bgColor={leftColor} />
<ProgressBar percentage={unlockedPercentage} bgColor={rightColor} /> <ProgressBar percentage={unlockedPercentage} bgColor={rightColor} />
</div> </div>
<ProgressContents light={light}> <ProgressContents light={light}>
<span> <span>
<ProgressIndicator bgColor={leftColor} side={'left'} /> <ProgressIndicator bgColor={leftColor} side={'left'} light={false} />
{leftLabel} {leftLabel}
</span> </span>
<span> <span>
{rightLabel} {rightLabel}
<ProgressIndicator bgColor={rightColor} side={'right'} /> <ProgressIndicator
bgColor={rightColor}
side={'right'}
light={false}
/>
</span> </span>
</ProgressContents> </ProgressContents>
<ProgressContents light={light}> <ProgressContents light={light}>
<span>{formatNumber(locked, 2)}</span> <span>{formatNumber(locked, decimals)}</span>
<span>{formatNumber(unlocked, 2)}</span> <span>{formatNumber(unlocked, decimals)}</span>
</ProgressContents> </ProgressContents>
</div> </>
); );
}; };

View File

@ -180,15 +180,15 @@ const VegaWalletConnected = ({ vegaKeys }: VegaWalletConnectedProps) => {
</div> </div>
))} ))}
<WalletCardActions> <WalletCardActions>
<Link style={{ flex: 1 }} to={Routes.GOVERNANCE}> <Link className="flex-1 pr-8" to={Routes.GOVERNANCE}>
<span className="flex items-center justify-center w-full px-28 border h-28 bg-white text-black"> <Button variant={'secondary'} className="w-full">
{t('governance')} {t('governance')}
</span> </Button>
</Link> </Link>
<Link style={{ flex: 1 }} to={Routes.STAKING}> <Link className="flex-1 pl-8" to={Routes.STAKING}>
<span className="flex items-center justify-center w-full px-28 border h-28 bg-white text-black"> <Button variant={'secondary'} className="w-full">
{t('staking')} {t('staking')}
</span> </Button>
</Link> </Link>
</WalletCardActions> </WalletCardActions>
<VegaWalletAssetList accounts={accounts} /> <VegaWalletAssetList accounts={accounts} />

View File

@ -55,6 +55,9 @@ export const ENV = {
commit: windowOrDefault('NX_COMMIT_REF'), commit: windowOrDefault('NX_COMMIT_REF'),
branch: windowOrDefault('NX_BRANCH'), branch: windowOrDefault('NX_BRANCH'),
vegaUrl: windowOrDefault('NX_VEGA_URL'), vegaUrl: windowOrDefault('NX_VEGA_URL'),
urlConnect: TRUTHY.includes(windowOrDefault('NX_ETH_URL_CONNECT')),
ethWalletMnemonic: windowOrDefault('NX_ETH_WALLET_MNEMONIC'),
localProviderUrl: windowOrDefault('NX_LOCAL_PROVIDER_URL'),
flags: { flags: {
NETWORK_DOWN: TRUTHY.includes(windowOrDefault('NX_NETWORK_DOWN')), NETWORK_DOWN: TRUTHY.includes(windowOrDefault('NX_NETWORK_DOWN')),
HOSTED_WALLET_ENABLED: TRUTHY.includes( HOSTED_WALLET_ENABLED: TRUTHY.includes(

View File

@ -1,10 +1,8 @@
import React from 'react'; import React from 'react';
import { useContracts } from '../../contexts/contracts/contracts-context';
import mock from './tranches-mock'; import mock from './tranches-mock';
import type { Tranche } from '@vegaprotocol/smart-contracts'; import type { Tranche } from '@vegaprotocol/smart-contracts';
export function useTranches() { export function useTranches() {
const { vesting } = useContracts();
const [tranches, setTranches] = React.useState<Tranche[] | null>(null); const [tranches, setTranches] = React.useState<Tranche[] | null>(null);
const [error, setError] = React.useState<string | null>(null); const [error, setError] = React.useState<string | null>(null);
@ -17,7 +15,7 @@ export function useTranches() {
} }
}; };
run(); run();
}, [vesting]); }, []);
return { tranches, error }; return { tranches, error };
} }

View File

@ -1,3 +1,4 @@
import { toBigNum } from '@vegaprotocol/react-helpers';
import React from 'react'; import React from 'react';
import { import {
@ -7,23 +8,26 @@ import {
import { useContracts } from '../contexts/contracts/contracts-context'; import { useContracts } from '../contexts/contracts/contracts-context';
export function useRefreshAssociatedBalances() { export function useRefreshAssociatedBalances() {
const { appDispatch } = useAppState(); const {
appDispatch,
appState: { decimals },
} = useAppState();
const { staking, vesting } = useContracts(); const { staking, vesting } = useContracts();
return React.useCallback( return React.useCallback(
async (ethAddress: string, vegaKey: string) => { async (ethAddress: string, vegaKey: string) => {
const [walletAssociatedBalance, vestingAssociatedBalance] = const [walletAssociatedBalance, vestingAssociatedBalance] =
await Promise.all([ await Promise.all([
staking.stakeBalance(ethAddress, `0x${vegaKey}`), staking.stakeBalance(ethAddress, vegaKey),
vesting.stakeBalance(ethAddress, `0x${vegaKey}`), vesting.stakeBalance(ethAddress, vegaKey),
]); ]);
appDispatch({ appDispatch({
type: AppStateActionType.REFRESH_ASSOCIATED_BALANCES, type: AppStateActionType.REFRESH_ASSOCIATED_BALANCES,
walletAssociatedBalance, walletAssociatedBalance: toBigNum(walletAssociatedBalance, decimals),
vestingAssociatedBalance, vestingAssociatedBalance: toBigNum(vestingAssociatedBalance, decimals),
}); });
}, },
[staking, vesting, appDispatch] [staking, vesting, appDispatch, decimals]
); );
} }

View File

@ -193,6 +193,7 @@
"noGovernanceTokens": "You need some VEGA tokens to participate in governance", "noGovernanceTokens": "You need some VEGA tokens to participate in governance",
"youVoted": "You voted", "youVoted": "You voted",
"changeVote": "Change vote", "changeVote": "Change vote",
"voteRequested": "Please confirm transaction in wallet",
"votePending": "Casting vote", "votePending": "Casting vote",
"voteError": "Something went wrong, and your vote was not seen by the network", "voteError": "Something went wrong, and your vote was not seen by the network",
"back": "back", "back": "back",
@ -280,6 +281,7 @@
"stakingHasAssociated": "You have {{tokens}} $VEGA tokens associated.", "stakingHasAssociated": "You have {{tokens}} $VEGA tokens associated.",
"stakingAssociateMoreButton": "Associate more $VEGA tokens with wallet", "stakingAssociateMoreButton": "Associate more $VEGA tokens with wallet",
"stakingDisassociateButton": "Disassociate $VEGA tokens from wallet", "stakingDisassociateButton": "Disassociate $VEGA tokens from wallet",
"stakingConfirm": "Open you wallet app to confirm",
"associateButton": "Associate $VEGA tokens with wallet", "associateButton": "Associate $VEGA tokens with wallet",
"nodeQueryFailed": "Could not get data for validator {{node}}", "nodeQueryFailed": "Could not get data for validator {{node}}",
"stakeAddPendingTitle": "Adding {{amount}} $VEGA to validator {{node}}", "stakeAddPendingTitle": "Adding {{amount}} $VEGA to validator {{node}}",
@ -540,5 +542,8 @@
"numberOfAgainstVotes": "Number of votes against", "numberOfAgainstVotes": "Number of votes against",
"yesPercentage": "Yes percentage", "yesPercentage": "Yes percentage",
"noPercentage": "No percentage", "noPercentage": "No percentage",
"proposalTerms": "Proposal terms" "proposalTerms": "Proposal terms",
"currentlySetTo": "Currently set to ",
"pass": "Pass",
"fail": "Fail"
} }

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" class="bg-black w-full h-full"> <html lang="en" class="dark bg-black w-full h-full">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="stylesheet" href="//static.vega.xyz/fonts.css" /> <link rel="stylesheet" href="//static.vega.xyz/fonts.css" />

View File

@ -0,0 +1,150 @@
import { ethers, Wallet } from 'ethers';
import { Connector } from '@web3-react/types';
import { Eip1193Bridge } from '@ethersproject/experimental';
import type { ConnectionInfo } from '@ethersproject/web';
import type { Actions } from '@web3-react/types';
import { ENV } from '../config/env';
export class CustomizedBridge extends Eip1193Bridge {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async sendAsync(...args: any) {
console.debug('sendAsync called', ...args);
return this.send(...args);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
override async send(...args: any) {
console.debug('send called', ...args);
const isCallbackForm =
typeof args[0] === 'object' && typeof args[1] === 'function';
let callback;
let method;
let params;
if (isCallbackForm) {
callback = args[1];
method = args[0].method;
params = args[0].params;
} else {
method = args[0];
params = args[1];
}
try {
// Hacky, https://github.com/ethers-io/ethers.js/issues/1683#issuecomment-1016227588
// If from is present on eth_call it errors, removing it makes the library set
// from as the connected wallet which works fine
if (params && params.length && params[0].from && method === 'eth_call')
delete params[0].from;
let result;
// For sending a transaction if we call send it will error
// as it wants gasLimit in sendTransaction but hexlify sets the property gas
// to gasLimit which makes sensd transaction error.
// This has taken the code from the super method for sendTransaction and altered
// it slightly to make it work with the gas limit issues.
if (
params &&
params.length &&
params[0].from &&
method === 'eth_sendTransaction'
) {
// Hexlify will not take gas, must be gasLimit, set this property to be gasLimit
params[0].gasLimit = params[0].gas;
delete params[0].gas;
// If from is present on eth_sendTransaction it errors, removing it makes the library set
// from as the connected wallet which works fine
delete params[0].from;
const req = ethers.providers.JsonRpcProvider.hexlifyTransaction(
params[0]
);
// Hexlify sets the gasLimit property to be gas again and send transaction requires gasLimit
req['gasLimit'] = req['gas'];
delete req['gas'];
if (!this.signer) {
throw new Error('No signer');
}
// Send the transaction
const tx = await this.signer.sendTransaction(req);
result = tx.hash;
} else {
// All other transactions the base class works for
result = await super.send(method, params);
}
console.debug('result received', method, params, result);
if (isCallbackForm) {
callback(null, { result });
} else {
return result;
}
} catch (error) {
console.error(error);
if (isCallbackForm) {
callback(error, null);
} else {
throw error;
}
}
}
}
type url = string | ConnectionInfo;
export class Url extends Connector {
/** {@inheritdoc Connector.provider} */
public override provider: Eip1193Bridge | undefined;
private eagerConnection?: Promise<void>;
private url: url;
/**
* @param url - An RPC url.
* @param connectEagerly - A flag indicating whether connection should be initiated when the class is constructed.
*/
constructor(actions: Actions, url: url, connectEagerly = false) {
super(actions);
if (connectEagerly && typeof window === 'undefined') {
throw new Error(
'connectEagerly = true is invalid for SSR, instead use the activate method in a useEffect'
);
}
this.url = url;
if (connectEagerly) void this.activate();
}
private async isomorphicInitialize() {
if (this.eagerConnection) return this.eagerConnection;
await (this.eagerConnection = import('@ethersproject/providers')
.then(({ JsonRpcProvider }) => JsonRpcProvider)
.then((JsonRpcProvider) => {
const provider = new JsonRpcProvider(this.url);
const privateKey = Wallet.fromMnemonic(
ENV.ethWalletMnemonic,
`m/44'/60'/0'/0/0`
).privateKey;
const signer = new Wallet(privateKey, provider);
this.provider = new CustomizedBridge(signer, provider);
this.actions.update({ accounts: [signer.address], chainId: 1440 });
}));
}
/** {@inheritdoc Connector.activate} */
public async activate(): Promise<void> {
this.actions.startActivation();
await this.isomorphicInitialize();
try {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const chainId = await this.provider!.request({ method: 'eth_chainId' });
this.actions.update({ chainId: Number(chainId) });
} catch (error) {
this.actions.reportError(error as Error);
}
}
}

View File

@ -3,11 +3,18 @@ import type { Web3ReactHooks } from '@web3-react/core';
import { initializeConnector } from '@web3-react/core'; import { initializeConnector } from '@web3-react/core';
import { MetaMask } from '@web3-react/metamask'; import { MetaMask } from '@web3-react/metamask';
import { WalletConnect } from '@web3-react/walletconnect'; import { WalletConnect } from '@web3-react/walletconnect';
import { Url } from './url-connector';
import type { Connector } from '@web3-react/types';
import { ENV } from '../config/env';
const [metamask, metamaskHooks] = initializeConnector<MetaMask>( const [metamask, metamaskHooks] = initializeConnector<MetaMask>(
(actions) => new MetaMask(actions) (actions) => new MetaMask(actions)
); );
const [urlConnector, urlHooks] = initializeConnector<Url>(
(actions) => new Url(actions, ENV.localProviderUrl)
);
export const createDefaultProvider = (providerUrl: string, chainId: number) => { export const createDefaultProvider = (providerUrl: string, chainId: number) => {
return new ethers.providers.JsonRpcProvider(providerUrl, chainId); return new ethers.providers.JsonRpcProvider(providerUrl, chainId);
}; };
@ -27,7 +34,8 @@ export const createConnectors = (providerUrl: string, chainId: number) => {
[chainId] [chainId]
); );
return [ return [
ENV.urlConnect ? [urlConnector, urlHooks] : null,
[metamask, metamaskHooks], [metamask, metamaskHooks],
[walletconnect, walletconnectHooks], [walletconnect, walletconnectHooks],
] as [MetaMask | WalletConnect, Web3ReactHooks][]; ].filter(Boolean) as [Connector, Web3ReactHooks][];
}; };

View File

@ -25,7 +25,7 @@ export const TargetAddressMismatch = ({
}} }}
components={{ components={{
bold: <strong />, bold: <strong />,
red: <span className={'text-red'} />, red: <span className={'text-vega-red'} />,
}} }}
/> />
</p> </p>

View File

@ -43,7 +43,7 @@ const Contracts = () => {
title={t('View address on Etherscan')} title={t('View address on Etherscan')}
href={`${ETHERSCAN_URL}/address/${contract.address}`} href={`${ETHERSCAN_URL}/address/${contract.address}`}
> >
{config.collateral_bridge_contract.address} {contract.address}
</Link> </Link>
</div> </div>
); );
@ -58,7 +58,6 @@ const Contracts = () => {
title={t('View address on Etherscan')} title={t('View address on Etherscan')}
href={`${ETHERSCAN_URL}/address/${value}`} href={`${ETHERSCAN_URL}/address/${value}`}
> >
asdfasd
{value} {value}
</Link> </Link>
</div> </div>

View File

@ -34,30 +34,22 @@ export const CurrentProposalStatus = ({
{ addSuffix: true } { addSuffix: true }
); );
if (proposal.state === ProposalState.Open && willPass) { if (proposal.state === ProposalState.Open) {
return <StatusPass>{t('shouldPass')}</StatusPass>; if (willPass) {
} return (
<>
if (!participationMet) { {t('currentlySetTo')}
return ( <StatusPass>{t('pass')}</StatusPass>
<> </>
<span>{t('voteFailedReason')}</span> );
<span className="current-proposal-status__fail"> } else {
{t('participationNotMet')} return (
</span> <>
<span>&nbsp;{daysClosedAgo}.</span> {t('currentlySetTo')}
</> <StatusFail>{t('fail')}</StatusFail>
); </>
} );
}
if (!majorityMet) {
return (
<>
<span>{t('voteFailedReason')}</span>
<StatusFail>{t('majorityNotMet')}</StatusFail>
<span>&nbsp;{daysClosedAgo}.</span>
</>
);
} }
if ( if (
@ -65,6 +57,26 @@ export const CurrentProposalStatus = ({
proposal.state === ProposalState.Declined || proposal.state === ProposalState.Declined ||
proposal.state === ProposalState.Rejected proposal.state === ProposalState.Rejected
) { ) {
if (!participationMet) {
return (
<>
<span>{t('voteFailedReason')}</span>
<StatusFail>{t('participationNotMet')}</StatusFail>
<span>&nbsp;{daysClosedAgo}.</span>
</>
);
}
if (!majorityMet) {
return (
<>
<span>{t('voteFailedReason')}</span>
<StatusFail>{t('majorityNotMet')}</StatusFail>
<span>&nbsp;{daysClosedAgo}.</span>
</>
);
}
return ( return (
<> <>
<span>{t('voteFailedReason')}</span> <span>{t('voteFailedReason')}</span>

View File

@ -17,6 +17,7 @@ export enum VoteState {
NotCast = 'NotCast', NotCast = 'NotCast',
Yes = 'Yes', Yes = 'Yes',
No = 'No', No = 'No',
Requested = 'Requested',
Pending = 'Pending', Pending = 'Pending',
Failed = 'Failed', Failed = 'Failed',
} }
@ -69,7 +70,7 @@ export function useUserVote(
} }
}, [userVote]); }, [userVote]);
// Start a starts a timeout of 30s to set a failed message if // Starts a timeout of 30s to set a failed message if
// the vote is not seen by the time the callback is invoked // the vote is not seen by the time the callback is invoked
React.useEffect(() => { React.useEffect(() => {
// eslint-disable-next-line // eslint-disable-next-line
@ -93,7 +94,7 @@ export function useUserVote(
async function castVote(value: VoteValue) { async function castVote(value: VoteValue) {
if (!proposalId || !keypair) return; if (!proposalId || !keypair) return;
setVoteState(VoteState.Pending); setVoteState(VoteState.Requested);
try { try {
const variables = { const variables = {
@ -105,6 +106,7 @@ export function useUserVote(
}, },
}; };
await sendTx(variables); await sendTx(variables);
setVoteState(VoteState.Pending);
// Now await vote via poll in parent component // Now await vote via poll in parent component
} catch (err) { } catch (err) {

View File

@ -124,6 +124,10 @@ export const VoteButtons = ({
return <p>{cantVoteUI}</p>; return <p>{cantVoteUI}</p>;
} }
if (voteState === VoteState.Requested) {
return <p>{t('voteRequested')}...</p>;
}
if (voteState === VoteState.Pending) { if (voteState === VoteState.Pending) {
return <p>{t('votePending')}...</p>; return <p>{t('votePending')}...</p>;
} }

View File

@ -49,7 +49,7 @@ export const VoteDetails = ({ proposal }: VoteDetailsProps) => {
<span> <span>
<CurrentProposalStatus proposal={proposal} /> <CurrentProposalStatus proposal={proposal} />
</span> </span>
.&nbsp; {'. '}
{proposal.state === ProposalState.Open ? daysLeft : null} {proposal.state === ProposalState.Open ? daysLeft : null}
</p> </p>
<table className="w-full font-normal mb-12"> <table className="w-full font-normal mb-12">

View File

@ -8,7 +8,6 @@ import { Link } from 'react-router-dom';
import { EthConnectPrompt } from '../../components/eth-connect-prompt'; import { EthConnectPrompt } from '../../components/eth-connect-prompt';
import { SplashLoader } from '../../components/splash-loader'; import { SplashLoader } from '../../components/splash-loader';
import { useAppState } from '../../contexts/app-state/app-state-context'; import { useAppState } from '../../contexts/app-state/app-state-context';
import { useContracts } from '../../contexts/contracts/contracts-context';
import { useTranches } from '../../hooks/use-tranches'; import { useTranches } from '../../hooks/use-tranches';
import { Routes as RoutesConfig } from '../router-config'; import { Routes as RoutesConfig } from '../router-config';
import { import {
@ -19,7 +18,6 @@ import {
const RedemptionRouter = () => { const RedemptionRouter = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { vesting } = useContracts();
const [state, dispatch] = React.useReducer( const [state, dispatch] = React.useReducer(
redemptionReducer, redemptionReducer,
initialRedemptionState initialRedemptionState
@ -49,7 +47,7 @@ const RedemptionRouter = () => {
if (account) { if (account) {
run(account); run(account);
} }
}, [account, tranches, vesting]); }, [account, tranches]);
if (error) { if (error) {
return ( return (

View File

@ -76,6 +76,7 @@ export const TrancheItem = ({
total={total} total={total}
leftLabel={t('Locked')} leftLabel={t('Locked')}
rightLabel={t('Unlocked')} rightLabel={t('Unlocked')}
decimals={18}
/> />
<div className="text-right" data-testid="tranche-item-footer"> <div className="text-right" data-testid="tranche-item-footer">

View File

@ -12,7 +12,7 @@ import { useTransaction } from '../../../hooks/use-transaction';
import { BigNumber } from '../../../lib/bignumber'; import { BigNumber } from '../../../lib/bignumber';
import { AssociateInfo } from './associate-info'; import { AssociateInfo } from './associate-info';
import type { VegaKeyExtended } from '@vegaprotocol/wallet'; import type { VegaKeyExtended } from '@vegaprotocol/wallet';
import { toBigNum } from '@vegaprotocol/react-helpers'; import { removeDecimal, toBigNum } from '@vegaprotocol/react-helpers';
import type { EthereumConfig } from '@vegaprotocol/web3'; import type { EthereumConfig } from '@vegaprotocol/web3';
export const WalletAssociate = ({ export const WalletAssociate = ({
@ -35,19 +35,18 @@ export const WalletAssociate = ({
appDispatch, appDispatch,
appState: { walletBalance, allowance, walletAssociatedBalance, decimals }, appState: { walletBalance, allowance, walletAssociatedBalance, decimals },
} = useAppState(); } = useAppState();
const { token } = useContracts(); const { token } = useContracts();
const { const {
state: approveState, state: approveState,
perform: approve, perform: approve,
dispatch: approveDispatch, dispatch: approveDispatch,
} = useTransaction(() => } = useTransaction(() => {
token.approve( return token.approve(
ethereumConfig.staking_bridge_contract.address, ethereumConfig.staking_bridge_contract.address,
Number.MAX_SAFE_INTEGER.toString() removeDecimal('1000000', decimals).toString()
) );
); });
// Once they have approved deposits then we need to refresh their allowance // Once they have approved deposits then we need to refresh their allowance
React.useEffect(() => { React.useEffect(() => {

View File

@ -8,6 +8,7 @@ import {
} from '../../../components/staking-method-radio'; } from '../../../components/staking-method-radio';
import { TxState } from '../../../hooks/transaction-reducer'; import { TxState } from '../../../hooks/transaction-reducer';
import { useSearchParams } from '../../../hooks/use-search-params'; import { useSearchParams } from '../../../hooks/use-search-params';
import { useRefreshAssociatedBalances } from '../../../hooks/use-refresh-associated-balances';
import { ContractDisassociate } from './contract-disassociate'; import { ContractDisassociate } from './contract-disassociate';
import { DisassociateTransaction } from './disassociate-transaction'; import { DisassociateTransaction } from './disassociate-transaction';
import { useRemoveStake } from './hooks'; import { useRemoveStake } from './hooks';
@ -26,6 +27,7 @@ export const DisassociatePage = ({
const [amount, setAmount] = React.useState<string>(''); const [amount, setAmount] = React.useState<string>('');
const [selectedStakingMethod, setSelectedStakingMethod] = const [selectedStakingMethod, setSelectedStakingMethod] =
React.useState<StakingMethod | null>(params.method || null); React.useState<StakingMethod | null>(params.method || null);
const refreshBalances = useRefreshAssociatedBalances();
// Clear the amount when the staking method changes // Clear the amount when the staking method changes
React.useEffect(() => { React.useEffect(() => {
@ -38,6 +40,12 @@ export const DisassociatePage = ({
perform: txPerform, perform: txPerform,
} = useRemoveStake(address, amount, vegaKey.pub, selectedStakingMethod); } = useRemoveStake(address, amount, vegaKey.pub, selectedStakingMethod);
React.useEffect(() => {
if (txState.txState === TxState.Complete) {
refreshBalances(address, vegaKey.pub);
}
}, [txState, refreshBalances, address, vegaKey.pub]);
if (txState.txState !== TxState.Default) { if (txState.txState !== TxState.Default) {
return ( return (
<DisassociateTransaction <DisassociateTransaction

View File

@ -18,7 +18,14 @@ import type {
import { StakeFailure } from './stake-failure'; import { StakeFailure } from './stake-failure';
import { StakePending } from './stake-pending'; import { StakePending } from './stake-pending';
import { StakeSuccess } from './stake-success'; import { StakeSuccess } from './stake-success';
import { Button, FormGroup, RadioGroup, Radio } from '@vegaprotocol/ui-toolkit'; import {
Button,
Callout,
FormGroup,
Intent,
Radio,
RadioGroup,
} from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet';
import type { import type {
DelegateSubmissionBody, DelegateSubmissionBody,
@ -46,6 +53,7 @@ export const PARTY_DELEGATIONS_QUERY = gql`
enum FormState { enum FormState {
Default, Default,
Requested,
Pending, Pending,
Success, Success,
Failure, Failure,
@ -115,7 +123,7 @@ export const StakingForm = ({
}, [action, availableStakeToAdd, availableStakeToRemove]); }, [action, availableStakeToAdd, availableStakeToRemove]);
async function onSubmit() { async function onSubmit() {
setFormState(FormState.Pending); setFormState(FormState.Requested);
const delegateInput: DelegateSubmissionBody = { const delegateInput: DelegateSubmissionBody = {
pubKey: pubkey, pubKey: pubkey,
propagate: true, propagate: true,
@ -139,6 +147,7 @@ export const StakingForm = ({
try { try {
const command = action === Actions.Add ? delegateInput : undelegateInput; const command = action === Actions.Add ? delegateInput : undelegateInput;
await sendTx(command); await sendTx(command);
setFormState(FormState.Pending);
// await success via poll // await success via poll
} catch (err) { } catch (err) {
@ -184,6 +193,12 @@ export const StakingForm = ({
if (formState === FormState.Failure) { if (formState === FormState.Failure) {
return <StakeFailure nodeName={nodeName} />; return <StakeFailure nodeName={nodeName} />;
} else if (formState === FormState.Requested) {
return (
<Callout title="Confirm transaction in wallet" intent={Intent.Warning}>
<p>{t('stakingConfirm')}</p>
</Callout>
);
} else if (formState === FormState.Pending) { } else if (formState === FormState.Pending) {
return <StakePending action={action} amount={amount} nodeName={nodeName} />; return <StakePending action={action} amount={amount} nodeName={nodeName} />;
} else if (formState === FormState.Success) { } else if (formState === FormState.Success) {

View File

@ -88,7 +88,9 @@ export const StakingNode = ({ vegaKey, data }: StakingNodeProps) => {
if (!nodeInfo) { if (!nodeInfo) {
return ( return (
<span className={'text-red'}>{t('stakingNodeNotFound', { node })}</span> <span className={'text-vega-red'}>
{t('stakingNodeNotFound', { node })}
</span>
); );
} }

View File

@ -40,7 +40,7 @@ export const TrancheProgress = ({
<span className="tranches__progress-title">{t('Redeemed')}</span> <span className="tranches__progress-title">{t('Redeemed')}</span>
<ProgressBar <ProgressBar
width={220} width={220}
color={Colors.green.DEFAULT} color={Colors.vega.green}
percentage={removedPercentage} percentage={removedPercentage}
/> />
<span className="tranches__progress-numbers"> <span className="tranches__progress-numbers">

View File

@ -36,7 +36,7 @@ export const VestingChart = () => {
['pink', Colors.vega.pink], ['pink', Colors.vega.pink],
['green', Colors.vega.green], ['green', Colors.vega.green],
['orange', Colors.orange], ['orange', Colors.orange],
['yellow', Colors.yellow.DEFAULT], ['yellow', Colors.vega.yellow],
].map(([key, color]) => ( ].map(([key, color]) => (
<linearGradient key={key} id={key} x1="0" y1="0" x2="0" y2="1"> <linearGradient key={key} id={key} x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor={color} stopOpacity={0.85} /> <stop offset="0%" stopColor={color} stopOpacity={0.85} />
@ -119,7 +119,7 @@ export const VestingChart = () => {
dot={false} dot={false}
type="monotone" type="monotone"
dataKey="publicSale" dataKey="publicSale"
stroke={Colors.yellow.DEFAULT} stroke={Colors.vega.yellow}
fill="url(#yellow)" fill="url(#yellow)"
yAxisId={0} yAxisId={0}
strokeWidth={2} strokeWidth={2}

View File

@ -76,6 +76,7 @@ export const generateOrders = (override?: PartialDeep<Orders>): Orders => {
id: 'c6f4337b31ed57a961969c3ba10297b369d01b9e75a4cbb96db4fc62886444e6', id: 'c6f4337b31ed57a961969c3ba10297b369d01b9e75a4cbb96db4fc62886444e6',
name: 'BTCUSD Monthly (30 Jun 2022)', name: 'BTCUSD Monthly (30 Jun 2022)',
decimalPlaces: 5, decimalPlaces: 5,
positionDecimalPlaces: 0,
tradableInstrument: { tradableInstrument: {
__typename: 'TradableInstrument', __typename: 'TradableInstrument',
instrument: { instrument: {

View File

@ -1,8 +1,8 @@
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { Vega } from '../icons/vega'; import { Vega } from '../icons/vega';
import Link from 'next/link'; import Link from 'next/link';
import { AnchorButton } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/react-helpers'; import { t } from '@vegaprotocol/react-helpers';
import classNames from 'classnames';
export const Navbar = () => { export const Navbar = () => {
return ( return (
@ -33,14 +33,17 @@ const NavLink = ({ name, path, exact, testId = name }: NavLinkProps) => {
const router = useRouter(); const router = useRouter();
const isActive = const isActive =
router.asPath === path || (!exact && router.asPath.startsWith(path)); router.asPath === path || (!exact && router.asPath.startsWith(path));
const linkClasses = classNames(
'px-16 py-6 border-0 self-end',
'uppercase xs:text-ui sm:text-body-large md:text-h5 lg:text-h4',
{
'bg-vega-pink dark:bg-vega-yellow text-white dark:text-black': isActive,
'text-black dark:text-white': !isActive,
}
);
return ( return (
<AnchorButton <Link data-testid={testId} href={path} passHref={true}>
variant={isActive ? 'accent' : 'inline'} <a className={linkClasses}>{name}</a>
className="px-16 py-6 h-[38px] uppercase border-0 self-end xs:text-ui sm:text-body-large md:text-h5 lg:text-h4" </Link>
data-testid={testId}
href={path}
>
{name}
</AnchorButton>
); );
}; };

View File

@ -24,84 +24,80 @@ function AppBody({ Component, pageProps }: AppProps) {
const { push } = useRouter(); const { push } = useRouter();
const store = useGlobalStore(); const store = useGlobalStore();
const { VEGA_NETWORKS } = useEnvironment(); const { VEGA_NETWORKS } = useEnvironment();
const [, toggleTheme] = useThemeSwitcher(); const [theme, toggleTheme] = useThemeSwitcher();
return ( return (
<div className="h-full dark:bg-black dark:text-white-60 bg-white relative z-0 text-black-60 grid grid-rows-[min-content,1fr]"> <ThemeContext.Provider value={theme}>
<AppLoader> <div className="h-full dark:bg-black dark:text-white-60 bg-white relative z-0 text-black-60 grid grid-rows-[min-content,1fr]">
<div className="flex items-stretch border-b-[7px] border-vega-yellow"> <AppLoader>
<Navbar /> <div className="flex items-stretch border-b-[7px] border-vega-pink dark:border-vega-yellow">
<div className="flex items-center gap-4 ml-auto mr-8"> <Navbar />
<VegaWalletConnectButton <div className="flex items-center gap-4 ml-auto mr-8">
setConnectDialog={(open) => { <VegaWalletConnectButton
store.setVegaWalletConnectDialog(open); setConnectDialog={(open) => {
}} store.setVegaWalletConnectDialog(open);
setManageDialog={(open) => { }}
store.setVegaWalletManageDialog(open); setManageDialog={(open) => {
}} store.setVegaWalletManageDialog(open);
/> }}
<ThemeSwitcher onToggle={toggleTheme} className="-my-4" /> />
<ThemeSwitcher onToggle={toggleTheme} className="-my-4" />
</div>
</div> </div>
</div> <main data-testid={pageProps.page}>
<main data-testid={pageProps.page}> {/* @ts-ignore conflict between @types/react and nextjs internal types */}
{/* @ts-ignore conflict between @types/react and nextjs internal types */} <Component {...pageProps} />
<Component {...pageProps} /> </main>
</main> <VegaConnectDialog
<VegaConnectDialog connectors={Connectors}
connectors={Connectors} dialogOpen={store.vegaWalletConnectDialog}
dialogOpen={store.vegaWalletConnectDialog} setDialogOpen={(open) => store.setVegaWalletConnectDialog(open)}
setDialogOpen={(open) => store.setVegaWalletConnectDialog(open)} />
/> <VegaManageDialog
<VegaManageDialog dialogOpen={store.vegaWalletManageDialog}
dialogOpen={store.vegaWalletManageDialog} setDialogOpen={(open) => store.setVegaWalletManageDialog(open)}
setDialogOpen={(open) => store.setVegaWalletManageDialog(open)} />
/> <NetworkSwitcherDialog
<NetworkSwitcherDialog dialogOpen={store.vegaNetworkSwitcherDialog}
dialogOpen={store.vegaNetworkSwitcherDialog} setDialogOpen={(open) => store.setVegaNetworkSwitcherDialog(open)}
setDialogOpen={(open) => store.setVegaNetworkSwitcherDialog(open)} onConnect={({ network }) => {
onConnect={({ network }) => { if (VEGA_NETWORKS[network]) {
if (VEGA_NETWORKS[network]) { push(VEGA_NETWORKS[network] ?? '');
push(VEGA_NETWORKS[network] ?? ''); }
} }}
}} />
/> </AppLoader>
</AppLoader> </div>
</div> </ThemeContext.Provider>
); );
} }
function VegaTradingApp(props: AppProps) { function VegaTradingApp(props: AppProps) {
const [theme] = useThemeSwitcher();
return ( return (
<EnvironmentProvider> <EnvironmentProvider>
<ThemeContext.Provider value={theme}> <VegaWalletProvider>
<VegaWalletProvider> <Head>
<Head> <link
<link rel="preload"
rel="preload" href="https://static.vega.xyz/AlphaLyrae-Medium.woff2"
href="https://static.vega.xyz/AlphaLyrae-Medium.woff2" as="font"
as="font" type="font/woff2"
type="font/woff2" crossOrigin="anonymous"
crossOrigin="anonymous" />
/> <title>{t('Welcome to Vega trading!')}</title>
<title>{t('Welcome to Vega trading!')}</title> <link
<link rel="icon"
rel="icon" type="image/x-icon"
type="image/x-icon" href="https://static.vega.xyz/favicon.ico"
href="https://static.vega.xyz/favicon.ico" />
/> <link rel="stylesheet" href="https://static.vega.xyz/fonts.css" />
<link rel="stylesheet" href="https://static.vega.xyz/fonts.css" /> {['1', 'true'].includes(process.env['NX_USE_ENV_OVERRIDES'] || '') ? (
{['1', 'true'].includes( /* eslint-disable-next-line @next/next/no-sync-scripts */
process.env['NX_USE_ENV_OVERRIDES'] || '' <script src="./env-config.js" type="text/javascript" />
) ? ( ) : null}
/* eslint-disable-next-line @next/next/no-sync-scripts */ </Head>
<script src="./env-config.js" type="text/javascript" /> <AppBody {...props} />
) : null} </VegaWalletProvider>
</Head>
<AppBody {...props} />
</VegaWalletProvider>
</ThemeContext.Provider>
</EnvironmentProvider> </EnvironmentProvider>
); );
} }

View File

@ -18,8 +18,8 @@ import { CandlesChartContainer } from '@vegaprotocol/candles-chart';
import { SelectMarketDialog } from '@vegaprotocol/market-list'; import { SelectMarketDialog } from '@vegaprotocol/market-list';
import { import {
ArrowDown, ArrowDown,
GridTab, Tab,
GridTabs, Tabs,
PriceCellChange, PriceCellChange,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import type { CandleClose } from '@vegaprotocol/types'; import type { CandleClose } from '@vegaprotocol/types';
@ -66,7 +66,7 @@ export const TradeMarketHeader = ({
<div className="flex flex-col md:flex-row gap-20 md:gap-64 ml-auto mr-8"> <div className="flex flex-col md:flex-row gap-20 md:gap-64 ml-auto mr-8">
<button <button
onClick={() => setOpen(!open)} onClick={() => setOpen(!open)}
className="shrink-0 dark:text-vega-yellow text-black text-h5 flex items-center gap-8 px-4 py-0 h-37 hover:bg-vega-yellow dark:hover:bg-white/20" className="shrink-0 dark:text-vega-yellow text-black text-h5 flex items-center gap-8 px-4 py-0 h-37 hover:bg-black/20 dark:hover:bg-white/20"
> >
<span className="break-words text-left">{market.name}</span> <span className="break-words text-left">{market.name}</span>
<ArrowDown color="yellow" borderX={8} borderTop={12} /> <ArrowDown color="yellow" borderX={8} borderTop={12} />
@ -122,47 +122,47 @@ export const TradeGrid = ({ market }: TradeGridProps) => {
className="row-start-1 row-end-2 col-start-1 col-end-4" className="row-start-1 row-end-2 col-start-1 col-end-4"
/> />
<TradeGridChild className="row-start-2 row-end-3 col-start-1 col-end-2"> <TradeGridChild className="row-start-2 row-end-3 col-start-1 col-end-2">
<GridTabs> <Tabs>
<GridTab id="candles" name={t('Candles')}> <Tab id="candles" name={t('Candles')}>
<TradingViews.Candles marketId={market.id} /> <TradingViews.Candles marketId={market.id} />
</GridTab> </Tab>
<GridTab id="depth" name={t('Depth')}> <Tab id="depth" name={t('Depth')}>
<TradingViews.Depth marketId={market.id} /> <TradingViews.Depth marketId={market.id} />
</GridTab> </Tab>
</GridTabs> </Tabs>
</TradeGridChild> </TradeGridChild>
<TradeGridChild className="row-start-2 row-end-3 col-start-2 col-end-3"> <TradeGridChild className="row-start-2 row-end-3 col-start-2 col-end-3">
<GridTabs> <Tabs>
<GridTab id="ticket" name={t('Ticket')}> <Tab id="ticket" name={t('Ticket')}>
<TradingViews.Ticket marketId={market.id} /> <TradingViews.Ticket marketId={market.id} />
</GridTab> </Tab>
<GridTab id="info" name={t('Info')}> <Tab id="info" name={t('Info')}>
<TradingViews.Info marketId={market.id} /> <TradingViews.Info marketId={market.id} />
</GridTab> </Tab>
</GridTabs> </Tabs>
</TradeGridChild> </TradeGridChild>
<TradeGridChild className="row-start-2 row-end-3 col-start-3 col-end-4"> <TradeGridChild className="row-start-2 row-end-3 col-start-3 col-end-4">
<GridTabs> <Tabs>
<GridTab id="trades" name={t('Trades')}> <Tab id="trades" name={t('Trades')}>
<TradingViews.Trades marketId={market.id} /> <TradingViews.Trades marketId={market.id} />
</GridTab> </Tab>
<GridTab id="orderbook" name={t('Orderbook')}> <Tab id="orderbook" name={t('Orderbook')}>
<TradingViews.Orderbook marketId={market.id} /> <TradingViews.Orderbook marketId={market.id} />
</GridTab> </Tab>
</GridTabs> </Tabs>
</TradeGridChild> </TradeGridChild>
<TradeGridChild className="col-span-3"> <TradeGridChild className="col-span-3">
<GridTabs> <Tabs>
<GridTab id="orders" name={t('Orders')}> <Tab id="orders" name={t('Orders')}>
<TradingViews.Orders /> <TradingViews.Orders />
</GridTab> </Tab>
<GridTab id="positions" name={t('Positions')}> <Tab id="positions" name={t('Positions')}>
<TradingViews.Positions /> <TradingViews.Positions />
</GridTab> </Tab>
<GridTab id="accounts" name={t('Accounts')}> <Tab id="accounts" name={t('Accounts')}>
<TradingViews.Accounts /> <TradingViews.Accounts />
</GridTab> </Tab>
</GridTabs> </Tabs>
</TradeGridChild> </TradeGridChild>
</div> </div>
</> </>

View File

@ -3,8 +3,7 @@ import { t } from '@vegaprotocol/react-helpers';
import { PositionsContainer } from '@vegaprotocol/positions'; import { PositionsContainer } from '@vegaprotocol/positions';
import { OrderListContainer } from '@vegaprotocol/order-list'; import { OrderListContainer } from '@vegaprotocol/order-list';
import { AccountsContainer } from '@vegaprotocol/accounts'; import { AccountsContainer } from '@vegaprotocol/accounts';
import { AnchorButton, GridTab, GridTabs } from '@vegaprotocol/ui-toolkit'; import { AnchorButton, Tab, Tabs } from '@vegaprotocol/ui-toolkit';
import { WithdrawalsContainer } from './withdrawals/withdrawals-container'; import { WithdrawalsContainer } from './withdrawals/withdrawals-container';
const Portfolio = () => { const Portfolio = () => {
@ -20,54 +19,54 @@ const Portfolio = () => {
</h2> </h2>
</aside> </aside>
<section data-testid="portfolio-grid"> <section data-testid="portfolio-grid">
<GridTabs> <Tabs>
<GridTab id="positions" name={t('Positions')}> <Tab id="positions" name={t('Positions')}>
<div className={tabClassName}> <div className={tabClassName}>
<h4 className="text-h4 text-black dark:text-white"> <h4 className="text-h4 text-black dark:text-white">
{t('Positions')} {t('Positions')}
</h4> </h4>
<PositionsContainer /> <PositionsContainer />
</div> </div>
</GridTab> </Tab>
<GridTab id="orders" name={t('Orders')}> <Tab id="orders" name={t('Orders')}>
<div className={tabClassName}> <div className={tabClassName}>
<h4 className="text-h4 text-black dark:text-white"> <h4 className="text-h4 text-black dark:text-white">
{t('Orders')} {t('Orders')}
</h4> </h4>
<OrderListContainer /> <OrderListContainer />
</div> </div>
</GridTab> </Tab>
<GridTab id="fills" name={t('Fills')}> <Tab id="fills" name={t('Fills')}>
<div className={tabClassName}> <div className={tabClassName}>
<h4 className="text-h4 text-black dark:text-white"> <h4 className="text-h4 text-black dark:text-white">
{t('Fills')} {t('Fills')}
</h4> </h4>
</div> </div>
</GridTab> </Tab>
<GridTab id="history" name={t('History')}> <Tab id="history" name={t('History')}>
<div className={tabClassName}> <div className={tabClassName}>
<h4 className="text-h4 text-black dark:text-white"> <h4 className="text-h4 text-black dark:text-white">
{t('History')} {t('History')}
</h4> </h4>
</div> </div>
</GridTab> </Tab>
</GridTabs> </Tabs>
</section> </section>
</main> </main>
<section className="fixed bottom-0 left-0 w-full h-[200px]"> <section className="fixed bottom-0 left-0 w-full h-[200px]">
<GridTabs> <Tabs>
<GridTab id="collateral" name={t('Collateral')}> <Tab id="collateral" name={t('Collateral')}>
<AccountsContainer /> <AccountsContainer />
</GridTab> </Tab>
<GridTab id="deposits" name={t('Deposits')}> <Tab id="deposits" name={t('Deposits')}>
<AnchorButton data-testid="deposit" href="/portfolio/deposit"> <AnchorButton data-testid="deposit" href="/portfolio/deposit">
{t('Deposit')} {t('Deposit')}
</AnchorButton> </AnchorButton>
</GridTab> </Tab>
<GridTab id="withdrawals" name={t('Withdrawals')}> <Tab id="withdrawals" name={t('Withdrawals')}>
<WithdrawalsContainer /> <WithdrawalsContainer />
</GridTab> </Tab>
</GridTabs> </Tabs>
</section> </section>
</div> </div>
</Web3Container> </Web3Container>

View File

@ -17,7 +17,6 @@ import { useContext, useMemo, useState } from 'react';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet';
import { ThemeContext } from '@vegaprotocol/react-helpers'; import { ThemeContext } from '@vegaprotocol/react-helpers';
import { import {
Button,
DropdownMenu, DropdownMenu,
DropdownMenuCheckboxItem, DropdownMenuCheckboxItem,
DropdownMenuContent, DropdownMenuContent,
@ -58,14 +57,14 @@ export const CandlesChartContainer = ({
return new VegaDataSource(client, marketId, keypair?.pub); return new VegaDataSource(client, marketId, keypair?.pub);
}, [client, marketId, keypair]); }, [client, marketId, keypair]);
const dropdownTriggerStyles = 'border-black-60 dark:border-white-60 px-20';
return ( return (
<div className="h-full flex flex-col"> <div className="h-full flex flex-col">
<div className="p-8 flex flex-row flex-wrap gap-8"> <div className="p-8 flex flex-row flex-wrap gap-8">
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild={true}> <DropdownMenuTrigger className={dropdownTriggerStyles}>
<Button appendIconName="caret-down" variant="secondary"> {t('Interval')}
{t('Interval')}
</Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent> <DropdownMenuContent>
<DropdownMenuRadioGroup <DropdownMenuRadioGroup
@ -90,10 +89,8 @@ export const CandlesChartContainer = ({
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild={true}> <DropdownMenuTrigger className={dropdownTriggerStyles}>
<Button appendIconName="caret-down" variant="secondary"> <Icon name={chartTypeIcon.get(chartType) as IconName} />
<Icon name={chartTypeIcon.get(chartType) as IconName} />
</Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent> <DropdownMenuContent>
<DropdownMenuRadioGroup <DropdownMenuRadioGroup
@ -114,10 +111,8 @@ export const CandlesChartContainer = ({
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild={true}> <DropdownMenuTrigger className={dropdownTriggerStyles}>
<Button appendIconName="caret-down" variant="secondary"> {t('Overlays')}
{t('Overlays')}
</Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent> <DropdownMenuContent>
{Object.values(Overlay).map((overlay) => ( {Object.values(Overlay).map((overlay) => (
@ -145,10 +140,8 @@ export const CandlesChartContainer = ({
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild={true}> <DropdownMenuTrigger className={dropdownTriggerStyles}>
<Button appendIconName="caret-down" variant="secondary"> {t('Studies')}
{t('Studies')}
</Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent> <DropdownMenuContent>
{Object.values(Study).map((study) => ( {Object.values(Study).map((study) => (

View File

@ -109,7 +109,7 @@ export const DealTicket = ({
)} )}
<Button <Button
className="w-full mb-8" className="w-full mb-8"
variant="primary" variant="trade"
type="submit" type="submit"
disabled={isDisabled} disabled={isDisabled}
data-testid="place-order" data-testid="place-order"

View File

@ -37,7 +37,6 @@ beforeEach(() => {
submitDeposit: jest.fn(), submitDeposit: jest.fn(),
requestFaucet: jest.fn(), requestFaucet: jest.fn(),
limits: { limits: {
min: new BigNumber(0),
max: new BigNumber(20), max: new BigNumber(20),
}, },
allowance: new BigNumber(30), allowance: new BigNumber(30),
@ -199,6 +198,6 @@ it('Deposit', async () => {
// @ts-ignore contract address definitely defined // @ts-ignore contract address definitely defined
assetSource: asset.source.contractAddress, assetSource: asset.source.contractAddress,
amount: '1500', amount: '1500',
vegaPublicKey: `0x${vegaKey}`, vegaPublicKey: vegaKey,
}); });
}); });

View File

@ -89,7 +89,7 @@ export const DepositForm = ({
submitDeposit({ submitDeposit({
assetSource: selectedAsset.source.contractAddress, assetSource: selectedAsset.source.contractAddress,
amount: removeDecimal(fields.amount, selectedAsset.decimals), amount: removeDecimal(fields.amount, selectedAsset.decimals),
vegaPublicKey: `0x${fields.to}`, vegaPublicKey: fields.to,
}); });
}; };

View File

@ -1,6 +1,7 @@
import type { BigNumber } from 'ethers'; import type { BigNumber } from 'ethers';
import { ethers } from 'ethers'; import { ethers } from 'ethers';
import abi from '../abis/erc20_bridge_new_abi.json'; import abi from '../abis/erc20_bridge_new_abi.json';
import { prepend0x } from '../utils';
export class CollateralBridgeNew { export class CollateralBridgeNew {
public contract: ethers.Contract; public contract: ethers.Contract;
@ -14,7 +15,11 @@ export class CollateralBridgeNew {
} }
depositAsset(assetSource: string, amount: string, vegaPublicKey: string) { depositAsset(assetSource: string, amount: string, vegaPublicKey: string) {
return this.contract.deposit_asset(assetSource, amount, vegaPublicKey); return this.contract.deposit_asset(
assetSource,
amount,
prepend0x(vegaPublicKey)
);
} }
getAssetSource(vegaAssetId: string) { getAssetSource(vegaAssetId: string) {
return this.contract.get_asset_source(vegaAssetId); return this.contract.get_asset_source(vegaAssetId);

View File

@ -1,6 +1,7 @@
import type { BigNumber } from 'ethers'; import type { BigNumber } from 'ethers';
import { ethers } from 'ethers'; import { ethers } from 'ethers';
import abi from '../abis/erc20_bridge_abi.json'; import abi from '../abis/erc20_bridge_abi.json';
import { prepend0x } from '../utils';
export class CollateralBridge { export class CollateralBridge {
public contract: ethers.Contract; public contract: ethers.Contract;
@ -16,7 +17,11 @@ export class CollateralBridge {
} }
depositAsset(assetSource: string, amount: string, vegaPublicKey: string) { depositAsset(assetSource: string, amount: string, vegaPublicKey: string) {
return this.contract.deposit_asset(assetSource, amount, vegaPublicKey); return this.contract.deposit_asset(
assetSource,
amount,
prepend0x(vegaPublicKey)
);
} }
getAssetSource(vegaAssetId: string) { getAssetSource(vegaAssetId: string) {
return this.contract.get_asset_source(vegaAssetId); return this.contract.get_asset_source(vegaAssetId);

View File

@ -1,5 +1,6 @@
import { ethers } from 'ethers'; import { ethers } from 'ethers';
import abi from '../abis/staking_abi.json'; import abi from '../abis/staking_abi.json';
import { prepend0x } from '../utils';
export class StakingBridge { export class StakingBridge {
public contract: ethers.Contract; public contract: ethers.Contract;
@ -14,19 +15,23 @@ export class StakingBridge {
} }
stake(amount: string, vegaPublicKey: string) { stake(amount: string, vegaPublicKey: string) {
return this.contract.stake(amount, `0x${vegaPublicKey}`); return this.contract.stake(amount, prepend0x(vegaPublicKey));
} }
removeStake(amount: string, vegaPublicKey: string) { removeStake(amount: string, vegaPublicKey: string) {
return this.contract.remove_stake(amount, `0x${vegaPublicKey}`); return this.contract.remove_stake(amount, prepend0x(vegaPublicKey));
} }
transferStake(amount: string, newAddress: string, vegaPublicKey: string) { transferStake(amount: string, newAddress: string, vegaPublicKey: string) {
return this.contract.transfer_stake(amount, newAddress, vegaPublicKey); return this.contract.transfer_stake(
amount,
newAddress,
prepend0x(vegaPublicKey)
);
} }
stakingToken() { stakingToken() {
return this.contract.staking_token(); return this.contract.staking_token();
} }
stakeBalance(target: string, vegaPublicKey: string) { stakeBalance(target: string, vegaPublicKey: string) {
return this.contract.stake_balance(target, vegaPublicKey); return this.contract.stake_balance(target, prepend0x(vegaPublicKey));
} }
totalStaked() { totalStaked() {
return this.contract.total_staked(); return this.contract.total_staked();

View File

@ -1,5 +1,6 @@
import { ethers } from 'ethers'; import { ethers } from 'ethers';
import abi from '../abis/vesting_abi.json'; import abi from '../abis/vesting_abi.json';
import { prepend0x } from '../utils';
export class TokenVesting { export class TokenVesting {
public contract: ethers.Contract; public contract: ethers.Contract;
@ -14,13 +15,13 @@ export class TokenVesting {
} }
stakeTokens(amount: string, vegaPublicKey: string) { stakeTokens(amount: string, vegaPublicKey: string) {
return this.contract.stake_tokens(amount, vegaPublicKey); return this.contract.stake_tokens(amount, prepend0x(vegaPublicKey));
} }
removeStake(amount: string, vegaPublicKey: string) { removeStake(amount: string, vegaPublicKey: string) {
return this.contract.remove_stake(amount, vegaPublicKey); return this.contract.remove_stake(amount, prepend0x(vegaPublicKey));
} }
stakeBalance(address: string, vegaPublicKey: string) { stakeBalance(address: string, vegaPublicKey: string) {
return this.contract.stake_balance(address, vegaPublicKey); return this.contract.stake_balance(address, prepend0x(vegaPublicKey));
} }
totalStaked() { totalStaked() {
return this.contract.total_staked(); return this.contract.total_staked();

View File

@ -1,6 +0,0 @@
import { hexadecimalify } from './hexadecimalify';
test('Prepends strings with 0x', () => {
expect(hexadecimalify('abc')).toEqual('0xabc');
expect(hexadecimalify('123456789')).toEqual('0x123456789');
});

View File

@ -1,3 +0,0 @@
export function hexadecimalify(str: string) {
return `0x${str}`;
}

View File

@ -1,2 +1,2 @@
export * from './ascii-to-hex'; export * from './ascii-to-hex';
export * from './hexadecimalify'; export * from './prepend-0x';

View File

@ -0,0 +1,6 @@
import { prepend0x } from './prepend-0x';
test('Prepends strings with 0x', () => {
expect(prepend0x('abc')).toEqual('0xabc');
expect(prepend0x('123456789')).toEqual('0x123456789');
});

View File

@ -0,0 +1,3 @@
export function prepend0x(str: string) {
return `0x${str}`;
}

View File

@ -6,6 +6,73 @@ const shadeOfGray = (shade) => {
return `#${hexValue}${hexValue}${hexValue}`; return `#${hexValue}${hexValue}${hexValue}`;
}; };
const colours = {
transparent: 'transparent',
current: 'currentColor',
text: '#C7C7C7',
deemphasise: '#8A9BA8',
white: {
DEFAULT: '#FFF',
strong: '#FFF',
normal: '#F5F8FA',
muted: '#676767',
'02': shadeOfGray(2),
'05': shadeOfGray(5),
10: shadeOfGray(10),
25: shadeOfGray(25),
40: shadeOfGray(40),
60: shadeOfGray(60),
70: shadeOfGray(70),
80: shadeOfGray(80),
90: shadeOfGray(90),
95: shadeOfGray(95),
100: shadeOfGray(100),
},
black: {
DEFAULT: '#000',
strong: '#000',
normal: '#000',
muted: '#BFCCD6',
'02': shadeOfGray(100 - 2),
'05': shadeOfGray(100 - 5),
10: shadeOfGray(100 - 10),
25: shadeOfGray(100 - 25),
40: shadeOfGray(100 - 40),
50: shadeOfGray(100 - 50),
60: shadeOfGray(100 - 60),
70: shadeOfGray(100 - 70),
80: shadeOfGray(100 - 80),
90: shadeOfGray(100 - 90),
95: shadeOfGray(100 - 95),
100: shadeOfGray(100 - 100),
},
vega: {
yellow: '#DFFF0B',
'yellow-dark': '#474B0A',
pink: '#FF077F',
green: '#00F780',
'green-medium': '#00DE73',
'green-dark': '#008545',
red: '#FF261A',
'red-dark': '#EB001B',
},
blue: '#1DA2FB',
coral: '#FF6057',
pink: '#FF2D5E',
orange: '#D9822B',
danger: '#FF261A',
warning: '#FF7A1A',
selected: '#DFFF0B',
success: '#00F780',
'danger-bg': '#9E0025', // for white text
};
const boxShadowPosition = {
outer: '2px 2px 0 0',
insetUnderline: 'inset 0 -2px 0 0',
insetShading: 'inset 2px 2px 6px',
};
module.exports = { module.exports = {
screens: { screens: {
xs: '500px', xs: '500px',
@ -16,67 +83,7 @@ module.exports = {
xxl: '1536px', xxl: '1536px',
}, },
colors: { colors: {
transparent: 'transparent', ...colours,
current: 'currentColor',
vega: {
yellow: '#EDFF22',
pink: '#FF2D5E',
green: '#00F780',
red: '#FF261A',
},
red: {
DEFAULT: '#ED1515',
dark: '#EB001B',
bar: 'rgba(47, 246, 139, 0.45)', // #2FF68B 45%
},
green: {
DEFAULT: '#26FF8A',
dark: '#008545',
bar: 'rgba(47, 246, 139, 0.45)', // #2FF68B 45%
},
text: '#C7C7C7',
deemphasise: '#8A9BA8',
white: {
DEFAULT: '#FFF',
strong: '#FFF',
normal: '#F5F8FA',
muted: '#676767',
'02': shadeOfGray(2),
'05': shadeOfGray(5),
10: shadeOfGray(10),
25: shadeOfGray(25),
40: shadeOfGray(40),
60: shadeOfGray(60),
80: shadeOfGray(80),
95: shadeOfGray(95),
100: shadeOfGray(100),
},
black: {
DEFAULT: '#000',
strong: '#000',
normal: '#000',
muted: '#BFCCD6',
'02': shadeOfGray(100 - 2),
'05': shadeOfGray(100 - 5),
10: shadeOfGray(100 - 10),
25: shadeOfGray(100 - 25),
40: shadeOfGray(100 - 40),
60: shadeOfGray(100 - 60),
80: shadeOfGray(100 - 80),
95: shadeOfGray(100 - 95),
100: shadeOfGray(100 - 100),
},
blue: '#1DA2FB',
coral: '#FF6057',
orange: '#D9822B',
yellow: {
DEFAULT: '#EDFF22',
dark: '#474B0A', // yellow 0.3 opacity on black
},
danger: '#FF261A',
warning: '#FF7A1A',
success: '#26FF8A',
'danger-bg': '#9E0025', // for white text
}, },
spacing: { spacing: {
0: '0px', 0: '0px',
@ -121,7 +128,9 @@ module.exports = {
borderWidth: { borderWidth: {
DEFAULT: '1px', DEFAULT: '1px',
1: '1px', 1: '1px',
2: '2px',
4: '4px', 4: '4px',
7: '7px',
}, },
borderRadius: { borderRadius: {
none: '0', none: '0',
@ -178,11 +187,27 @@ module.exports = {
ui: ['14px', '20px'], ui: ['14px', '20px'],
'ui-small': ['12px', '16px'], 'ui-small': ['12px', '16px'],
}, },
boxShadow: { boxShadow: {
callout: '5px 5px 0 1px rgba(255, 255, 255, 0.05)',
focus: '0px 0px 0px 1px #FFFFFF, 0px 0px 3px 2px #FFE600', focus: '0px 0px 0px 1px #FFFFFF, 0px 0px 3px 2px #FFE600',
'focus-dark': '0px 0px 0px 1px #000000, 0px 0px 3px 2px #FFE600', 'focus-dark': '0px 0px 0px 1px #000000, 0px 0px 3px 2px #FFE600',
radio: '1px 1px 0 0', intent: `3px 3px 0 0`,
'vega-yellow': `${boxShadowPosition.outer} ${colours.vega.yellow}`,
'vega-pink': `${boxShadowPosition.outer} ${colours.vega.pink}`,
'inset-black': `${boxShadowPosition.insetUnderline} ${colours.black.DEFAULT}`,
'inset-white': `${boxShadowPosition.insetUnderline} ${colours.white.DEFAULT}`,
'inset-vega-yellow': `${boxShadowPosition.insetUnderline} ${colours.vega.yellow}`,
'inset-vega-pink': `${boxShadowPosition.insetUnderline} ${colours.vega.pink}`,
'inset-danger': `${boxShadowPosition.insetUnderline} ${colours.danger}`,
input: `${boxShadowPosition.insetShading} ${colours.white['80']}`,
'input-dark': `${boxShadowPosition.insetShading} ${colours.black['80']}`,
'input-focus': `${boxShadowPosition.insetShading} ${colours.white['80']}, ${boxShadowPosition.insetUnderline} ${colours.vega.pink}`,
'input-focus-dark': `${boxShadowPosition.insetShading} ${colours.black['80']}, ${boxShadowPosition.insetUnderline} ${colours.vega.yellow}`,
'input-focus-error': `${boxShadowPosition.insetShading} ${colours.white['80']}, ${boxShadowPosition.insetUnderline} ${colours.danger}`,
'input-focus-error-dark': `${boxShadowPosition.insetShading} ${colours.black['80']}, ${boxShadowPosition.insetUnderline} ${colours.danger}`,
'checkbox-focus': `${boxShadowPosition.insetShading} ${colours.white['80']}, ${boxShadowPosition.outer} ${colours.vega.pink}`,
'checkbox-focus-dark': `${boxShadowPosition.insetShading} ${colours.black['80']}, ${boxShadowPosition.outer} ${colours.vega.yellow}`,
},
backgroundImage: {
'fairground-nav': "url('https://static.vega.xyz/fairground-nav-bg.jpg')",
}, },
}; };

View File

@ -29,6 +29,25 @@ const vegaCustomClasses = plugin(function ({ addUtilities }) {
'.syntax-highlighter-wrapper .hljs-string': { '.syntax-highlighter-wrapper .hljs-string': {
color: theme.colors.blue, color: theme.colors.blue,
}, },
'.input-border': {
borderWidth: '1px',
borderStyle: 'solid',
borderTopColor: theme.colors.black['60'],
borderLeftColor: theme.colors.black['60'],
borderRightColor: theme.colors.black['40'],
borderBottomColor: theme.colors.black['40'],
},
'.input-border-dark': {
borderWidth: '1px',
borderStyle: 'solid',
borderTopColor: theme.colors.white['40'],
borderLeftColor: theme.colors.white['40'],
borderRightColor: theme.colors.white['60'],
borderBottomColor: theme.colors.white['60'],
},
'.color-scheme-dark': {
colorScheme: 'dark',
},
}); });
}); });

View File

@ -14,7 +14,7 @@ import BigNumber from 'bignumber.js';
import { sortTrades } from './trades-data-provider'; import { sortTrades } from './trades-data-provider';
export const UP_CLASS = 'text-vega-green'; export const UP_CLASS = 'text-vega-green';
export const DOWN_CLASS = 'text-vega-pink'; export const DOWN_CLASS = 'text-vega-red';
const changeCellClass = const changeCellClass =
(dataKey: string) => (dataKey: string) =>

View File

@ -9,7 +9,7 @@ const agGridLightVariables = `
--ag-header-background-color: ${theme.colors.white[100]}; --ag-header-background-color: ${theme.colors.white[100]};
--ag-odd-row-background-color: ${theme.colors.white[100]}; --ag-odd-row-background-color: ${theme.colors.white[100]};
--ag-row-border-color: ${theme.colors.white[100]}; --ag-row-border-color: ${theme.colors.white[100]};
--ag-row-hover-color: ${theme.colors.yellow.DEFAULT}; --ag-row-hover-color: ${theme.colors.black[10]};
--ag-font-size: 12px; --ag-font-size: 12px;
} }

View File

@ -11,7 +11,7 @@ const Template: Story = (args) => (
<div className="mb-8"> <div className="mb-8">
<Button {...args} /> <Button {...args} />
</div> </div>
{args['variant'] !== 'inline' && <Button {...args} disabled />} {args['variant'] !== 'inline-link' && <Button {...args} disabled />}
</> </>
); );
@ -26,18 +26,18 @@ Secondary.args = {
variant: 'secondary', variant: 'secondary',
}; };
export const Trade = Template.bind({});
Trade.args = {
children: 'Trade',
variant: 'trade',
};
export const Accent = Template.bind({}); export const Accent = Template.bind({});
Accent.args = { Accent.args = {
children: 'Accent', children: 'Accent',
variant: 'accent', variant: 'accent',
}; };
export const Inline = Template.bind({});
Inline.args = {
children: 'Inline',
variant: 'inline',
};
export const InlineLink = Template.bind({}); export const InlineLink = Template.bind({});
InlineLink.args = { InlineLink.args = {
children: 'Inline link', children: 'Inline link',
@ -67,13 +67,13 @@ export const NavAccent: Story = () => (
export const NavInline: Story = () => ( export const NavInline: Story = () => (
<> <>
<div className="mb-8"> <div className="mb-8">
<Button variant="inline" className="uppercase"> <Button variant="inline-link" className="uppercase">
Background Background
</Button> </Button>
</div> </div>
<div className="mb-8"> <div className="mb-8">
<Button <Button
variant="inline" variant="inline-link"
className="uppercase" className="uppercase"
prependIconName="menu-open" prependIconName="menu-open"
> >
@ -82,7 +82,7 @@ export const NavInline: Story = () => (
</div> </div>
<div className="mb-8"> <div className="mb-8">
<Button <Button
variant="inline" variant="inline-link"
className="uppercase" className="uppercase"
appendIconName="menu-closed" appendIconName="menu-closed"
> >
@ -96,12 +96,33 @@ export const IconPrepend = Template.bind({});
IconPrepend.args = { IconPrepend.args = {
children: 'Icon prepend', children: 'Icon prepend',
prependIconName: 'search', prependIconName: 'search',
variant: 'accent', variant: 'trade',
}; };
export const IconAppend = Template.bind({}); export const IconAppend = Template.bind({});
IconAppend.args = { IconAppend.args = {
children: 'Icon append', children: 'Icon append',
appendIconName: 'search', appendIconName: 'search',
variant: 'accent', variant: 'trade',
};
export const InlineIconPrepend = Template.bind({});
InlineIconPrepend.args = {
children: 'Icon prepend',
prependIconName: 'search',
variant: 'inline-link',
};
export const InlineIconAppend = Template.bind({});
InlineIconAppend.args = {
children: 'Icon append',
appendIconName: 'search',
variant: 'inline-link',
};
export const SpanWithButtonStyleAndContent = Template.bind({});
SpanWithButtonStyleAndContent.args = {
children: 'Apply button styles to other elements (i.e. span, <Link>)',
appendIconName: 'search',
variant: 'trade',
}; };

View File

@ -1,4 +1,8 @@
import type { AnchorHTMLAttributes, ButtonHTMLAttributes } from 'react'; import type {
AnchorHTMLAttributes,
ButtonHTMLAttributes,
ReactNode,
} from 'react';
import { forwardRef } from 'react'; import { forwardRef } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import type { IconName } from '../icon'; import type { IconName } from '../icon';
@ -9,13 +13,15 @@ import {
includesBorderWidth, includesBorderWidth,
includesHeight, includesHeight,
} from '../../utils/class-names'; } from '../../utils/class-names';
import classnames from 'classnames';
interface CommonProps { interface CommonProps {
children?: React.ReactNode; children?: ReactNode;
variant?: 'primary' | 'secondary' | 'accent' | 'inline' | 'inline-link'; variant?: 'primary' | 'secondary' | 'trade' | 'accent' | 'inline-link';
className?: string; className?: string;
prependIconName?: IconName; prependIconName?: IconName;
appendIconName?: IconName; appendIconName?: IconName;
boxShadow?: boolean;
} }
export interface ButtonProps export interface ButtonProps
extends ButtonHTMLAttributes<HTMLButtonElement>, extends ButtonHTMLAttributes<HTMLButtonElement>,
@ -25,21 +31,30 @@ export interface AnchorButtonProps
extends AnchorHTMLAttributes<HTMLAnchorElement>, extends AnchorHTMLAttributes<HTMLAnchorElement>,
CommonProps {} CommonProps {}
const getClasses = ( export const getButtonClasses = (
variant: CommonProps['variant'], className?: string,
paddingLeftProvided: boolean, variant?: 'primary' | 'secondary' | 'trade' | 'accent' | 'inline-link',
paddingRightProvided: boolean, boxShadow?: boolean
borderWidthProvided: boolean,
heightProvided: boolean
) => { ) => {
const paddingLeftProvided = includesLeftPadding(className);
const paddingRightProvided = includesRightPadding(className);
const borderWidthProvided = includesBorderWidth(className);
const heightProvided = includesHeight(className);
// Add classes into variables if there are multiple classes shared in multiple button styles // Add classes into variables if there are multiple classes shared in multiple button styles
const sharedClasses = const sharedClasses =
'inline-flex items-center justify-center box-border transition-all disabled:no-underline'; 'inline-flex items-center justify-center box-border transition-[background-color] ease-linear duration-50 disabled:no-underline';
const underlineOnHover = 'no-underline hover:underline'; const commonButtonClasses = classnames(
const commonHoverAndActiveBorder = 'relative disabled:static',
'hover:border-black dark:hover:border-white active:border-black dark:active:border-white'; 'text-ui font-semibold focus-visible:outline-none border no-underline hover:no-underline',
{
'shadow-none': !boxShadow,
'shadow-[3px_3px_0_0] focus-visible:shadow-vega-pink dark:focus-visible:shadow-vega-yellow active:top-[1px] active:left-[1px] active:shadow-[2px_2px_0_0]':
boxShadow === undefined || boxShadow,
}
);
const commonDisabled = const commonDisabled =
'disabled:bg-black-10 dark:disabled:bg-white-10 disabled:text-black-60 dark:disabled:text-white-60 disabled:border-black-25 dark:disabled:border-white-25'; 'disabled:bg-black-10 dark:disabled:bg-white-10 disabled:text-black-60 dark:disabled:text-white-60 disabled:border-black-25 dark:disabled:border-white-25 disabled:shadow-none dark:disabled:shadow-none';
const inlineTextColour = const inlineTextColour =
'text-black-95 dark:text-white-95 hover:text-black hover:dark:text-white active:text-black dark:active:text-vega-yellow'; 'text-black-95 dark:text-white-95 hover:text-black hover:dark:text-white active:text-black dark:active:text-vega-yellow';
@ -62,104 +77,92 @@ const getClasses = (
const primaryClasses = [ const primaryClasses = [
sharedClasses, sharedClasses,
commonHoverAndActiveBorder, commonButtonClasses,
underlineOnHover,
commonDisabled, commonDisabled,
standardButtonPaddingLeft, standardButtonPaddingLeft,
standardButtonPaddingRight, standardButtonPaddingRight,
standardButtonBorderWidth, standardButtonBorderWidth,
buttonHeight, buttonHeight,
'bg-black dark:bg-white hover:bg-black-80 dark:hover:bg-white-80 active:bg-white dark:active:bg-black', 'bg-black dark:bg-white hover:bg-black-80 dark:hover:bg-white-90 active:bg-black-80 dark:active:bg-white-90',
'text-ui text-white dark:text-black active:text-black dark:active:text-white', 'border-white dark:border-black shadow-black active:shadow-black dark:shadow-white-80 dark:active:shadow-white',
'text-white dark:text-black',
]; ];
const secondaryClasses = [ const secondaryClasses = [
sharedClasses, sharedClasses,
commonHoverAndActiveBorder, commonButtonClasses,
underlineOnHover,
commonDisabled, commonDisabled,
standardButtonPaddingLeft, standardButtonPaddingLeft,
standardButtonPaddingRight, standardButtonPaddingRight,
standardButtonBorderWidth, standardButtonBorderWidth,
buttonHeight, buttonHeight,
'bg-white dark:bg-black hover:bg-black-25 dark:hover:bg-white-25 active:bg-black dark:active:bg-white', 'bg-white dark:bg-black hover:bg-black-25 dark:hover:bg-white-25',
'text-ui text-black dark:text-white active:text-white dark:active:text-black', 'border-black dark:border-white shadow-black dark:shadow-white',
'border-black-60 dark:border-white-60 hover:border-black', 'text-black dark:text-white',
];
const tradeClasses = [
sharedClasses,
commonButtonClasses,
commonDisabled,
standardButtonPaddingLeft,
standardButtonPaddingRight,
standardButtonBorderWidth,
buttonHeight,
'bg-vega-green hover:bg-vega-green-medium',
'border-black disabled:shadow-none dark:disabled:shadow-none shadow-black dark:shadow-white',
'text-black',
]; ];
const accentClasses = [ const accentClasses = [
sharedClasses, sharedClasses,
commonHoverAndActiveBorder,
underlineOnHover,
commonDisabled, commonDisabled,
standardButtonPaddingLeft, standardButtonPaddingLeft,
standardButtonPaddingRight, standardButtonPaddingRight,
standardButtonBorderWidth, standardButtonBorderWidth,
buttonHeight, buttonHeight,
'bg-vega-yellow dark:bg-vega-yellow hover:bg-yellow/dark dark:hover:bg-vega-yellow/30 active:bg-white dark:active:bg-black', 'bg-vega-yellow dark:bg-vega-yellow hover:bg-vega-yellow-dark dark:hover:bg-vega-yellow-dark active:bg-white dark:active:bg-black',
'text-ui uppercase text-black dark:text-black hover:text-white dark:hover:text-white active:text-black dark:active:text-white', 'uppercase text-black dark:text-black hover:text-white dark:hover:text-white active:text-black dark:active:text-white',
'border-transparent dark:border-transparent', 'border-transparent dark:border-transparent',
]; ];
const inlineClasses = [
sharedClasses,
inlineButtonPaddingLeft,
inlineButtonPaddingRight,
buttonHeight,
inlineTextColour,
'border-none',
'text-ui',
];
const inlineLinkClasses = [ const inlineLinkClasses = [
sharedClasses, sharedClasses,
inlineButtonPaddingLeft, inlineButtonPaddingLeft,
inlineButtonPaddingRight, inlineButtonPaddingRight,
buttonHeight, buttonHeight,
inlineTextColour, inlineTextColour,
'underline hover:underline', 'underline hover:underline hover:text-black-60 dark:hover:text-white-80',
'border-none', 'border-none',
]; ];
let variantClasses: string[];
switch (variant) { switch (variant) {
case 'primary': case 'primary':
return primaryClasses; variantClasses = primaryClasses;
break;
case 'secondary': case 'secondary':
return secondaryClasses; variantClasses = secondaryClasses;
break;
case 'trade':
variantClasses = tradeClasses;
break;
case 'accent': case 'accent':
return accentClasses; variantClasses = accentClasses;
case 'inline': break;
return inlineClasses;
case 'inline-link': case 'inline-link':
return inlineLinkClasses; variantClasses = inlineLinkClasses;
break;
default: default:
return ''; variantClasses = [''];
} }
return classNames(...variantClasses, className);
}; };
const classes = ( export const getButtonContent = (
className: CommonProps['className'], children: ReactNode,
variant: CommonProps['variant']
) => {
const paddingLeftProvided = includesLeftPadding(className);
const paddingRightProvided = includesRightPadding(className);
const borderWidthProvided = includesBorderWidth(className);
const heightProvided = includesHeight(className);
return classNames(
getClasses(
variant,
paddingLeftProvided,
paddingRightProvided,
borderWidthProvided,
heightProvided
),
className
);
};
const getContent = (
children: React.ReactNode,
prependIconName?: IconName, prependIconName?: IconName,
appendIconName?: IconName appendIconName?: IconName
) => { ) => {
@ -190,6 +193,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
className, className,
prependIconName, prependIconName,
appendIconName, appendIconName,
boxShadow,
...props ...props
}, },
ref ref
@ -197,11 +201,11 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
return ( return (
<button <button
ref={ref} ref={ref}
className={classes(className, variant)} className={getButtonClasses(className, variant, boxShadow)}
type={type} type={type}
{...props} {...props}
> >
{getContent(children, prependIconName, appendIconName)} {getButtonContent(children, prependIconName, appendIconName)}
</button> </button>
); );
} }
@ -215,13 +219,18 @@ export const AnchorButton = forwardRef<HTMLAnchorElement, AnchorButtonProps>(
className, className,
prependIconName, prependIconName,
appendIconName, appendIconName,
boxShadow,
...props ...props
}, },
ref ref
) => { ) => {
return ( return (
<a ref={ref} className={classes(className, variant)} {...props}> <a
{getContent(children, prependIconName, appendIconName)} ref={ref}
className={getButtonClasses(className, variant, boxShadow)}
{...props}
>
{getButtonContent(children, prependIconName, appendIconName)}
</a> </a>
); );
} }

View File

@ -17,31 +17,29 @@ it('renders title and icon', () => {
it(`Applies class for success intent`, () => { it(`Applies class for success intent`, () => {
render(<Callout intent={Intent.Danger} />); render(<Callout intent={Intent.Danger} />);
expect(screen.getByTestId('callout')).toHaveClass('shadow-danger'); expect(screen.getByTestId('callout')).toHaveClass('border-danger');
}); });
it(`Applies class for warning intent`, () => { it(`Applies class for warning intent`, () => {
render(<Callout intent={Intent.Warning} />); render(<Callout intent={Intent.Warning} />);
expect(screen.getByTestId('callout')).toHaveClass('shadow-warning'); expect(screen.getByTestId('callout')).toHaveClass('border-warning');
}); });
it(`Applies class for danger intent`, () => { it(`Applies class for danger intent`, () => {
render(<Callout intent={Intent.Danger} />); render(<Callout intent={Intent.Danger} />);
expect(screen.getByTestId('callout')).toHaveClass('shadow-danger'); expect(screen.getByTestId('callout')).toHaveClass('border-danger');
}); });
it(`Applies class for primary intent`, () => { it(`Applies class for primary intent`, () => {
render(<Callout intent={Intent.Primary} />); render(<Callout intent={Intent.Primary} />);
expect(screen.getByTestId('callout')).toHaveClass( expect(screen.getByTestId('callout')).toHaveClass(
'shadow-vega-pink', 'border-black dark:border-white'
'dark:shadow-vega-yellow'
); );
}); });
it(`Applies class for none intent`, () => { it(`Applies class for none intent`, () => {
render(<Callout />); render(<Callout />);
expect(screen.getByTestId('callout')).toHaveClass( expect(screen.getByTestId('callout')).toHaveClass(
'shadow-black', 'border-black dark:border-white'
'dark:shadow-white'
); );
}); });

View File

@ -21,31 +21,31 @@ const Template: ComponentStory<typeof Callout> = (args) => (
export const Default = Template.bind({}); export const Default = Template.bind({});
Default.args = { Default.args = {
children: 'Content', children: 'No intent supplied',
}; };
export const Primary = Template.bind({}); export const Primary = Template.bind({});
Primary.args = { Primary.args = {
intent: Intent.Primary, intent: Intent.Primary,
children: 'Content', children: 'Intent: Primary',
}; };
export const Danger = Template.bind({}); export const Danger = Template.bind({});
Danger.args = { Danger.args = {
intent: Intent.Danger, intent: Intent.Danger,
children: 'Content', children: 'Intent: Danger',
}; };
export const Warning = Template.bind({}); export const Warning = Template.bind({});
Warning.args = { Warning.args = {
intent: Intent.Warning, intent: Intent.Warning,
children: 'Content', children: 'Intent: Warning',
}; };
export const Success = Template.bind({}); export const Success = Template.bind({});
Success.args = { Success.args = {
intent: Intent.Success, intent: Intent.Success,
children: 'Content', children: 'Intent: Success',
}; };
export const IconAndContent = Template.bind({}); export const IconAndContent = Template.bind({});
@ -55,7 +55,7 @@ IconAndContent.args = {
iconName: 'endorsed', iconName: 'endorsed',
children: ( children: (
<div className="flex flex-col"> <div className="flex flex-col">
<div>With a longer explaination</div> <div>With a longer explanation</div>
<Button className="block mt-8" variant="secondary"> <Button className="block mt-8" variant="secondary">
Action Action
</Button> </Button>
@ -74,7 +74,7 @@ CustomIconAndContent.args = {
), ),
children: ( children: (
<div className="flex flex-col"> <div className="flex flex-col">
<div>With a longer explaination</div> <div>With a longer explanation</div>
<Button className="block mt-8" variant="secondary"> <Button className="block mt-8" variant="secondary">
Action Action
</Button> </Button>
@ -89,7 +89,7 @@ Loading.args = {
isLoading: true, isLoading: true,
children: ( children: (
<div className="flex flex-col"> <div className="flex flex-col">
<div>With a longer explaination</div> <div>With a longer explanation</div>
<Button className="block mt-8" variant="secondary"> <Button className="block mt-8" variant="secondary">
Action Action
</Button> </Button>

View File

@ -1,13 +1,13 @@
import type { ReactNode } from 'react'; import type { ReactNode, ReactElement } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { getIntentShadow, Intent } from '../../utils/intent'; import { getIntentBorder, Intent } from '../../utils/intent';
import { Loader } from '../loader'; import { Loader } from '../loader';
import type { IconName } from '../icon'; import type { IconName } from '../icon';
import { Icon } from '../icon'; import { Icon } from '../icon';
interface CalloutRootProps { interface CalloutRootProps {
children?: React.ReactNode; children?: ReactNode;
title?: React.ReactElement | string; title?: ReactElement | string;
intent?: Intent; intent?: Intent;
headingLevel?: 1 | 2 | 3 | 4 | 5 | 6; headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
isLoading?: boolean; isLoading?: boolean;
@ -89,13 +89,10 @@ export function Callout({
const className = classNames( const className = classNames(
'flex gap-20', 'flex gap-20',
'border',
'border-black',
'dark:border-white',
'text-body-large', 'text-body-large',
'dark:text-white', 'dark:text-white',
'p-16', 'p-16',
getIntentShadow(intent) getIntentBorder(intent)
); );
const TitleTag: keyof JSX.IntrinsicElements = headingLevel const TitleTag: keyof JSX.IntrinsicElements = headingLevel
? `h${headingLevel}` ? `h${headingLevel}`

View File

@ -0,0 +1,64 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { Checkbox } from './checkbox';
describe('Checkbox', () => {
it('should render checkbox with label successfully', () => {
render(<Checkbox label="test" />);
expect(screen.getByText('test')).toBeInTheDocument();
});
it('should render a checked checkbox if specified in state', () => {
render(<Checkbox label="checked" state={'checked'} />);
expect(screen.getByTestId('checkbox-checked')).toBeInTheDocument();
});
it('should render an unchecked checkbox if specified in state', () => {
render(<Checkbox label="unchecked" state={'unchecked'} />);
expect(screen.getByTestId('checkbox-unchecked')).toBeInTheDocument();
});
it('should render an indeterminate checkbox if specified in state', () => {
render(<Checkbox label="indeterminate" state={'indeterminate'} />);
expect(screen.getByTestId('checkbox-indeterminate')).toBeInTheDocument();
});
it('should render a checkbox in error if specified', () => {
render(<Checkbox label="error" error={true} />);
expect(screen.getByTestId('checkbox-error')).toBeInTheDocument();
});
it('should render a checked checkbox in error if specified', () => {
render(<Checkbox label="error-checked" state={'checked'} error={true} />);
expect(screen.getByTestId('checkbox-error-checked')).toBeInTheDocument();
});
it('should render an unchecked checkbox in error if specified', () => {
render(
<Checkbox label="error-unchecked" state={'unchecked'} error={true} />
);
expect(screen.getByTestId('checkbox-error-unchecked')).toBeInTheDocument();
});
it('should render an indeterminate checkbox in error if specified', () => {
render(
<Checkbox
label="error-indeterminate"
state={'indeterminate'}
error={true}
/>
);
expect(
screen.getByTestId('checkbox-error-indeterminate')
).toBeInTheDocument();
});
it('fires callback on change if provided', () => {
const callback = jest.fn();
render(<Checkbox label="onchange" onChange={callback} />);
const checkbox = screen.getByText('onchange');
fireEvent.click(checkbox);
expect(callback.mock.calls.length).toEqual(1);
});
});

View File

@ -0,0 +1,45 @@
import type { ComponentStory, ComponentMeta } from '@storybook/react';
import { Checkbox } from './checkbox';
import { useState } from 'react';
export default {
component: Checkbox,
title: 'Checkbox',
} as ComponentMeta<typeof Checkbox>;
export const Controlled: ComponentStory<typeof Checkbox> = () => {
const [checkboxState, setCheckboxState] = useState<
'checked' | 'unchecked' | 'indeterminate'
>('indeterminate');
return (
<Checkbox
label={'controlled - initially indeterminate'}
state={checkboxState}
onChange={() => {
if (
checkboxState === 'indeterminate' ||
checkboxState === 'unchecked'
) {
setCheckboxState('checked');
}
if (checkboxState === 'checked') {
setCheckboxState('unchecked');
}
}}
/>
);
};
export const Default: ComponentStory<typeof Checkbox> = () => (
<Checkbox label={'uncontrolled - default checkbox behaviour'} />
);
export const Error: ComponentStory<typeof Checkbox> = () => (
<Checkbox label={'error'} error={true} />
);
export const Disabled: ComponentStory<typeof Checkbox> = () => (
<Checkbox label={'disabled'} disabled />
);

View File

@ -0,0 +1,81 @@
import classnames from 'classnames';
import { Icon } from '../icon';
import type { ChangeEvent, InputHTMLAttributes } from 'react';
export interface CheckboxProps extends InputHTMLAttributes<HTMLInputElement> {
label: string;
className?: string;
state?: 'checked' | 'unchecked' | 'indeterminate';
error?: boolean;
onChange?: (e: ChangeEvent<HTMLInputElement>) => void;
}
export const Checkbox = ({
label,
className,
state,
error,
onChange,
...props
}: CheckboxProps) => {
const containerClasses = classnames(
className,
'grid grid-cols-[auto_1fr] select-none'
);
const inputClasses = 'sr-only peer';
const vegaCheckboxClasses = classnames(
'col-start-1 row-start-1',
'inline-block w-20 h-20 relative z-0',
'shadow-input dark:shadow-input-dark bg-white dark:bg-white-25',
'focus-visible:outline-none focus-visible:shadow-checkbox-focus dark:focus-visible:shadow-checkbox-focus-dark',
'cursor-pointer peer-disabled:cursor-default',
{
'input-border dark:input-border-dark': !error,
'border border-vega-red': error,
}
);
// In uncontrolled elements without state, we apply the hidden class and 'peer-checked' can
// override it as necessary. At other times, we control display properties via state conditions.
const iconClasses = classnames(
'col-start-1 row-start-1 place-self-center relative z-50 pointer-events-none',
{
hidden: !state || state === 'unchecked',
}
);
const tickClasses = classnames(iconClasses, {
'peer-checked:block': !state,
block: state === 'checked',
hidden: state === 'indeterminate',
});
const minusClasses = classnames(iconClasses, {
block: state === 'indeterminate',
hidden: state === 'checked',
});
const labelClasses = classnames(
'col-start-2 row-start-1 pl-8 cursor-pointer peer-disabled:cursor-default'
);
return (
<label
className={containerClasses}
data-testid={`checkbox${error ? '-error' : ''}${
state ? `-${state}` : ''
}`}
>
<input
type="checkbox"
className={inputClasses}
onChange={onChange}
{...props}
/>
<span className={vegaCheckboxClasses} />
<span className={tickClasses}>
<Icon name={'tick'} className="fill-vega-pink dark:fill-vega-yellow" />
</span>
<span className={minusClasses}>
<Icon name={'minus'} className="fill-vega-pink dark:fill-vega-yellow" />
</span>
<span className={labelClasses}>{label}</span>
</label>
);
};

View File

@ -0,0 +1 @@
export * from './checkbox';

View File

@ -15,7 +15,9 @@ const Template: ComponentStory<typeof Dialog> = (args) => {
return ( return (
<div> <div>
<Button onClick={() => setOpen(true)}>Open dialog</Button> <Button onClick={() => setOpen(true)}>Open dialog</Button>
<Dialog {...args} open={open} setOpen={setOpen} /> <Dialog {...args} open={open} onChange={setOpen}>
{args.children}
</Dialog>
</div> </div>
); );
}; };
@ -23,38 +25,38 @@ const Template: ComponentStory<typeof Dialog> = (args) => {
export const Default = Template.bind({}); export const Default = Template.bind({});
Default.args = { Default.args = {
open: false, open: false,
title: 'Title', title: 'No intent supplied',
children: <p>Some content</p>, children: <p>Some content</p>,
}; };
export const Primary = Template.bind({});
Primary.args = {
open: false,
title: 'Intent: Primary',
children: <p>Some content</p>,
intent: Intent.Primary,
};
export const Danger = Template.bind({}); export const Danger = Template.bind({});
Danger.args = { Danger.args = {
open: false, open: false,
title: 'Danger', title: 'Intent: Danger',
children: <p>Some content</p>, children: <p>Some content</p>,
intent: Intent.Danger, intent: Intent.Danger,
}; };
export const Success = Template.bind({});
Success.args = {
open: false,
title: 'Success',
children: <p>Some content</p>,
intent: Intent.Success,
};
export const Warning = Template.bind({}); export const Warning = Template.bind({});
Warning.args = { Warning.args = {
open: false, open: false,
title: 'Warning', title: 'Intent: Warning',
children: <p>Some content</p>, children: <p>Some content</p>,
intent: Intent.Warning, intent: Intent.Warning,
}; };
export const Modal = Template.bind({}); export const Success = Template.bind({});
Modal.args = { Success.args = {
open: false, open: false,
title: 'Modal (Prompt)', title: 'Intent: Success',
children: <p>Some content</p>, children: <p>Some content</p>,
intent: Intent.Warning, intent: Intent.Success,
}; };

View File

@ -2,7 +2,7 @@ import * as DialogPrimitives from '@radix-ui/react-dialog';
import classNames from 'classnames'; import classNames from 'classnames';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import type { Intent } from '../../utils/intent'; import type { Intent } from '../../utils/intent';
import { getIntentShadow } from '../../utils/intent'; import { getIntentShadow, getIntentBorder } from '../../utils/intent';
import { Icon } from '../icon'; import { Icon } from '../icon';
interface DialogProps { interface DialogProps {
@ -30,6 +30,7 @@ export function Dialog({
// Need to apply background and text colors again as content is rendered in a portal // Need to apply background and text colors again as content is rendered in a portal
'dark:bg-black dark:text-white-95 bg-white text-black-95', 'dark:bg-black dark:text-white-95 bg-white text-black-95',
getIntentShadow(intent), getIntentShadow(intent),
getIntentBorder(intent),
contentClassNames contentClassNames
); );
return ( return (
@ -41,10 +42,13 @@ export function Dialog({
/> />
<DialogPrimitives.Content className={contentClasses}> <DialogPrimitives.Content className={contentClasses}>
<DialogPrimitives.Close <DialogPrimitives.Close
className="p-12 absolute top-0 right-0" className="p-2 absolute top-8 right-8 leading-[0] focus:outline-none focus-visible:outline-none focus-visible:border focus-visible:border-vega-yellow"
data-testid="dialog-close" data-testid="dialog-close"
> >
<Icon name="cross" /> <Icon
name="cross"
className="focus:outline-none focus-visible:outline-none"
/>
</DialogPrimitives.Close> </DialogPrimitives.Close>
{title && ( {title && (
<h1 <h1

View File

@ -6,13 +6,11 @@ import {
DropdownMenuCheckboxItem, DropdownMenuCheckboxItem,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuItemIndicator,
DropdownMenuRadioGroup, DropdownMenuRadioGroup,
DropdownMenuRadioItem, DropdownMenuRadioItem,
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger, DropdownMenuTrigger,
} from './dropdown-menu'; } from './dropdown-menu';
import { Button } from '../button';
import { Icon } from '../icon'; import { Icon } from '../icon';
export default { export default {
@ -31,20 +29,16 @@ export const CheckboxItems = () => {
return ( return (
<div style={{ textAlign: 'center', padding: 50 }}> <div style={{ textAlign: 'center', padding: 50 }}>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger> <DropdownMenuTrigger className="w-[300px]">
<Button appendIconName="chevron-down">Options</Button> <span>Select many things</span>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent> <DropdownMenuContent className="w-[300px]">
{checkboxItems.map(({ label, state: [checked, setChecked] }) => ( {checkboxItems.map(({ label, state: [checked, setChecked] }) => (
<DropdownMenuCheckboxItem <DropdownMenuCheckboxItem
key={label} key={label}
inset
checked={checked} checked={checked}
onCheckedChange={setChecked} onCheckedChange={setChecked}
> >
<DropdownMenuItemIndicator>
<Icon name="tick" />
</DropdownMenuItemIndicator>
{label} {label}
</DropdownMenuCheckboxItem> </DropdownMenuCheckboxItem>
))} ))}
@ -56,13 +50,13 @@ export const CheckboxItems = () => {
export const RadioItems = () => { export const RadioItems = () => {
const files = ['README.md', 'index.js', 'page.css']; const files = ['README.md', 'index.js', 'page.css'];
const [file, setFile] = useState(files[1]); const [selected, setSelected] = useState(files[1]);
return ( return (
<div style={{ textAlign: 'center', padding: 50 }}> <div style={{ textAlign: 'center', padding: 50 }}>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger> <DropdownMenuTrigger>
<Button appendIconName="chevron-down">Open</Button> <span>Open</span>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent> <DropdownMenuContent>
<DropdownMenuItem inset onSelect={() => console.log('minimize')}> <DropdownMenuItem inset onSelect={() => console.log('minimize')}>
@ -75,19 +69,38 @@ export const RadioItems = () => {
Smaller Smaller
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuRadioGroup value={file} onValueChange={setFile}> <DropdownMenuRadioGroup value={selected} onValueChange={setSelected}>
{files.map((file) => ( {files.map((file) => (
<DropdownMenuRadioItem key={file} inset value={file}> <DropdownMenuRadioItem key={file} inset value={file}>
{file} {file}
<DropdownMenuItemIndicator>
<Icon name="tick" />
</DropdownMenuItemIndicator>
</DropdownMenuRadioItem> </DropdownMenuRadioItem>
))} ))}
</DropdownMenuRadioGroup> </DropdownMenuRadioGroup>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
<p>Selected file: {file}</p> <p>Selected file: {selected}</p>
</div>
);
};
export const IconMenu = () => {
const iconMenuItems = [
{ label: 'IconMenu Item 1' },
{ label: 'IconMenu Item 2' },
];
return (
<div style={{ textAlign: 'center', padding: 50 }}>
<DropdownMenu>
<DropdownMenuTrigger>
<Icon name="cog" />
</DropdownMenuTrigger>
<DropdownMenuContent>
{iconMenuItems.map(({ label }) => (
<DropdownMenuItem key={label}>{label}</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</div> </div>
); );
}; };

View File

@ -1,34 +1,31 @@
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'; import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
import classNames from 'classnames'; import classNames from 'classnames';
import { forwardRef } from 'react'; import { forwardRef } from 'react';
import { Button } from '../button';
const itemStyles = classNames([ const itemClass = classNames(
'text-ui', 'relative',
'text-black', 'flex items-center justify-between',
'dark:text-white', 'text-ui leading-1',
'flex', 'h-[25px]',
'items-center', 'py-0 pr-8',
'justify-between',
'leading-1',
'cursor-default', 'cursor-default',
'hover:cursor-pointer',
'select-none', 'select-none',
'whitespace-nowrap', 'whitespace-nowrap',
'h-[25px]', 'focus:bg-vega-pink dark:focus:bg-vega-yellow',
'py-0', 'focus:text-white dark:focus:text-black',
'pr-8', 'focus:outline-none'
'color-black', );
]);
const itemClass = classNames(itemStyles, [ function getItemClasses(inset: boolean, checked?: boolean) {
'focus:bg-vega-yellow', return classNames(
'dark:focus:bg-vega-yellow', itemClass,
'focus:text-black', inset ? 'pl-28' : 'pl-8',
'dark:focus:text-black', checked
'focus:outline-none', ? 'bg-vega-pink dark:bg-vega-yellow text-white dark:text-black'
]); : 'text-black dark:text-white'
);
function getItemClasses(inset: boolean) {
return classNames(itemClass, inset ? 'pl-28' : 'pl-4', 'relative');
} }
/** /**
@ -40,7 +37,25 @@ export const DropdownMenu = DropdownMenuPrimitive.Root;
* The button that toggles the dropdown menu. * The button that toggles the dropdown menu.
* By default, the {@link DropdownMenuContent} will position itself against the trigger. * By default, the {@link DropdownMenuContent} will position itself against the trigger.
*/ */
export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; export const DropdownMenuTrigger = forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Trigger>,
React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>
>(({ children, className }, forwardedRef) => (
<DropdownMenuPrimitive.Trigger
asChild={true}
ref={forwardedRef}
className="focus-visible:outline-none focus-visible:shadow-inset-vega-pink dark:focus-visible:shadow-inset-vega-yellow"
>
<Button
variant="secondary"
appendIconName="chevron-down"
boxShadow={false}
className={classNames(className, 'justify-between px-8 font-normal')}
>
{children}
</Button>
</DropdownMenuPrimitive.Trigger>
));
/** /**
* Used to group multiple {@link DropdownMenuRadioItem}s. * Used to group multiple {@link DropdownMenuRadioItem}s.
@ -58,7 +73,7 @@ export const DropdownMenuContent = forwardRef<
{...contentProps} {...contentProps}
ref={forwardedRef} ref={forwardedRef}
className={classNames( className={classNames(
'inline-block box-border border-1 border-black bg-white dark:bg-black p-4', 'inline-block box-border border-1 border-black bg-white dark:bg-black-60',
className className
)} )}
/> />
@ -92,7 +107,12 @@ export const DropdownMenuCheckboxItem = forwardRef<
<DropdownMenuPrimitive.CheckboxItem <DropdownMenuPrimitive.CheckboxItem
{...checkboxItemProps} {...checkboxItemProps}
ref={forwardedRef} ref={forwardedRef}
className={classNames(getItemClasses(inset), className)} className={classNames(
getItemClasses(inset, checkboxItemProps.checked),
className,
'hover:shadow-inset-black dark:hover:shadow-inset-white',
'focus:shadow-inset-black dark:focus:shadow-inset-white'
)}
/> />
)); ));

View File

@ -1,32 +1,49 @@
import classNames from 'classnames'; import classNames from 'classnames';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import classnames from 'classnames';
interface FormGroupProps { interface FormGroupProps {
children: ReactNode; children: ReactNode;
label?: string; label?: string;
labelFor?: string; labelFor?: string;
labelAlign?: 'left' | 'right'; labelAlign?: 'left' | 'right';
labelDescription?: string;
className?: string; className?: string;
hasError?: boolean;
} }
export const FormGroup = ({ export const FormGroup = ({
children, children,
label, label,
labelFor, labelFor,
labelDescription,
labelAlign = 'left', labelAlign = 'left',
className, className,
hasError,
}: FormGroupProps) => { }: FormGroupProps) => {
const labelClasses = classNames('block text-ui mb-4', {
'text-right': labelAlign === 'right',
});
return ( return (
<div <div
data-testid="form-group" data-testid="form-group"
className={className?.includes('mb') ? className : `${className} mb-20`} className={classnames(className, { 'mb-20': !className?.includes('mb') })}
> >
{label && ( {label && (
<label className={labelClasses} htmlFor={labelFor}> <label htmlFor={labelFor}>
{label} <div
className={classNames(
'mb-4 text-body-large text-black dark:text-white',
{
'border-l-4 border-danger pl-8': hasError,
'text-right': labelAlign === 'right',
}
)}
>
<div className="font-bold mb-2">{label}</div>
{labelDescription && (
<div className={classNames({ 'text-danger': hasError })}>
{labelDescription}
</div>
)}
</div>
</label> </label>
)} )}
{children} {children}

View File

@ -11,20 +11,48 @@ export default {
labelFor: { labelFor: {
type: 'string', type: 'string',
}, },
labelDescription: {
type: 'string',
},
className: { className: {
type: 'string', type: 'string',
}, },
hasError: {
type: 'boolean',
},
}, },
} as Meta; } as Meta;
const Template: Story = (args) => ( const Template: Story = (args) => (
<FormGroup {...args} label="label" labelFor="test"> <FormGroup {...args}>
<Input id="labelFor" /> <Input id="labelFor" />
</FormGroup> </FormGroup>
); );
export const Default = Template.bind({}); export const Default = Template.bind({});
Default.args = { Default.args = {
label: 'label', label: 'Label',
labelFor: 'labelFor', labelFor: 'labelFor',
}; };
export const Error = Template.bind({});
Error.args = {
label: 'Label',
labelFor: 'labelFor',
hasError: true,
};
export const WithDescription = Template.bind({});
WithDescription.args = {
label: 'Label',
labelFor: 'labelFor',
labelDescription: 'with description text',
};
export const WithDescriptionAndError = Template.bind({});
WithDescriptionAndError.args = {
hasError: true,
label: 'Label',
labelFor: 'labelFor',
labelDescription: 'with description text',
};

View File

@ -1 +0,0 @@
export * from './grid-tabs';

View File

@ -6,10 +6,10 @@ export * from './button';
export * from './callout'; export * from './callout';
export * from './card'; export * from './card';
export * from './copy-with-tooltip'; export * from './copy-with-tooltip';
export * from './checkbox';
export * from './dialog'; export * from './dialog';
export * from './dropdown-menu'; export * from './dropdown-menu';
export * from './form-group'; export * from './form-group';
export * from './grid-tabs';
export * from './icon'; export * from './icon';
export * from './indicator'; export * from './indicator';
export * from './input'; export * from './input';
@ -24,6 +24,7 @@ export * from './select';
export * from './sparkline'; export * from './sparkline';
export * from './splash'; export * from './splash';
export * from './syntax-highlighter'; export * from './syntax-highlighter';
export * from './tabs';
export * from './text-area'; export * from './text-area';
export * from './theme-switcher'; export * from './theme-switcher';
export * from './toggle'; export * from './toggle';

View File

@ -1,6 +1,6 @@
import classNames from 'classnames'; import classNames from 'classnames';
import { Intent } from '../../utils/intent'; import { Intent } from '../../utils/intent';
import { getVariantBackground } from '../../utils/intent'; import { getIntentTextAndBackground } from '../../utils/intent';
interface IndicatorProps { interface IndicatorProps {
variant?: Intent; variant?: Intent;
@ -9,7 +9,7 @@ interface IndicatorProps {
export const Indicator = ({ variant = Intent.None }: IndicatorProps) => { export const Indicator = ({ variant = Intent.None }: IndicatorProps) => {
const names = classNames( const names = classNames(
'inline-block w-8 h-8 mb-2 mr-8 rounded', 'inline-block w-8 h-8 mb-2 mr-8 rounded',
getVariantBackground(variant) getIntentTextAndBackground(variant)
); );
return <div className={names} />; return <div className={names} />;
}; };

View File

@ -131,17 +131,22 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
const hasPrepended = !!(prependIconName || prependElement); const hasPrepended = !!(prependIconName || prependElement);
const hasAppended = !!(appendIconName || appendElement); const hasAppended = !!(appendIconName || appendElement);
const inputClassName = classNames('appearance-none', 'h-28', className, { const inputClassName = classNames(
'pl-28': hasPrepended, 'appearance-none',
'pr-28': hasAppended, 'h-28',
'border-vega-pink dark:border-vega-pink': hasError, 'dark:color-scheme-dark',
}); className,
{
'pl-28': hasPrepended,
'pr-28': hasAppended,
}
);
const input = ( const input = (
<input <input
{...props} {...props}
ref={ref} ref={ref}
className={classNames(defaultFormElement, inputClassName)} className={classNames(defaultFormElement(hasError), inputClassName)}
/> />
); );

View File

@ -1,6 +1,6 @@
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { getVariantBackground } from '../../utils/intent'; import { getIntentTextAndBackground } from '../../utils/intent';
import { Intent } from '../../utils/intent'; import { Intent } from '../../utils/intent';
interface LozengeProps { interface LozengeProps {
@ -15,7 +15,7 @@ const getLozengeClasses = (
) => { ) => {
return classNames( return classNames(
['rounded-md', 'font-mono', 'leading-none', 'p-4'], ['rounded-md', 'font-mono', 'leading-none', 'p-4'],
getVariantBackground(variant), getIntentTextAndBackground(variant),
className className
); );
}; };

View File

@ -40,8 +40,8 @@ const priceChangeClassNames = (value: number | bigint) =>
value === 0 value === 0
? 'text-black dark:text-white' ? 'text-black dark:text-white'
: value > 0 : value > 0
? `text-green-dark dark:text-green-vega ` ? `text-vega-green-dark dark:text-vega-green `
: `text-red-dark dark:text-red-vega`; : `text-vega-red-dark dark:text-vega-red`;
export const PriceCellChange = React.memo( export const PriceCellChange = React.memo(
({ candles, decimalPlaces }: PriceChangeCellProps) => { ({ candles, decimalPlaces }: PriceChangeCellProps) => {

View File

@ -35,9 +35,8 @@ export const Radio = ({ id, value, label, disabled, hasError }: RadioProps) => {
const itemClasses = classNames( const itemClasses = classNames(
'flex justify-center items-center', 'flex justify-center items-center',
'w-[17px] h-[17px] rounded-full border', 'w-[17px] h-[17px] rounded-full border',
'focus:outline-0 focus-visible:outline-0', 'focus:outline-none focus-visible:outline-none',
'focus-visible:shadow-radio focus-visible:shadow-vega-pink dark:focus-visible:shadow-vega-yellow', 'focus-visible:shadow-vega-pink dark:focus-visible:shadow-vega-yellow',
'border-black-60 dark:border-white-60',
'dark:bg-white-25', 'dark:bg-white-25',
{ {
'border-black-60 dark:border-white-60': !hasError, 'border-black-60 dark:border-white-60': !hasError,

View File

@ -8,7 +8,9 @@ export default {
const Template: Story = (args) => ( const Template: Story = (args) => (
<Select {...args}> <Select {...args}>
<option value="Only option">Only option</option> <option value="Option 1">Option 1</option>
<option value="Option 2">Option 2</option>
<option value="Option 3">Option 3</option>
</Select> </Select>
); );

View File

@ -15,9 +15,7 @@ export const Select = forwardRef<HTMLSelectElement, SelectProps>(
<select <select
ref={ref} ref={ref}
{...props} {...props}
className={classNames(defaultFormElement, className, 'h-28', { className={classNames(defaultFormElement(hasError), className, 'h-28')}
'border-vega-pink dark:border-vega-pink': hasError,
})}
/> />
) )
); );

View File

@ -36,7 +36,7 @@ it('Renders a red line if the last value is less than the first', () => {
const paths = screen.getAllByTestId('sparkline-path'); const paths = screen.getAllByTestId('sparkline-path');
const path = paths[0]; const path = paths[0];
expect(path).toHaveClass( expect(path).toHaveClass(
'[vector-effect:non-scaling-stroke] stroke-red-dark dark:stroke-red' '[vector-effect:non-scaling-stroke] stroke-vega-red-dark dark:stroke-vega-red'
); );
}); });
@ -48,7 +48,7 @@ it('Renders a green line if the last value is greater than the first', () => {
const paths = screen.getAllByTestId('sparkline-path'); const paths = screen.getAllByTestId('sparkline-path');
const path = paths[0]; const path = paths[0];
expect(path).toHaveClass( expect(path).toHaveClass(
'[vector-effect:non-scaling-stroke] stroke-green-dark dark:stroke-green' '[vector-effect:non-scaling-stroke] stroke-vega-green-dark dark:stroke-vega-green'
); );
}); });

View File

@ -8,8 +8,8 @@ function colorByChange(a: number, b: number) {
return a === b return a === b
? 'stroke-black/40 dark:stroke-white/40' ? 'stroke-black/40 dark:stroke-white/40'
: a < b : a < b
? 'stroke-green-dark dark:stroke-green' ? 'stroke-vega-green-dark dark:stroke-vega-green'
: 'stroke-red-dark dark:stroke-red'; : 'stroke-vega-red-dark dark:stroke-vega-red';
} }
export interface SparklineProps { export interface SparklineProps {

View File

@ -0,0 +1 @@
export * from './tabs';

View File

@ -0,0 +1,33 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Tabs, Tab } from './tabs';
const renderComponent = (
<Tabs>
<Tab id="one" name="Tab one">
<p>Tab one content</p>
</Tab>
<Tab id="two" name="Tab two">
<p>Tab two content</p>
</Tab>
<Tab id="three" name="Tab three">
<p>Tab three content</p>
</Tab>
</Tabs>
);
describe('Tabs', () => {
it('should render tabs successfully', () => {
render(renderComponent);
expect(screen.getByTestId('Tab one')).toBeInTheDocument();
expect(screen.getByTestId('Tab two')).toBeInTheDocument();
expect(screen.getByTestId('Tab three')).toBeInTheDocument();
});
it('shows tabs display the correct content when clicked', async () => {
render(renderComponent);
expect(screen.getByText('Tab one content')).toBeInTheDocument();
await userEvent.click(screen.getByText('Tab two'));
expect(await screen.getByText('Tab two content')).toBeInTheDocument();
});
});

View File

@ -0,0 +1,21 @@
import type { Story, Meta } from '@storybook/react';
import { Tabs, Tab } from './tabs';
export default {
component: Tabs,
title: 'Tabs',
} as Meta;
export const Default: Story = () => (
<Tabs>
<Tab id="one" name="Tab one">
<p>Tab one content</p>
</Tab>
<Tab id="two" name="Tab two">
<p>Tab two content</p>
</Tab>
<Tab id="three" name="Tab three">
<p>Tab three content</p>
</Tab>
</Tabs>
);

View File

@ -1,73 +1,79 @@
import * as Tabs from '@radix-ui/react-tabs'; import * as TabsPrimitive from '@radix-ui/react-tabs';
import classNames from 'classnames'; import classNames from 'classnames';
import type { ReactElement, ReactNode } from 'react'; import type { ReactElement, ReactNode } from 'react';
import { Children, isValidElement, useState } from 'react'; import { Children, isValidElement, useState } from 'react';
interface GridTabsProps { interface TabsProps {
children: ReactElement<GridTabProps>[]; children: ReactElement<TabProps>[];
} }
export const GridTabs = ({ children }: GridTabsProps) => { export const Tabs = ({ children }: TabsProps) => {
const [activeTab, setActiveTab] = useState<string>(() => { const [activeTab, setActiveTab] = useState<string>(() => {
return children[0].props.id; return children[0].props.id;
}); });
return ( return (
<Tabs.Root <TabsPrimitive.Root
value={activeTab} value={activeTab}
className="h-full grid grid-rows-[min-content_1fr]" className="h-full grid grid-rows-[min-content_1fr]"
onValueChange={(value) => setActiveTab(value)} onValueChange={(value) => setActiveTab(value)}
> >
<div className="bg-black-10 dark:bg-white-10"> <div className="bg-black-10 dark:bg-white-10">
<Tabs.List <TabsPrimitive.List
className="flex flex-nowrap gap-4 overflow-x-auto" className="flex flex-nowrap gap-4 overflow-x-auto"
role="tablist" role="tablist"
> >
{Children.map(children, (child) => { {Children.map(children, (child) => {
if (!isValidElement(child)) return null; if (!isValidElement(child)) return null;
const isActive = child.props.id === activeTab; const isActive = child.props.id === activeTab;
const triggerClass = classNames('py-4', 'px-12', 'capitalize', { const triggerClass = classNames(
'text-black dark:text-vega-yellow': isActive, 'py-4 px-20',
'bg-white dark:bg-black': isActive, 'capitalize',
'text-black-60 dark:text-white-60': !isActive, 'focus-visible:outline-none focus-visible:shadow-inset-vega-pink dark:focus-visible:shadow-inset-vega-yellow',
'bg-black-10 dark:bg-white-10': !isActive, {
}); 'font-semibold text-vega-pink dark:text-vega-yellow': isActive,
'bg-white dark:bg-black': isActive,
'text-black dark:text-white': !isActive,
'bg-white-90 dark:bg-black-70 hover:bg-white-95 dark:hover:bg-black-80':
!isActive,
}
);
return ( return (
<Tabs.Trigger <TabsPrimitive.Trigger
data-testid={child.props.name} data-testid={child.props.name}
value={child.props.id} value={child.props.id}
className={triggerClass} className={triggerClass}
> >
{child.props.name} {child.props.name}
</Tabs.Trigger> </TabsPrimitive.Trigger>
); );
})} })}
</Tabs.List> </TabsPrimitive.List>
</div> </div>
<div className="h-full overflow-auto"> <div className="h-full overflow-auto">
{Children.map(children, (child) => { {Children.map(children, (child) => {
if (!isValidElement(child)) return null; if (!isValidElement(child)) return null;
return ( return (
<Tabs.Content <TabsPrimitive.Content
value={child.props.id} value={child.props.id}
className="h-full" className="h-full"
data-testid={`tab-${child.props.id}`} data-testid={`tab-${child.props.id}`}
> >
{child.props.children} {child.props.children}
</Tabs.Content> </TabsPrimitive.Content>
); );
})} })}
</div> </div>
</Tabs.Root> </TabsPrimitive.Root>
); );
}; };
interface GridTabProps { interface TabProps {
children: ReactNode; children: ReactNode;
id: string; id: string;
name: string; name: string;
} }
export const GridTab = ({ id, children }: GridTabProps) => { export const Tab = ({ children, ...props }: TabProps) => {
return <div>{children}</div>; return <div {...props}>{children}</div>;
}; };

View File

@ -11,9 +11,7 @@ export interface TextAreaProps
export const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>( export const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
({ className, hasError, ...props }, ref) => { ({ className, hasError, ...props }, ref) => {
const classes = classNames(defaultFormElement, className, { const classes = classNames(defaultFormElement(hasError), className);
'border-vega-pink dark:border-vega-pink': hasError,
});
return <textarea {...props} ref={ref} className={classes} />; return <textarea {...props} ref={ref} className={classes} />;
} }
); );

View File

@ -34,9 +34,10 @@ export const Toggle = ({
'border border-black-60 active:border-black dark:border-white-60 dark:active:border-white peer-checked:border-black dark:peer-checked:border-vega-yellow', 'border border-black-60 active:border-black dark:border-white-60 dark:active:border-white peer-checked:border-black dark:peer-checked:border-vega-yellow',
'group-first-of-type:rounded-tl group-first-of-type:rounded-bl group-last-of-type:rounded-tr group-last-of-type:rounded-br', 'group-first-of-type:rounded-tl group-first-of-type:rounded-bl group-last-of-type:rounded-tr group-last-of-type:rounded-br',
'px-28 py-4', 'px-28 py-4',
'peer-checked:bg-vega-yellow hover:bg-black-25 dark:hover:bg-white-25 hover:peer-checked:bg-vega-yellow', 'peer-checked:bg-vega-pink dark:peer-checked:bg-vega-yellow hover:bg-black-10 dark:hover:bg-white-25 hover:peer-checked:bg-vega-pink dark:hover:peer-checked:bg-vega-yellow focus-visible:bg-black-10 dark:focus-visible:bg-white-25',
'text-ui text-black-60 dark:text-white-60 peer-checked:text-black active:text-black dark:active:text-white peer-checked:font-bold text-center', 'text-ui text-center peer-checked:font-bold peer-checked:text-white dark:peer-checked:text-black text-black-60 dark:text-white-60 active:text-black dark:active:text-white focus-visible:text-black dark:focus-visible:text-white',
'cursor-pointer peer-checked:cursor-auto select-none transition-all' 'focus-within:shadow-inset-black',
'cursor-pointer peer-checked:cursor-auto select-none transition-all duration-75'
); );
return ( return (

View File

@ -11,7 +11,7 @@ const Template: Story<TooltipProps> = (args) => <Tooltip {...args} />;
export const Uncontrolled = Template.bind({}); export const Uncontrolled = Template.bind({});
Uncontrolled.args = { Uncontrolled.args = {
children: <button>Click me!</button>, children: <button>Hover on me!</button>,
description: 'Tooltip content!', description: 'Tooltip content!',
}; };

View File

@ -21,15 +21,19 @@ export const Tooltip = ({ children, description, open, align }: TooltipProps) =>
<Root open={open}> <Root open={open}>
<Trigger asChild>{children}</Trigger> <Trigger asChild>{children}</Trigger>
<Content align={align} alignOffset={5}> <Content align={align} alignOffset={5}>
<div className="relative z-0 p-8 bg-black-50 border border-black-60 text-white rounded-sm max-w-sm">
{description}
</div>
<Arrow <Arrow
width={10} width={10}
height={5} height={5}
offset={10} className="z-[1] fill-black-60 dark:fill-white-60 z-0 translate-x-[1px] translate-y-[-1px]"
className="fill-vega-green" />
<Arrow
width={8}
height={4}
className="z-[1] translate-y-[-1px] fill-black-50"
/> />
<div className="px-12 py-8 bg-vega-green text-black rounded-sm max-w-sm">
{description}
</div>
</Content> </Content>
</Root> </Root>
</Provider> </Provider>

View File

@ -30,11 +30,28 @@ full colour palette [here](https://tailwindcss.com/docs/customizing-colors/#defa
subtitle="Black" subtitle="Black"
colors={theme.colors.black} colors={theme.colors.black}
/> />
<ColorItem
title="theme.color.blue"
subtitle="Blue"
colors={[theme.colors.blue]}
/>
<ColorItem <ColorItem
title="theme.color.coral" title="theme.color.coral"
subtitle="Coral" subtitle="Coral"
colors={[theme.colors.coral]} colors={[theme.colors.coral]}
/> />
<ColorItem
title="theme.color.orange"
subtitle="Orange"
colors={[theme.colors.orange]}
/>
<ColorPalette>
<ColorItem
title="theme.color.selected"
subtitle="Selected"
colors={[theme.colors.selected]}
/>
</ColorPalette>
</ColorPalette> </ColorPalette>
### Vega ### Vega
@ -51,13 +68,23 @@ full colour palette [here](https://tailwindcss.com/docs/customizing-colors/#defa
<ColorPalette> <ColorPalette>
<ColorItem <ColorItem
title="theme.color.intent" title="theme.color.danger"
subtitle="Intent" subtitle="danger"
colors={{ colors={[theme.colors.danger]}
success: theme.colors.success, />
warning: theme.colors.warning, </ColorPalette>
danger: theme.colors.danger, <ColorPalette>
}} <ColorItem
title="theme.color.warning"
subtitle="warning"
colors={[theme.colors.warning]}
/>
</ColorPalette>
<ColorPalette>
<ColorItem
title="theme.color.success"
subtitle="success"
colors={[theme.colors.success]}
/> />
</ColorPalette> </ColorPalette>

View File

@ -1,30 +1,40 @@
export enum Intent { export enum Intent {
None, None,
Primary, Primary,
Success,
Warning,
Danger, Danger,
Warning,
Success,
} }
export const getIntentShadow = (intent?: Intent) => { export const getIntentShadow = (intent = Intent.None) => {
return { return {
'shadow-callout': true, 'shadow-intent': true,
'shadow-danger': intent === Intent.Danger, 'shadow-danger': intent === Intent.Danger,
'shadow-warning': intent === Intent.Warning, 'shadow-warning': intent === Intent.Warning,
'shadow-black dark:shadow-white':
intent === Intent.None || intent === Intent.Primary,
'shadow-success': intent === Intent.Success, 'shadow-success': intent === Intent.Success,
'shadow-black dark:shadow-white': intent === Intent.None,
'shadow-vega-pink dark:shadow-vega-yellow': intent === Intent.Primary,
}; };
}; };
export const getVariantBackground = (variant?: Intent) => { export const getIntentBorder = (intent = Intent.None) => {
return { return {
'bg-black text-white dark:bg-white dark:text-black': border: true,
variant === Intent.None, 'border-danger': intent === Intent.Danger,
'bg-vega-pink text-black dark:bg-vega-yellow dark:text-black-normal': 'border-warning': intent === Intent.Warning,
variant === Intent.Primary, 'border-black dark:border-white':
'bg-danger text-white': variant === Intent.Danger, intent === Intent.None || intent === Intent.Primary,
'bg-warning text-black': variant === Intent.Warning, 'border-success': intent === Intent.Success,
'bg-success text-black': variant === Intent.Success, };
};
export const getIntentTextAndBackground = (intent = Intent.None) => {
return {
'bg-black text-white dark:bg-white text-black': intent === Intent.None,
'bg-vega-pink text-black dark:bg-vega-yellow dark:text-black-normal':
intent === Intent.Primary,
'bg-danger text-white': intent === Intent.Danger,
'bg-warning text-black': intent === Intent.Warning,
'bg-success text-black': intent === Intent.Success,
}; };
}; };

View File

@ -1,14 +1,22 @@
export const defaultFormElement = [ import classnames from 'classnames';
'flex items-center w-full',
'box-border', export const defaultFormElement = (hasError?: boolean) =>
'border rounded-none', classnames(
'bg-clip-padding', 'flex items-center w-full',
'border-black-60 dark:border-white-60', 'box-border',
'bg-black-25 dark:bg-white-25', 'border rounded-none',
'text-black placeholder:text-black-60 dark:text-white dark:placeholder:text-white-60', 'bg-clip-padding',
'text-ui', 'shadow-input dark:shadow-input-dark',
'px-8', 'bg-white dark:bg-white-25',
'focus-visible:shadow-focus dark:focus-visible:shadow-focus-dark', 'text-black placeholder:text-black-60 dark:text-white dark:placeholder:text-white-60',
'focus-visible:outline-0', 'text-ui',
'disabled:bg-black-10 disabled:dark:bg-white-10', 'px-8',
]; 'focus-visible:outline-none',
'disabled:bg-black-10 disabled:dark:bg-white-10',
{
'input-border dark:input-border-dark focus-visible:shadow-input-focus dark:focus-visible:shadow-input-focus-dark':
!hasError,
'border-vega-red focus:shadow-input-focus-error dark:focus:shadow-input-focus-error-dark':
hasError,
}
);

View File

@ -23,7 +23,7 @@ export const VegaManageDialog = ({
title={t('SELECT A VEGA KEY')} title={t('SELECT A VEGA KEY')}
open={dialogOpen} open={dialogOpen}
onChange={setDialogOpen} onChange={setDialogOpen}
intent={Intent.Warning} intent={Intent.Primary}
> >
<div className="text-ui"> <div className="text-ui">
{keypairs ? ( {keypairs ? (

View File

@ -22,7 +22,7 @@ export const Web3ConnectDialog = ({
<Dialog <Dialog
open={dialogOpen} open={dialogOpen}
onChange={setDialogOpen} onChange={setDialogOpen}
intent={Intent.Warning} intent={Intent.Primary}
title={t('Connect to your Ethereum wallet')} title={t('Connect to your Ethereum wallet')}
> >
<ul data-testid="web3-connector-list"> <ul data-testid="web3-connector-list">

View File

@ -24,12 +24,14 @@
"@radix-ui/react-dropdown-menu": "^0.1.6", "@radix-ui/react-dropdown-menu": "^0.1.6",
"@radix-ui/react-icons": "^1.1.1", "@radix-ui/react-icons": "^1.1.1",
"@radix-ui/react-radio-group": "^0.1.5", "@radix-ui/react-radio-group": "^0.1.5",
"@radix-ui/react-select": "^0.1.1",
"@radix-ui/react-tabs": "^0.1.5", "@radix-ui/react-tabs": "^0.1.5",
"@radix-ui/react-tooltip": "^0.1.7", "@radix-ui/react-tooltip": "^0.1.7",
"@sentry/nextjs": "^6.19.3", "@sentry/nextjs": "^6.19.3",
"@sentry/react": "^6.19.2", "@sentry/react": "^6.19.2",
"@sentry/tracing": "^6.19.2", "@sentry/tracing": "^6.19.2",
"@vegaprotocol/vegawallet-service-api-client": "^0.4.13", "@vegaprotocol/vegawallet-service-api-client": "^0.4.13",
"@testing-library/user-event": "^14.2.1",
"@walletconnect/ethereum-provider": "^1.7.5", "@walletconnect/ethereum-provider": "^1.7.5",
"@web3-react/core": "8.0.20-beta.0", "@web3-react/core": "8.0.20-beta.0",
"@web3-react/metamask": "8.0.16-beta.0", "@web3-react/metamask": "8.0.16-beta.0",

View File

@ -3349,6 +3349,13 @@
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.5.tgz#db5a11bf66bdab39569719555b0f76e138d7bd64" resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.5.tgz#db5a11bf66bdab39569719555b0f76e138d7bd64"
integrity sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw== integrity sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==
"@radix-ui/number@0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/number/-/number-0.1.0.tgz#73ad13d5cc5f75fa5e147d72e5d5d5e50d688256"
integrity sha512-rpf6QiOWLHAkM4FEMYu9i+5Jr8cKT893+R4mPpcdsy4LD7omr9JfdOqj/h/xPA5+EcVrpMMlU6rrRYpUB5UI8g==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/popper@0.1.0": "@radix-ui/popper@0.1.0":
version "0.1.0" version "0.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/popper/-/popper-0.1.0.tgz#c387a38f31b7799e1ea0d2bb1ca0c91c2931b063" resolved "https://registry.yarnpkg.com/@radix-ui/popper/-/popper-0.1.0.tgz#c387a38f31b7799e1ea0d2bb1ca0c91c2931b063"
@ -3613,6 +3620,31 @@
"@radix-ui/react-use-callback-ref" "0.1.0" "@radix-ui/react-use-callback-ref" "0.1.0"
"@radix-ui/react-use-controllable-state" "0.1.0" "@radix-ui/react-use-controllable-state" "0.1.0"
"@radix-ui/react-select@^0.1.1":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-select/-/react-select-0.1.1.tgz#ceedea6856a37e4079492e1c69601797cedd2c85"
integrity sha512-xY3jLz2c6ZBHflldGsA79bE/swUfRMpiRPQf+JDihOufsd3z5uW22PFe0MS5bGZiIpK6aAZAvwqEd2Bu7hqp8w==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/number" "0.1.0"
"@radix-ui/primitive" "0.1.0"
"@radix-ui/react-collection" "0.1.4"
"@radix-ui/react-compose-refs" "0.1.0"
"@radix-ui/react-context" "0.1.1"
"@radix-ui/react-dismissable-layer" "0.1.5"
"@radix-ui/react-focus-scope" "0.1.4"
"@radix-ui/react-id" "0.1.5"
"@radix-ui/react-label" "0.1.5"
"@radix-ui/react-portal" "0.1.4"
"@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-use-callback-ref" "0.1.0"
"@radix-ui/react-use-controllable-state" "0.1.0"
"@radix-ui/react-use-layout-effect" "0.1.0"
"@radix-ui/react-use-previous" "0.1.1"
"@radix-ui/react-visually-hidden" "0.1.4"
aria-hidden "^1.1.1"
react-remove-scroll "^2.4.0"
"@radix-ui/react-slot@0.1.2": "@radix-ui/react-slot@0.1.2":
version "0.1.2" version "0.1.2"
resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-0.1.2.tgz#e6f7ad9caa8ce81cc8d532c854c56f9b8b6307c8" resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-0.1.2.tgz#e6f7ad9caa8ce81cc8d532c854c56f9b8b6307c8"
@ -5816,6 +5848,11 @@
"@babel/runtime" "^7.12.5" "@babel/runtime" "^7.12.5"
"@testing-library/dom" "^8.0.0" "@testing-library/dom" "^8.0.0"
"@testing-library/user-event@^14.2.1":
version "14.2.1"
resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.2.1.tgz#8c5ff2d004544bb2220e1d864f7267fe7eb6c556"
integrity sha512-HOr1QiODrq+0j9lKU5i10y9TbhxMBMRMGimNx10asdmau9cb8Xb1Vyg0GvTwyIL2ziQyh2kAloOtAQFBQVuecA==
"@tootallnate/once@1": "@tootallnate/once@1":
version "1.1.2" version "1.1.2"
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"