Improvements from the PR
This commit is contained in:
commit
2d635808de
@ -1,7 +1,7 @@
|
||||
{
|
||||
"root": true,
|
||||
"ignorePatterns": ["**/*"],
|
||||
"plugins": ["@nrwl/nx"],
|
||||
"plugins": ["@nrwl/nx", "eslint-plugin-unicorn"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
@ -18,6 +18,13 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"unicorn/filename-case": [
|
||||
"error",
|
||||
{
|
||||
"case": "kebabCase",
|
||||
"ignore": ["\\[[a-zA-Z]+\\]\\.page"]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
55
.github/workflows/cypress.yml
vendored
Normal file
55
.github/workflows/cypress.yml
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
name: Cypress tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
master:
|
||||
name: Run end-to-end tests - main
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Derive appropriate SHAs for base and head for `nx affected` commands
|
||||
uses: nrwl/nx-set-shas@v2
|
||||
with:
|
||||
main-branch-name: master
|
||||
- name: Use Node.js 16
|
||||
id: Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16
|
||||
- name: Install root dependencies
|
||||
run: yarn install
|
||||
- name: Run Cypress tests
|
||||
run: npx nx affected:e2e --parallel=5 --record --key ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
pr:
|
||||
name: Run end-to-end tests - PR
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
fetch-depth: 0
|
||||
- name: Derive appropriate SHAs for base and head for `nx affected` commands
|
||||
uses: nrwl/nx-set-shas@v2
|
||||
with:
|
||||
main-branch-name: master
|
||||
- name: Use Node.js 16
|
||||
id: Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16
|
||||
- name: Install root dependencies
|
||||
run: yarn install
|
||||
- name: Run Cypress tests
|
||||
run: npx nx affected:e2e --parallel=5 --record --key ${{ secrets.CYPRESS_RECORD_KEY }}
|
6
.github/workflows/publish.yml
vendored
6
.github/workflows/publish.yml
vendored
@ -1,11 +1,11 @@
|
||||
---
|
||||
name: Publish
|
||||
|
||||
"on":
|
||||
'on':
|
||||
push:
|
||||
tags:
|
||||
- "v[0-9]+.[0-9]+.[0-9]+"
|
||||
- "v[0-9]+.[0-9]+.[0-9]+-pre[0-9]+"
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+-pre[0-9]+'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
@ -1,6 +1,6 @@
|
||||
module.exports = {
|
||||
stories: [],
|
||||
addons: ['@storybook/addon-essentials'],
|
||||
addons: ['@storybook/addon-essentials', '@storybook/addon-a11y'],
|
||||
// uncomment the property below if you want to apply some webpack config globally
|
||||
// webpackFinal: async (config, { configType }) => {
|
||||
// // Make whatever fine-grained changes you need that should apply to all storybook configs
|
||||
|
@ -2,3 +2,11 @@ NX_CHAIN_EXPLORER_URL = "https://explorer.vega.trading/.netlify/functions/chain-
|
||||
NX_TENDERMINT_URL = "https://lb.testnet.vega.xyz/tm"
|
||||
NX_TENDERMINT_WEBSOCKET_URL = "wss://lb.testnet.vega.xyz/tm/websocket"
|
||||
NX_VEGA_URL = "https://lb.testnet.vega.xyz/query"
|
||||
|
||||
NX_EXPLORER_ASSETS = 1
|
||||
NX_EXPLORER_GENESIS = 1
|
||||
NX_EXPLORER_GOVERNANCE = 1
|
||||
NX_EXPLORER_MARKETS = 1
|
||||
NX_EXPLORER_NETWORK_PARAMETERS = 1
|
||||
NX_EXPLORER_PARTIES = 1
|
||||
NX_EXPLORER_VALIDATORS = 1
|
34
apps/explorer-e2e/.env.devent
Normal file
34
apps/explorer-e2e/.env.devent
Normal file
@ -0,0 +1,34 @@
|
||||
# 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
|
||||
NX_VERSION=$npm_package_version
|
||||
NX_REPOSITORY_URL=$REPOSITORY_URL
|
||||
NX_BRANCH=$BRANCH
|
||||
NX_PULL_REQUEST=$PULL_REQUEST
|
||||
NX_HEAD=$HEAD
|
||||
NX_COMMIT_REF=$COMMIT_REF
|
||||
NX_CONTEXT=$CONTEXT
|
||||
NX_REVIEW_ID=$REVIEW_ID
|
||||
NX_INCOMING_HOOK_TITLE=$INCOMING_HOOK_TITLE
|
||||
NX_INCOMING_HOOK_URL=$INCOMING_HOOK_URL
|
||||
NX_INCOMING_HOOK_BODY=$INCOMING_HOOK_BODY
|
||||
NX_URL=$URL
|
||||
NX_DEPLOY_URL=$DEPLOY_URL
|
||||
NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL
|
||||
|
||||
# App configuration variables
|
||||
NX_CHAIN_EXPLORER_URL = "https://explorer.vega.trading/.netlify/functions/chain-explorer-api"
|
||||
NX_TENDERMINT_URL = "https://n04.d.vega.xyz/tm"
|
||||
NX_TENDERMINT_WEBSOCKET_URL = "wss://n04.d.vega.xyz/tm/websocket"
|
||||
NX_VEGA_URL = "https://n04.d.vega.xyz/query"
|
||||
|
||||
# App flags
|
||||
NX_EXPLORER_ASSETS = 1
|
||||
NX_EXPLORER_GENESIS = 1
|
||||
NX_EXPLORER_GOVERNANCE = 1
|
||||
NX_EXPLORER_MARKETS = 1
|
||||
NX_EXPLORER_NETWORK_PARAMETERS = 1
|
||||
NX_EXPLORER_PARTIES = 1
|
||||
NX_EXPLORER_VALIDATORS = 1
|
34
apps/explorer-e2e/.env.mainnet
Normal file
34
apps/explorer-e2e/.env.mainnet
Normal file
@ -0,0 +1,34 @@
|
||||
# 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
|
||||
NX_VERSION=$npm_package_version
|
||||
NX_REPOSITORY_URL=$REPOSITORY_URL
|
||||
NX_BRANCH=$BRANCH
|
||||
NX_PULL_REQUEST=$PULL_REQUEST
|
||||
NX_HEAD=$HEAD
|
||||
NX_COMMIT_REF=$COMMIT_REF
|
||||
NX_CONTEXT=$CONTEXT
|
||||
NX_REVIEW_ID=$REVIEW_ID
|
||||
NX_INCOMING_HOOK_TITLE=$INCOMING_HOOK_TITLE
|
||||
NX_INCOMING_HOOK_URL=$INCOMING_HOOK_URL
|
||||
NX_INCOMING_HOOK_BODY=$INCOMING_HOOK_BODY
|
||||
NX_URL=$URL
|
||||
NX_DEPLOY_URL=$DEPLOY_URL
|
||||
NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL
|
||||
|
||||
# App configuration variables
|
||||
NX_CHAIN_EXPLORER_URL = "https://explorer.vega.trading/.netlify/functions/chain-explorer-api"
|
||||
NX_TENDERMINT_URL = "https://mainnet-observer-proxy01.ops.vega.xyz/"
|
||||
NX_TENDERMINT_WEBSOCKET_URL = "wss://mainnet-observer-proxy01.ops.vega.xyz/websocket"
|
||||
NX_VEGA_URL = "https://api.token.vega.xyz/query"
|
||||
|
||||
# App flags
|
||||
NX_EXPLORER_ASSETS = 1
|
||||
NX_EXPLORER_GENESIS = 1
|
||||
NX_EXPLORER_GOVERNANCE = 1
|
||||
NX_EXPLORER_MARKETS = 1
|
||||
NX_EXPLORER_NETWORK_PARAMETERS = 1
|
||||
NX_EXPLORER_PARTIES = 1
|
||||
NX_EXPLORER_VALIDATORS = 1
|
35
apps/explorer-e2e/.env.stagnet1
Normal file
35
apps/explorer-e2e/.env.stagnet1
Normal file
@ -0,0 +1,35 @@
|
||||
# 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
|
||||
NX_VERSION=$npm_package_version
|
||||
NX_REPOSITORY_URL=$REPOSITORY_URL
|
||||
NX_BRANCH=$BRANCH
|
||||
NX_PULL_REQUEST=$PULL_REQUEST
|
||||
NX_HEAD=$HEAD
|
||||
NX_COMMIT_REF=$COMMIT_REF
|
||||
NX_CONTEXT=$CONTEXT
|
||||
NX_REVIEW_ID=$REVIEW_ID
|
||||
NX_INCOMING_HOOK_TITLE=$INCOMING_HOOK_TITLE
|
||||
NX_INCOMING_HOOK_URL=$INCOMING_HOOK_URL
|
||||
NX_INCOMING_HOOK_BODY=$INCOMING_HOOK_BODY
|
||||
NX_URL=$URL
|
||||
NX_DEPLOY_URL=$DEPLOY_URL
|
||||
NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL
|
||||
|
||||
# App configuration variables
|
||||
NX_CHAIN_EXPLORER_URL = "https://explorer.vega.trading/.netlify/functions/chain-explorer-api"
|
||||
NX_TENDERMINT_URL = "https://n03.s.vega.xyz/tm"
|
||||
NX_TENDERMINT_WEBSOCKET_URL = "wss://n03.s.vega.xyz/tm/websocket"
|
||||
NX_VEGA_URL = "https://n03.s.vega.xyz/query"
|
||||
|
||||
|
||||
# App flags
|
||||
NX_EXPLORER_ASSETS = 1
|
||||
NX_EXPLORER_GENESIS = 1
|
||||
NX_EXPLORER_GOVERNANCE = 1
|
||||
NX_EXPLORER_MARKETS = 1
|
||||
NX_EXPLORER_NETWORK_PARAMETERS = 1
|
||||
NX_EXPLORER_PARTIES = 1
|
||||
NX_EXPLORER_VALIDATORS = 1
|
34
apps/explorer-e2e/.env.stagnet2
Normal file
34
apps/explorer-e2e/.env.stagnet2
Normal file
@ -0,0 +1,34 @@
|
||||
# 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
|
||||
NX_VERSION=$npm_package_version
|
||||
NX_REPOSITORY_URL=$REPOSITORY_URL
|
||||
NX_BRANCH=$BRANCH
|
||||
NX_PULL_REQUEST=$PULL_REQUEST
|
||||
NX_HEAD=$HEAD
|
||||
NX_COMMIT_REF=$COMMIT_REF
|
||||
NX_CONTEXT=$CONTEXT
|
||||
NX_REVIEW_ID=$REVIEW_ID
|
||||
NX_INCOMING_HOOK_TITLE=$INCOMING_HOOK_TITLE
|
||||
NX_INCOMING_HOOK_URL=$INCOMING_HOOK_URL
|
||||
NX_INCOMING_HOOK_BODY=$INCOMING_HOOK_BODY
|
||||
NX_URL=$URL
|
||||
NX_DEPLOY_URL=$DEPLOY_URL
|
||||
NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL
|
||||
|
||||
# App configuration variables
|
||||
NX_CHAIN_EXPLORER_URL = "https://explorer.vega.trading/.netlify/functions/chain-explorer-api"
|
||||
NX_TENDERMINT_URL = "https://n03.stagnet2.vega.xyz/tm"
|
||||
NX_TENDERMINT_WEBSOCKET_URL = "wss://n03.stagnet2.vega.xyz/tm/websocket"
|
||||
NX_VEGA_URL = "https://n03.stagnet2.vega.xyz/query"
|
||||
|
||||
# App flags
|
||||
NX_EXPLORER_ASSETS = 1
|
||||
NX_EXPLORER_GENESIS = 1
|
||||
NX_EXPLORER_GOVERNANCE = 1
|
||||
NX_EXPLORER_MARKETS = 1
|
||||
NX_EXPLORER_NETWORK_PARAMETERS = 1
|
||||
NX_EXPLORER_PARTIES = 1
|
||||
NX_EXPLORER_VALIDATORS = 1
|
34
apps/explorer-e2e/.env.testnet
Normal file
34
apps/explorer-e2e/.env.testnet
Normal file
@ -0,0 +1,34 @@
|
||||
# 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
|
||||
NX_VERSION=$npm_package_version
|
||||
NX_REPOSITORY_URL=$REPOSITORY_URL
|
||||
NX_BRANCH=$BRANCH
|
||||
NX_PULL_REQUEST=$PULL_REQUEST
|
||||
NX_HEAD=$HEAD
|
||||
NX_COMMIT_REF=$COMMIT_REF
|
||||
NX_CONTEXT=$CONTEXT
|
||||
NX_REVIEW_ID=$REVIEW_ID
|
||||
NX_INCOMING_HOOK_TITLE=$INCOMING_HOOK_TITLE
|
||||
NX_INCOMING_HOOK_URL=$INCOMING_HOOK_URL
|
||||
NX_INCOMING_HOOK_BODY=$INCOMING_HOOK_BODY
|
||||
NX_URL=$URL
|
||||
NX_DEPLOY_URL=$DEPLOY_URL
|
||||
NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL
|
||||
|
||||
# App configuration variables
|
||||
NX_CHAIN_EXPLORER_URL = "https://explorer.vega.trading/.netlify/functions/chain-explorer-api"
|
||||
NX_TENDERMINT_URL = "https://lb.testnet.vega.xyz/tm"
|
||||
NX_TENDERMINT_WEBSOCKET_URL = "wss://lb.testnet.vega.xyz/tm/websocket"
|
||||
NX_VEGA_URL = "https://lb.testnet.vega.xyz/query"
|
||||
|
||||
# App flags
|
||||
NX_EXPLORER_ASSETS = 1
|
||||
NX_EXPLORER_GENESIS = 1
|
||||
NX_EXPLORER_GOVERNANCE = 1
|
||||
NX_EXPLORER_MARKETS = 1
|
||||
NX_EXPLORER_NETWORK_PARAMETERS = 1
|
||||
NX_EXPLORER_PARTIES = 1
|
||||
NX_EXPLORER_VALIDATORS = 1
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"baseUrl": "http://localhost:4200",
|
||||
"projectId": "et4snf",
|
||||
"fileServerFolder": ".",
|
||||
"fixturesFolder": false,
|
||||
"pluginsFile": "./src/plugins/index.js",
|
||||
|
6
apps/explorer-e2e/src/integration/asset-page.feature
Normal file
6
apps/explorer-e2e/src/integration/asset-page.feature
Normal file
@ -0,0 +1,6 @@
|
||||
Feature: Asset Page
|
||||
|
||||
Scenario: Navigate to Asset Page
|
||||
Given I am on the homepage
|
||||
When I navigate to the asset page
|
||||
Then asset page is correctly displayed
|
6
apps/explorer-e2e/src/integration/blocks-page.feature
Normal file
6
apps/explorer-e2e/src/integration/blocks-page.feature
Normal file
@ -0,0 +1,6 @@
|
||||
Feature: Blocks Page
|
||||
|
||||
Scenario: Navigate to blocks page
|
||||
Given I am on the homepage
|
||||
When I navigate to the blocks page
|
||||
Then blocks page is correctly displayed
|
@ -1,4 +1,4 @@
|
||||
Feature: Home page
|
||||
|
||||
Scenario: Visit Home page
|
||||
Given I go to the homepage
|
||||
Given I am on the homepage
|
||||
|
6
apps/explorer-e2e/src/integration/markets-page.feature
Normal file
6
apps/explorer-e2e/src/integration/markets-page.feature
Normal file
@ -0,0 +1,6 @@
|
||||
Feature: Markets Page
|
||||
|
||||
Scenario: Navigate to markets page
|
||||
Given I am on the homepage
|
||||
When I navigate to the markets page
|
||||
Then markets page is correctly displayed
|
6
apps/explorer-e2e/src/integration/network-page.feature
Normal file
6
apps/explorer-e2e/src/integration/network-page.feature
Normal file
@ -0,0 +1,6 @@
|
||||
Feature: Network parameters Page
|
||||
|
||||
Scenario: Navigate to network parameters page
|
||||
Given I am on the homepage
|
||||
When I navigate to the transactions page
|
||||
Then transactions page is correctly displayed
|
@ -0,0 +1,6 @@
|
||||
Feature: Transactions Page
|
||||
|
||||
Scenario: Navigate to transactions page
|
||||
Given I am on the homepage
|
||||
When I navigate to the transactions page
|
||||
Then transactions page is correctly displayed
|
@ -0,0 +1,6 @@
|
||||
Feature: Validators Page
|
||||
|
||||
Scenario: Navigate to validators page
|
||||
Given I am on the homepage
|
||||
When I navigate to the validators page
|
||||
Then validators page is correctly displayed
|
@ -13,6 +13,7 @@ declare namespace Cypress {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
interface Chainable<Subject> {
|
||||
login(email: string, password: string): void;
|
||||
getByTestId(selector: string): Chainable<JQuery<HTMLElement>>;
|
||||
}
|
||||
}
|
||||
//
|
||||
@ -31,3 +32,6 @@ Cypress.Commands.add('login', (email, password) => {
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||
Cypress.Commands.add('getByTestId', (selector, ...args) => {
|
||||
return cy.get(`[data-testid=${selector}]`, ...args);
|
||||
});
|
||||
|
9
apps/explorer-e2e/src/support/pages/assets-page.js
Normal file
9
apps/explorer-e2e/src/support/pages/assets-page.js
Normal file
@ -0,0 +1,9 @@
|
||||
import BasePage from './base-page';
|
||||
|
||||
export default class AssetsPage extends BasePage {
|
||||
assetHeader = 'asset-header';
|
||||
|
||||
validateAssetsDisplayed() {
|
||||
this.validateBlockDataDisplayed(this.assetHeader);
|
||||
}
|
||||
}
|
79
apps/explorer-e2e/src/support/pages/base-page.js
Normal file
79
apps/explorer-e2e/src/support/pages/base-page.js
Normal file
@ -0,0 +1,79 @@
|
||||
export default class BasePage {
|
||||
transactionsUrl = '/txs';
|
||||
blocksUrl = '/blocks';
|
||||
partiesUrl = '/parties';
|
||||
assetsUrl = '/assets';
|
||||
genesisUrl = '/genesis';
|
||||
governanceUrl = '/governance';
|
||||
marketsUrl = '/markets';
|
||||
networkParametersUrl = '/network-parameters';
|
||||
validatorsUrl = '/validators';
|
||||
blockExplorerHeader = 'explorer-header';
|
||||
searchField = 'search-input';
|
||||
|
||||
navigateToTxs() {
|
||||
cy.get(`a[href='${this.transactionsUrl}']`).click();
|
||||
}
|
||||
|
||||
navigateToBlocks() {
|
||||
cy.get(`a[href='${this.blocksUrl}']`).click();
|
||||
}
|
||||
|
||||
navigateToParties() {
|
||||
cy.get(`a[href='${this.partiesUrl}']`).click();
|
||||
}
|
||||
|
||||
navigateToAssets() {
|
||||
cy.get(`a[href*='${this.assetsUrl}']`).click();
|
||||
}
|
||||
|
||||
navigateToGenesis() {
|
||||
cy.get(`a[href='${this.genesisUrl}']`).click();
|
||||
}
|
||||
|
||||
navigateToGovernance() {
|
||||
cy.get(`a[href='${this.governanceUrl}']`).click();
|
||||
}
|
||||
|
||||
navigateToMarkets() {
|
||||
cy.get(`a[href='${this.marketsUrl}']`).click();
|
||||
}
|
||||
|
||||
navigateToNetworkParameters() {
|
||||
cy.get(`a[href='${this.networkParametersUrl}']`).click();
|
||||
}
|
||||
|
||||
navigateToValidators() {
|
||||
cy.get(`a[href='${this.validatorsUrl}']`).click();
|
||||
}
|
||||
|
||||
search(searchText) {
|
||||
cy.getByTestId(this.searchField).type(searchText);
|
||||
}
|
||||
|
||||
validateSearchDisplayed() {
|
||||
cy.getByTestId(this.blockExplorerHeader).should(
|
||||
'have.text',
|
||||
'Vega Block Explorer'
|
||||
);
|
||||
cy.getByTestId(this.searchField).should('be.visible');
|
||||
}
|
||||
|
||||
validateBlockDataDisplayed(headerTestId) {
|
||||
cy.getByTestId(headerTestId).then(($assetHeaders) => {
|
||||
const headersAmount = parseInt($assetHeaders.length);
|
||||
|
||||
cy.wrap($assetHeaders).each(($header) => {
|
||||
expect($header).to.not.be.empty;
|
||||
});
|
||||
|
||||
cy.get('.language-json')
|
||||
.each(($asset, index, $list) => {
|
||||
expect($asset).to.not.be.empty;
|
||||
})
|
||||
.then(($list) => {
|
||||
expect($list).to.have.length(headersAmount);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
9
apps/explorer-e2e/src/support/pages/genesis-page.js
Normal file
9
apps/explorer-e2e/src/support/pages/genesis-page.js
Normal file
@ -0,0 +1,9 @@
|
||||
import BasePage from './base-page';
|
||||
|
||||
export default class GenesisPage extends BasePage {
|
||||
GenesisHeader = 'genesis-header';
|
||||
|
||||
genesisFieldsDisplayed() {
|
||||
this.validateBlockDataDisplayed(this.GenesisHeader);
|
||||
}
|
||||
}
|
3
apps/explorer-e2e/src/support/pages/home-page.js
Normal file
3
apps/explorer-e2e/src/support/pages/home-page.js
Normal file
@ -0,0 +1,3 @@
|
||||
import BasePage from './base-page';
|
||||
|
||||
export default class HomePage extends BasePage {}
|
9
apps/explorer-e2e/src/support/pages/markets-page.js
Normal file
9
apps/explorer-e2e/src/support/pages/markets-page.js
Normal file
@ -0,0 +1,9 @@
|
||||
import BasePage from './base-page';
|
||||
|
||||
export default class MarketsPage extends BasePage {
|
||||
marketHeaders = 'markets-header';
|
||||
|
||||
validateMarketDataDisplayed() {
|
||||
this.validateBlockDataDisplayed(this.marketHeaders);
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import BasePage from './base-page';
|
||||
|
||||
export default class NetworkParametersPage extends BasePage {
|
||||
networkParametersHeader = 'network-param-header';
|
||||
parameters = 'parameters';
|
||||
|
||||
verifyNetworkParametersDisplayed() {
|
||||
cy.getByTestId(this.networkParametersHeader).should(
|
||||
'have.text',
|
||||
'NetworkParameters'
|
||||
);
|
||||
cy.getByTestId(this.parameters).should('not.be.empty');
|
||||
}
|
||||
}
|
37
apps/explorer-e2e/src/support/pages/transactions-page.js
Normal file
37
apps/explorer-e2e/src/support/pages/transactions-page.js
Normal file
@ -0,0 +1,37 @@
|
||||
import BasePage from './base-page';
|
||||
|
||||
export default class TransactionsPage extends BasePage {
|
||||
blockRow = 'block-row';
|
||||
blockHeight = 'block-height';
|
||||
numberOfTransactions = 'num-txs';
|
||||
validatorLink = 'validator-link';
|
||||
blockTime = 'block-time';
|
||||
refreshBtn = 'refresh';
|
||||
|
||||
validateTransactionsPagedisplayed() {
|
||||
cy.getByTestId(this.blockRow).should('have.length.above', 1);
|
||||
cy.getByTestId(this.blockHeight).first().should('not.be.empty');
|
||||
cy.getByTestId(this.numberOfTransactions).first().should('not.be.empty');
|
||||
cy.getByTestId(this.validatorLink).first().should('not.be.empty');
|
||||
cy.getByTestId(this.blockTime).first().should('not.be.empty');
|
||||
}
|
||||
|
||||
validateRefreshBtn() {
|
||||
cy.getByTestId(this.blockHeight)
|
||||
.first()
|
||||
.invoke('text')
|
||||
.then((blockHeightTxt) => {
|
||||
cy.wait(1000); // eslint-disable-line cypress/no-unnecessary-waiting
|
||||
//Wait needed to allow blocks to change
|
||||
cy.getByTestId(this.refreshBtn).click();
|
||||
|
||||
cy.getByTestId(this.blockHeight)
|
||||
.first()
|
||||
.invoke('text')
|
||||
.should((newBlockHeightText) => {
|
||||
expect(blockHeightTxt).not.to.eq(newBlockHeightText);
|
||||
});
|
||||
});
|
||||
cy.getByTestId(this.blockTime).first();
|
||||
}
|
||||
}
|
18
apps/explorer-e2e/src/support/pages/validators-page.js
Normal file
18
apps/explorer-e2e/src/support/pages/validators-page.js
Normal file
@ -0,0 +1,18 @@
|
||||
import BasePage from './base-page';
|
||||
|
||||
export default class ValidatorPage extends BasePage {
|
||||
tendermintHeader = 'tendermint-header';
|
||||
vegaHeader = 'vega-header';
|
||||
tendermintData = 'tendermint-data';
|
||||
vegaData = 'vega-data';
|
||||
|
||||
validateValidatorsDisplayed() {
|
||||
cy.getByTestId(this.tendermintHeader).should(
|
||||
'have.text',
|
||||
'Tendermint data'
|
||||
);
|
||||
cy.getByTestId(this.tendermintData).should('not.be.empty');
|
||||
cy.getByTestId(this.vegaHeader).should('have.text', 'Vega data');
|
||||
cy.getByTestId(this.vegaData).should('not.be.empty');
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
import { Given, Then, When } from 'cypress-cucumber-preprocessor/steps';
|
||||
|
||||
import AssetsPage from '../pages/assets-page';
|
||||
const assetPage = new AssetsPage();
|
||||
|
||||
When('I navigate to the asset page', () => {
|
||||
assetPage.navigateToAssets();
|
||||
});
|
||||
|
||||
Then('asset page is correctly displayed', () => {
|
||||
assetPage.validateAssetsDisplayed();
|
||||
});
|
@ -0,0 +1,5 @@
|
||||
import { Given, Then, When } from 'cypress-cucumber-preprocessor/steps';
|
||||
|
||||
Given('I am on the homepage', () => {
|
||||
cy.visit('/');
|
||||
});
|
@ -1,5 +1 @@
|
||||
import { Given, Then, When } from 'cypress-cucumber-preprocessor/steps';
|
||||
|
||||
Given('I go to the homepage', () => {
|
||||
cy.visit('/');
|
||||
});
|
||||
|
@ -0,0 +1,12 @@
|
||||
import { Given, Then, When } from 'cypress-cucumber-preprocessor/steps';
|
||||
|
||||
import MarketsPage from '../pages/markets-page';
|
||||
const marketsPage = new MarketsPage();
|
||||
|
||||
When('I navigate to the markets page', () => {
|
||||
marketsPage.navigateToMarkets();
|
||||
});
|
||||
|
||||
Then('markets page is correctly displayed', () => {
|
||||
marketsPage.validateMarketDataDisplayed();
|
||||
});
|
@ -0,0 +1,22 @@
|
||||
import { Then, When } from 'cypress-cucumber-preprocessor/steps';
|
||||
|
||||
import TransactionsPage from '../pages/transactions-page';
|
||||
const transactionsPage = new TransactionsPage();
|
||||
|
||||
When('I navigate to the transactions page', () => {
|
||||
transactionsPage.navigateToTxs();
|
||||
});
|
||||
|
||||
When('I navigate to the blocks page', () => {
|
||||
transactionsPage.navigateToBlocks();
|
||||
});
|
||||
|
||||
Then('transactions page is correctly displayed', () => {
|
||||
transactionsPage.validateTransactionsPagedisplayed();
|
||||
transactionsPage.validateRefreshBtn();
|
||||
});
|
||||
|
||||
Then('blocks page is correctly displayed', () => {
|
||||
transactionsPage.validateTransactionsPagedisplayed();
|
||||
transactionsPage.validateRefreshBtn();
|
||||
});
|
@ -0,0 +1,12 @@
|
||||
import { Then, When } from 'cypress-cucumber-preprocessor/steps';
|
||||
|
||||
import ValidatorPage from '../pages/validators-page';
|
||||
const validatorsPage = new ValidatorPage();
|
||||
|
||||
When('I navigate to the validators page', () => {
|
||||
validatorsPage.navigateToValidators();
|
||||
});
|
||||
|
||||
Then('validators page is correctly displayed', () => {
|
||||
validatorsPage.validateValidatorsDisplayed();
|
||||
});
|
@ -20,6 +20,15 @@ NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL
|
||||
|
||||
# App configuration variables
|
||||
NX_CHAIN_EXPLORER_URL = "https://explorer.vega.trading/.netlify/functions/chain-explorer-api"
|
||||
NX_TENDERMINT_URL = "https://mainnet-observer-proxy01.ops.vega.xyz"
|
||||
NX_TENDERMINT_WEBSOCKET_URL = "wss://mainnet-observer-proxy01.ops.vega.xyz/websocket"
|
||||
NX_VEGA_URL = "http://mainnet-observer.ops.vega.xyz:3008/query"
|
||||
NX_TENDERMINT_URL = "https://lb.testnet.vega.xyz/tm"
|
||||
NX_TENDERMINT_WEBSOCKET_URL = "wss://lb.testnet.vega.xyz/tm/websocket"
|
||||
NX_VEGA_URL = "https://lb.testnet.vega.xyz/query"
|
||||
|
||||
# App flags
|
||||
NX_EXPLORER_ASSETS = 1
|
||||
NX_EXPLORER_GENESIS = 1
|
||||
NX_EXPLORER_GOVERNANCE = 1
|
||||
NX_EXPLORER_MARKETS = 1
|
||||
NX_EXPLORER_NETWORK_PARAMETERS = 1
|
||||
NX_EXPLORER_PARTIES = 1
|
||||
NX_EXPLORER_VALIDATORS = 1
|
||||
|
34
apps/explorer/.env.devent
Normal file
34
apps/explorer/.env.devent
Normal file
@ -0,0 +1,34 @@
|
||||
# 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
|
||||
NX_VERSION=$npm_package_version
|
||||
NX_REPOSITORY_URL=$REPOSITORY_URL
|
||||
NX_BRANCH=$BRANCH
|
||||
NX_PULL_REQUEST=$PULL_REQUEST
|
||||
NX_HEAD=$HEAD
|
||||
NX_COMMIT_REF=$COMMIT_REF
|
||||
NX_CONTEXT=$CONTEXT
|
||||
NX_REVIEW_ID=$REVIEW_ID
|
||||
NX_INCOMING_HOOK_TITLE=$INCOMING_HOOK_TITLE
|
||||
NX_INCOMING_HOOK_URL=$INCOMING_HOOK_URL
|
||||
NX_INCOMING_HOOK_BODY=$INCOMING_HOOK_BODY
|
||||
NX_URL=$URL
|
||||
NX_DEPLOY_URL=$DEPLOY_URL
|
||||
NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL
|
||||
|
||||
# App configuration variables
|
||||
NX_CHAIN_EXPLORER_URL = "https://explorer.vega.trading/.netlify/functions/chain-explorer-api"
|
||||
NX_TENDERMINT_URL = "https://n04.d.vega.xyz/tm"
|
||||
NX_TENDERMINT_WEBSOCKET_URL = "wss://n04.d.vega.xyz/tm/websocket"
|
||||
NX_VEGA_URL = "https://n04.d.vega.xyz/query"
|
||||
|
||||
# App flags
|
||||
NX_EXPLORER_ASSETS = 1
|
||||
NX_EXPLORER_GENESIS = 1
|
||||
NX_EXPLORER_GOVERNANCE = 1
|
||||
NX_EXPLORER_MARKETS = 1
|
||||
NX_EXPLORER_NETWORK_PARAMETERS = 1
|
||||
NX_EXPLORER_PARTIES = 1
|
||||
NX_EXPLORER_VALIDATORS = 1
|
34
apps/explorer/.env.mainnet
Normal file
34
apps/explorer/.env.mainnet
Normal file
@ -0,0 +1,34 @@
|
||||
# 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
|
||||
NX_VERSION=$npm_package_version
|
||||
NX_REPOSITORY_URL=$REPOSITORY_URL
|
||||
NX_BRANCH=$BRANCH
|
||||
NX_PULL_REQUEST=$PULL_REQUEST
|
||||
NX_HEAD=$HEAD
|
||||
NX_COMMIT_REF=$COMMIT_REF
|
||||
NX_CONTEXT=$CONTEXT
|
||||
NX_REVIEW_ID=$REVIEW_ID
|
||||
NX_INCOMING_HOOK_TITLE=$INCOMING_HOOK_TITLE
|
||||
NX_INCOMING_HOOK_URL=$INCOMING_HOOK_URL
|
||||
NX_INCOMING_HOOK_BODY=$INCOMING_HOOK_BODY
|
||||
NX_URL=$URL
|
||||
NX_DEPLOY_URL=$DEPLOY_URL
|
||||
NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL
|
||||
|
||||
# App configuration variables
|
||||
NX_CHAIN_EXPLORER_URL = "https://explorer.vega.trading/.netlify/functions/chain-explorer-api"
|
||||
NX_TENDERMINT_URL = "https://mainnet-observer-proxy01.ops.vega.xyz/"
|
||||
NX_TENDERMINT_WEBSOCKET_URL = "wss://mainnet-observer-proxy01.ops.vega.xyz/websocket"
|
||||
NX_VEGA_URL = "https://api.token.vega.xyz/query"
|
||||
|
||||
# App flags
|
||||
NX_EXPLORER_ASSETS = 1
|
||||
NX_EXPLORER_GENESIS = 1
|
||||
NX_EXPLORER_GOVERNANCE = 1
|
||||
NX_EXPLORER_MARKETS = 1
|
||||
NX_EXPLORER_NETWORK_PARAMETERS = 1
|
||||
NX_EXPLORER_PARTIES = 1
|
||||
NX_EXPLORER_VALIDATORS = 1
|
35
apps/explorer/.env.stagnet1
Normal file
35
apps/explorer/.env.stagnet1
Normal file
@ -0,0 +1,35 @@
|
||||
# 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
|
||||
NX_VERSION=$npm_package_version
|
||||
NX_REPOSITORY_URL=$REPOSITORY_URL
|
||||
NX_BRANCH=$BRANCH
|
||||
NX_PULL_REQUEST=$PULL_REQUEST
|
||||
NX_HEAD=$HEAD
|
||||
NX_COMMIT_REF=$COMMIT_REF
|
||||
NX_CONTEXT=$CONTEXT
|
||||
NX_REVIEW_ID=$REVIEW_ID
|
||||
NX_INCOMING_HOOK_TITLE=$INCOMING_HOOK_TITLE
|
||||
NX_INCOMING_HOOK_URL=$INCOMING_HOOK_URL
|
||||
NX_INCOMING_HOOK_BODY=$INCOMING_HOOK_BODY
|
||||
NX_URL=$URL
|
||||
NX_DEPLOY_URL=$DEPLOY_URL
|
||||
NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL
|
||||
|
||||
# App configuration variables
|
||||
NX_CHAIN_EXPLORER_URL = "https://explorer.vega.trading/.netlify/functions/chain-explorer-api"
|
||||
NX_TENDERMINT_URL = "https://n03.s.vega.xyz/tm"
|
||||
NX_TENDERMINT_WEBSOCKET_URL = "wss://n03.s.vega.xyz/tm/websocket"
|
||||
NX_VEGA_URL = "https://n03.s.vega.xyz/query"
|
||||
|
||||
|
||||
# App flags
|
||||
NX_EXPLORER_ASSETS = 1
|
||||
NX_EXPLORER_GENESIS = 1
|
||||
NX_EXPLORER_GOVERNANCE = 1
|
||||
NX_EXPLORER_MARKETS = 1
|
||||
NX_EXPLORER_NETWORK_PARAMETERS = 1
|
||||
NX_EXPLORER_PARTIES = 1
|
||||
NX_EXPLORER_VALIDATORS = 1
|
34
apps/explorer/.env.stagnet2
Normal file
34
apps/explorer/.env.stagnet2
Normal file
@ -0,0 +1,34 @@
|
||||
# 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
|
||||
NX_VERSION=$npm_package_version
|
||||
NX_REPOSITORY_URL=$REPOSITORY_URL
|
||||
NX_BRANCH=$BRANCH
|
||||
NX_PULL_REQUEST=$PULL_REQUEST
|
||||
NX_HEAD=$HEAD
|
||||
NX_COMMIT_REF=$COMMIT_REF
|
||||
NX_CONTEXT=$CONTEXT
|
||||
NX_REVIEW_ID=$REVIEW_ID
|
||||
NX_INCOMING_HOOK_TITLE=$INCOMING_HOOK_TITLE
|
||||
NX_INCOMING_HOOK_URL=$INCOMING_HOOK_URL
|
||||
NX_INCOMING_HOOK_BODY=$INCOMING_HOOK_BODY
|
||||
NX_URL=$URL
|
||||
NX_DEPLOY_URL=$DEPLOY_URL
|
||||
NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL
|
||||
|
||||
# App configuration variables
|
||||
NX_CHAIN_EXPLORER_URL = "https://explorer.vega.trading/.netlify/functions/chain-explorer-api"
|
||||
NX_TENDERMINT_URL = "https://n03.stagnet2.vega.xyz/tm"
|
||||
NX_TENDERMINT_WEBSOCKET_URL = "wss://n03.stagnet2.vega.xyz/tm/websocket"
|
||||
NX_VEGA_URL = "https://n03.stagnet2.vega.xyz/query"
|
||||
|
||||
# App flags
|
||||
NX_EXPLORER_ASSETS = 1
|
||||
NX_EXPLORER_GENESIS = 1
|
||||
NX_EXPLORER_GOVERNANCE = 1
|
||||
NX_EXPLORER_MARKETS = 1
|
||||
NX_EXPLORER_NETWORK_PARAMETERS = 1
|
||||
NX_EXPLORER_PARTIES = 1
|
||||
NX_EXPLORER_VALIDATORS = 1
|
34
apps/explorer/.env.testnet
Normal file
34
apps/explorer/.env.testnet
Normal file
@ -0,0 +1,34 @@
|
||||
# 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
|
||||
NX_VERSION=$npm_package_version
|
||||
NX_REPOSITORY_URL=$REPOSITORY_URL
|
||||
NX_BRANCH=$BRANCH
|
||||
NX_PULL_REQUEST=$PULL_REQUEST
|
||||
NX_HEAD=$HEAD
|
||||
NX_COMMIT_REF=$COMMIT_REF
|
||||
NX_CONTEXT=$CONTEXT
|
||||
NX_REVIEW_ID=$REVIEW_ID
|
||||
NX_INCOMING_HOOK_TITLE=$INCOMING_HOOK_TITLE
|
||||
NX_INCOMING_HOOK_URL=$INCOMING_HOOK_URL
|
||||
NX_INCOMING_HOOK_BODY=$INCOMING_HOOK_BODY
|
||||
NX_URL=$URL
|
||||
NX_DEPLOY_URL=$DEPLOY_URL
|
||||
NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL
|
||||
|
||||
# App configuration variables
|
||||
NX_CHAIN_EXPLORER_URL = "https://explorer.vega.trading/.netlify/functions/chain-explorer-api"
|
||||
NX_TENDERMINT_URL = "https://lb.testnet.vega.xyz/tm"
|
||||
NX_TENDERMINT_WEBSOCKET_URL = "wss://lb.testnet.vega.xyz/tm/websocket"
|
||||
NX_VEGA_URL = "https://lb.testnet.vega.xyz/query"
|
||||
|
||||
# App flags
|
||||
NX_EXPLORER_ASSETS = 1
|
||||
NX_EXPLORER_GENESIS = 1
|
||||
NX_EXPLORER_GOVERNANCE = 1
|
||||
NX_EXPLORER_MARKETS = 1
|
||||
NX_EXPLORER_NETWORK_PARAMETERS = 1
|
||||
NX_EXPLORER_PARTIES = 1
|
||||
NX_EXPLORER_VALIDATORS = 1
|
@ -74,6 +74,19 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"build-netlify": {
|
||||
"builder": "@nrwl/workspace:run-commands",
|
||||
"options": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "cp apps/explorer/netlify.toml netlify.toml"
|
||||
},
|
||||
{
|
||||
"command": "nx build explorer"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": []
|
||||
|
@ -4,7 +4,11 @@ interface BlocksRefetchProps {
|
||||
|
||||
export const BlocksRefetch = ({ refetch }: BlocksRefetchProps) => {
|
||||
return (
|
||||
<button onClick={() => refetch()} className="underline mb-28">
|
||||
<button
|
||||
onClick={() => refetch()}
|
||||
className="underline mb-28"
|
||||
data-testid="refresh"
|
||||
>
|
||||
Refresh to see latest blocks
|
||||
</button>
|
||||
);
|
||||
|
@ -3,7 +3,7 @@ import { TendermintBlockchainResponse } from '../../../routes/blocks/tendermint-
|
||||
import { Link } from 'react-router-dom';
|
||||
import { SecondsAgo } from '../../seconds-ago';
|
||||
import { TxsPerBlock } from '../../txs/txs-per-block';
|
||||
import { Table } from '../../table';
|
||||
import { Table, TableRow, TableCell } from '../../table';
|
||||
|
||||
interface BlocksProps {
|
||||
data: TendermintBlockchainResponse | undefined;
|
||||
@ -20,33 +20,39 @@ export const BlocksTable = ({ data, showTransactions }: BlocksProps) => {
|
||||
{data.result?.block_metas?.map((block, index) => {
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
<tr className="bg-neutral-850 border-b-4 border-b-black">
|
||||
<td className="pl-4 py-2">
|
||||
<TableRow dataTestId="block-row" modifier="background">
|
||||
<TableCell dataTestId="block-height" className="pl-4 py-2">
|
||||
<Link
|
||||
to={`/blocks/${block.header?.height}`}
|
||||
className="text-vega-yellow"
|
||||
>
|
||||
{block.header?.height}
|
||||
</Link>
|
||||
</td>
|
||||
<td className="px-8 text-center">
|
||||
</TableCell>
|
||||
<TableCell dataTestId="num-txs" className="px-8 text-center">
|
||||
{block.num_txs === '1'
|
||||
? '1 transaction'
|
||||
: `${block.num_txs} transactions`}
|
||||
</td>
|
||||
<td className="px-8 text-center">
|
||||
</TableCell>
|
||||
<TableCell
|
||||
dataTestId="validator-link"
|
||||
className="px-8 text-center"
|
||||
>
|
||||
<Link to={`/validators/${block.header?.proposer_address}`}>
|
||||
{block.header.proposer_address}
|
||||
</Link>
|
||||
</td>
|
||||
<td className="text-center pr-28 text-neutral-300">
|
||||
</TableCell>
|
||||
<TableCell
|
||||
dataTestId="block-time"
|
||||
className="text-center pr-28 text-neutral-300"
|
||||
>
|
||||
<SecondsAgo date={block.header?.time} />
|
||||
</td>
|
||||
</tr>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{showTransactions && (
|
||||
<tr>
|
||||
<TableRow>
|
||||
<TxsPerBlock blockHeight={block.header?.height} />
|
||||
</tr>
|
||||
</TableRow>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
|
@ -28,12 +28,16 @@ export const JumpToBlock = () => {
|
||||
</label>
|
||||
<input
|
||||
id="block-input"
|
||||
type='tel'
|
||||
type="tel"
|
||||
name={'blockNumber'}
|
||||
placeholder={'Block number'}
|
||||
className="form-input"
|
||||
className="bg-white-25 border-white border px-8 py-4 placeholder-white-60"
|
||||
/>
|
||||
<input
|
||||
className="border-white border px-28 py-4 cursor-pointer"
|
||||
type={'submit'}
|
||||
value={'Go'}
|
||||
/>
|
||||
<input className="form-submit" type={'submit'} value={'Go'} />
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
19
apps/explorer/src/app/components/route-title/index.tsx
Normal file
19
apps/explorer/src/app/components/route-title/index.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import classnames from 'classnames';
|
||||
import React from 'react';
|
||||
|
||||
interface RouteTitleProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const RouteTitle = ({ children, className }: RouteTitleProps) => {
|
||||
const classes = classnames(
|
||||
'font-alpha',
|
||||
'text-h3',
|
||||
'uppercase',
|
||||
'mt-12',
|
||||
'mb-28',
|
||||
className
|
||||
);
|
||||
return <h1 className={classes}>{children}</h1>;
|
||||
};
|
@ -91,10 +91,15 @@ const Search = () => {
|
||||
const { search, onChange } = useGuess();
|
||||
return (
|
||||
<section>
|
||||
<h1>Vega Block Explorer</h1>
|
||||
<h1 data-testid="explorer-header">Vega Block Explorer</h1>
|
||||
<fieldset>
|
||||
<label htmlFor="search">Search: </label>
|
||||
<input name="search" value={search} onChange={(e) => onChange(e)} />
|
||||
<input
|
||||
data-testid="search-field"
|
||||
name="search"
|
||||
value={search}
|
||||
onChange={(e) => onChange(e)}
|
||||
/>
|
||||
</fieldset>
|
||||
</section>
|
||||
);
|
||||
|
@ -1 +1,76 @@
|
||||
export { Table } from './table';
|
||||
import React, { ThHTMLAttributes } from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
||||
interface TableProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
interface TableHeaderProps extends ThHTMLAttributes<HTMLTableRowElement> {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
interface TableChildProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
dataTestId?: string;
|
||||
modifier?: 'bordered' | 'background';
|
||||
}
|
||||
|
||||
export const Table = ({ children }: TableProps) => {
|
||||
return (
|
||||
<div className="overflow-x-auto whitespace-nowrap mb-28">
|
||||
<table className="w-full">
|
||||
<tbody>{children}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const TableRow = ({
|
||||
children,
|
||||
className,
|
||||
dataTestId,
|
||||
modifier,
|
||||
}: TableChildProps) => {
|
||||
const cellClasses = classnames(className, {
|
||||
'border-b border-white-40': modifier === 'bordered',
|
||||
'bg-white-25 border-b-4 border-b-black': modifier === 'background',
|
||||
});
|
||||
return (
|
||||
<tr className={cellClasses} data-testid={dataTestId || null}>
|
||||
{children}
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
export const TableHeader = ({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: TableHeaderProps) => {
|
||||
const cellClasses = classnames(className, {
|
||||
'text-left, font-normal': props?.scope === 'row',
|
||||
});
|
||||
return (
|
||||
<tr className={cellClasses} {...props}>
|
||||
{children}
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
export const TableCell = ({
|
||||
children,
|
||||
className,
|
||||
dataTestId,
|
||||
modifier,
|
||||
}: TableChildProps) => {
|
||||
const cellClasses = classnames(className, {
|
||||
'py-4': modifier === 'bordered',
|
||||
});
|
||||
return (
|
||||
<td className={cellClasses} data-testid={dataTestId || null}>
|
||||
{children}
|
||||
</td>
|
||||
);
|
||||
};
|
||||
|
@ -1,15 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
interface TableProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const Table = ({ children }: TableProps) => {
|
||||
return (
|
||||
<div className="overflow-x-auto whitespace-nowrap mb-28">
|
||||
<table className="w-full">
|
||||
<tbody>{children}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -5,7 +5,6 @@ const ELLIPSIS = '\u2026';
|
||||
interface TruncateInlineProps {
|
||||
text: string | null;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
children?: (truncatedText: string) => React.ReactElement;
|
||||
startChars?: number; // number chars to show before ellipsis
|
||||
endChars?: number; // number of chars to show after ellipsis
|
||||
@ -20,7 +19,6 @@ interface TruncateInlineProps {
|
||||
export function TruncateInline({
|
||||
text,
|
||||
className,
|
||||
style,
|
||||
children,
|
||||
startChars,
|
||||
endChars,
|
||||
@ -32,7 +30,6 @@ export function TruncateInline({
|
||||
|
||||
const wrapperProps = {
|
||||
title: text,
|
||||
style,
|
||||
className,
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Routes } from '../../../routes/router-config';
|
||||
import { Result } from '../../../routes/txs/tendermint-transaction-response.d';
|
||||
import { Table } from '../../table';
|
||||
import { Table, TableRow, TableCell } from '../../table';
|
||||
|
||||
interface TxDetailsProps {
|
||||
txData: Result | undefined;
|
||||
@ -15,35 +15,35 @@ export const TxDetails = ({ txData, pubKey }: TxDetailsProps) => {
|
||||
|
||||
return (
|
||||
<Table>
|
||||
<tr>
|
||||
<td>Hash</td>
|
||||
<td>{txData.hash}</td>
|
||||
</tr>
|
||||
<TableRow>
|
||||
<TableCell>Hash</TableCell>
|
||||
<TableCell dataTestId="hash">{txData.hash}</TableCell>
|
||||
</TableRow>
|
||||
{pubKey ? (
|
||||
<tr>
|
||||
<TableRow>
|
||||
<td>Submitted by</td>
|
||||
<td>
|
||||
<td data-testid="submitted-by">
|
||||
<Link to={`/${Routes.PARTIES}/${pubKey}`}>{pubKey}</Link>
|
||||
</td>
|
||||
</tr>
|
||||
</TableRow>
|
||||
) : (
|
||||
<tr>
|
||||
<TableRow>
|
||||
<td>Submitted by</td>
|
||||
<td>Awaiting decoded transaction data</td>
|
||||
</tr>
|
||||
</TableRow>
|
||||
)}
|
||||
{txData.height ? (
|
||||
<tr>
|
||||
<TableRow>
|
||||
<td>Block</td>
|
||||
<td>
|
||||
<td data-testid="block">
|
||||
<Link to={`/blocks/${txData.height}`}>{txData.height}</Link>
|
||||
</td>
|
||||
</tr>
|
||||
</TableRow>
|
||||
) : null}
|
||||
<tr>
|
||||
<TableRow>
|
||||
<td>Encoded tnx</td>
|
||||
<td>{txData.tx}</td>
|
||||
</tr>
|
||||
<td data-testid="encoded-tnx">{txData.tx}</td>
|
||||
</TableRow>
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { WebSocketHook } from "react-use-websocket/dist/lib/types";
|
||||
import React from 'react';
|
||||
import { WebSocketHook } from 'react-use-websocket/dist/lib/types';
|
||||
|
||||
export type WebsocketContextShape = WebSocketHook;
|
||||
|
||||
@ -9,7 +9,7 @@ export const TendermintWebsocketContext =
|
||||
export function useTendermintWebsocketContext() {
|
||||
const context = React.useContext(TendermintWebsocketContext);
|
||||
if (context === null) {
|
||||
throw new Error("useWebsocket must be used within WebsocketContext");
|
||||
throw new Error('useWebsocket must be used within WebsocketContext');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
13
apps/explorer/src/app/lib/flags.ts
Normal file
13
apps/explorer/src/app/lib/flags.ts
Normal file
@ -0,0 +1,13 @@
|
||||
const truthy = ['1', 'true'];
|
||||
|
||||
export default {
|
||||
assets: truthy.includes(process.env['NX_EXPLORER_ASSETS'] || ''),
|
||||
genesis: truthy.includes(process.env['NX_EXPLORER_GENESIS'] || ''),
|
||||
governance: truthy.includes(process.env['NX_EXPLORER_GOVERNANCE'] || ''),
|
||||
markets: truthy.includes(process.env['NX_EXPLORER_MARKETS'] || ''),
|
||||
networkParameters: truthy.includes(
|
||||
process.env['NX_EXPLORER_NETWORK_PARAMETERS'] || ''
|
||||
),
|
||||
parties: truthy.includes(process.env['NX_EXPLORER_PARTIES'] || ''),
|
||||
validators: truthy.includes(process.env['NX_EXPLORER_VALIDATORS'] || ''),
|
||||
};
|
@ -39,7 +39,7 @@ const Assets = () => {
|
||||
<h1>Assets</h1>
|
||||
{data?.assets.map((a) => (
|
||||
<React.Fragment key={a.id}>
|
||||
<h2>
|
||||
<h2 data-testid="asset-header">
|
||||
{a.name} ({a.symbol})
|
||||
</h2>
|
||||
<SyntaxHighlighter data={a} />
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { DATA_SOURCES } from '../../../config';
|
||||
import useFetch from '../../../hooks/use-fetch';
|
||||
import { TendermintBlockchainResponse } from '../tendermint-blockchain-response';
|
||||
import { RouteTitle } from '../../../components/route-title';
|
||||
import { BlocksTable, BlocksRefetch } from '../../../components/blocks';
|
||||
import { JumpToBlock } from '../../../components/jump-to-block';
|
||||
|
||||
@ -15,7 +16,7 @@ const Blocks = () => {
|
||||
return (
|
||||
<>
|
||||
<section>
|
||||
<h1 className="route-header">Blocks</h1>
|
||||
<RouteTitle>Blocks</RouteTitle>
|
||||
<BlocksRefetch refetch={refetch} />
|
||||
<BlocksTable data={data} />
|
||||
</section>
|
||||
|
@ -3,9 +3,15 @@ import { Link, useParams } from 'react-router-dom';
|
||||
import { DATA_SOURCES } from '../../../config';
|
||||
import useFetch from '../../../hooks/use-fetch';
|
||||
import { TendermintBlocksResponse } from '../tendermint-blocks-response';
|
||||
import { RouteTitle } from '../../../components/route-title';
|
||||
import { TxsPerBlock } from '../../../components/txs/txs-per-block';
|
||||
import { SecondsAgo } from '../../../components/seconds-ago';
|
||||
import { Table } from '../../../components/table';
|
||||
import {
|
||||
Table,
|
||||
TableRow,
|
||||
TableHeader,
|
||||
TableCell,
|
||||
} from '../../../components/table';
|
||||
|
||||
const Block = () => {
|
||||
const { block } = useParams<{ block: string }>();
|
||||
@ -23,25 +29,25 @@ const Block = () => {
|
||||
|
||||
return (
|
||||
<section>
|
||||
<h1 className="route-header">BLOCK {block}</h1>
|
||||
<RouteTitle>BLOCK {block}</RouteTitle>
|
||||
<Table>
|
||||
<tr className="table-bordered-tr">
|
||||
<td className="table-bordered-td">Mined by</td>
|
||||
<td className="table-bordered-td">
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader scope="row">Mined by</TableHeader>
|
||||
<TableCell modifier="bordered">
|
||||
<Link
|
||||
className="text-vega-yellow"
|
||||
to={`/validators/${header.proposer_address}`}
|
||||
>
|
||||
{header.proposer_address}
|
||||
</Link>
|
||||
</td>
|
||||
</tr>
|
||||
<tr className="table-bordered-tr">
|
||||
<td className="table-bordered-td">Time</td>
|
||||
<td className="table-bordered-td">
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader scope="row">Time</TableHeader>
|
||||
<TableCell modifier="bordered">
|
||||
<SecondsAgo date={header.time} />
|
||||
</td>
|
||||
</tr>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</Table>
|
||||
{blockData?.result.block.data.txs.length > 0 && (
|
||||
<TxsPerBlock blockHeight={block} />
|
||||
|
@ -12,7 +12,7 @@ const Genesis = () => {
|
||||
if (!genesis?.result.genesis) return null;
|
||||
return (
|
||||
<section>
|
||||
<h1>Genesis</h1>
|
||||
<h1 data-testid="genesis-header">Genesis</h1>
|
||||
<SyntaxHighlighter data={genesis?.result.genesis} />
|
||||
</section>
|
||||
);
|
||||
|
@ -1,7 +1,24 @@
|
||||
import { gql, useQuery } from '@apollo/client';
|
||||
import React from 'react';
|
||||
import { SyntaxHighlighter } from '../../components/syntax-highlighter';
|
||||
import { ProposalsQuery } from './__generated__/ProposalsQuery';
|
||||
import {
|
||||
ProposalsQuery,
|
||||
ProposalsQuery_proposals_terms_change,
|
||||
} from './__generated__/ProposalsQuery';
|
||||
|
||||
export function getProposalName(change: ProposalsQuery_proposals_terms_change) {
|
||||
if (change.__typename === 'NewAsset') {
|
||||
return `New asset: ${change.symbol}`;
|
||||
} else if (change.__typename === 'NewMarket') {
|
||||
return `New market: ${change.instrument.name}`;
|
||||
} else if (change.__typename === 'UpdateMarket') {
|
||||
return `Update market: ${change.marketId}`;
|
||||
} else if (change.__typename === 'UpdateNetworkParameter') {
|
||||
return `Update network: ${change.networkParameter.key}`;
|
||||
}
|
||||
|
||||
return 'Unknown proposal';
|
||||
}
|
||||
|
||||
const PROPOSAL_QUERY = gql`
|
||||
query ProposalsQuery {
|
||||
@ -90,7 +107,7 @@ const Governance = () => {
|
||||
{data.proposals.map((p) => (
|
||||
<React.Fragment key={p.id}>
|
||||
{/* TODO get proposal name generator from console */}
|
||||
<h2>{p.id}</h2>
|
||||
<h2>{getProposalName(p.terms.change)}</h2>
|
||||
<SyntaxHighlighter data={p} />
|
||||
</React.Fragment>
|
||||
))}
|
||||
|
@ -153,7 +153,7 @@ const Markets = () => {
|
||||
<h1>Markets</h1>
|
||||
{data.markets.map((m) => (
|
||||
<React.Fragment key={m.id}>
|
||||
<h2>{m.name}</h2>
|
||||
<h2 data-testid="markets-header">{m.name}</h2>
|
||||
<SyntaxHighlighter data={m} />
|
||||
</React.Fragment>
|
||||
))}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { gql, useQuery } from "@apollo/client";
|
||||
import { NetworkParametersQuery } from "./__generated__/NetworkParametersQuery";
|
||||
import { gql, useQuery } from '@apollo/client';
|
||||
import { NetworkParametersQuery } from './__generated__/NetworkParametersQuery';
|
||||
|
||||
export const NETWORK_PARAMETERS_QUERY = gql`
|
||||
query NetworkParametersQuery {
|
||||
@ -14,8 +14,8 @@ const NetworkParameters = () => {
|
||||
const { data } = useQuery<NetworkParametersQuery>(NETWORK_PARAMETERS_QUERY);
|
||||
return (
|
||||
<section>
|
||||
<h1>NetworkParameters</h1>
|
||||
<pre>{JSON.stringify(data, null, " ")}</pre>
|
||||
<h1 data-testid="network-param-header">NetworkParameters</h1>
|
||||
<pre data-testid="parameters">{JSON.stringify(data, null, ' ')}</pre>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
@ -15,7 +15,7 @@ import { Blocks } from './blocks/home';
|
||||
import { Tx } from './txs/id';
|
||||
import { Txs as TxHome } from './txs/home';
|
||||
import { PendingTxs } from './pending';
|
||||
|
||||
import flags from '../lib/flags';
|
||||
export const Routes = {
|
||||
HOME: '/',
|
||||
TX: 'txs',
|
||||
@ -29,6 +29,85 @@ export const Routes = {
|
||||
NETWORK_PARAMETERS: 'network-parameters',
|
||||
};
|
||||
|
||||
const partiesRoutes = flags.parties
|
||||
? [
|
||||
{
|
||||
path: Routes.PARTIES,
|
||||
name: 'Parties',
|
||||
element: <Party />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Parties />,
|
||||
},
|
||||
{
|
||||
path: ':party',
|
||||
element: <PartySingle />,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const assetsRoutes = flags.assets
|
||||
? [
|
||||
{
|
||||
path: Routes.ASSETS,
|
||||
name: 'Assets',
|
||||
element: <Assets />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const genesisRoutes = flags.genesis
|
||||
? [
|
||||
{
|
||||
path: Routes.GENESIS,
|
||||
name: 'Genesis',
|
||||
element: <Genesis />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const governanceRoutes = flags.governance
|
||||
? [
|
||||
{
|
||||
path: Routes.GOVERNANCE,
|
||||
name: 'Governance',
|
||||
element: <Governance />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const marketsRoutes = flags.markets
|
||||
? [
|
||||
{
|
||||
path: Routes.MARKETS,
|
||||
name: 'Markets',
|
||||
element: <Markets />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const networkParametersRoutes = flags.networkParameters
|
||||
? [
|
||||
{
|
||||
path: Routes.NETWORK_PARAMETERS,
|
||||
name: 'NetworkParameters',
|
||||
element: <NetworkParameters />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
const validators = flags.validators
|
||||
? [
|
||||
{
|
||||
path: Routes.VALIDATORS,
|
||||
name: 'Validators',
|
||||
element: <Validators />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const routerConfig = [
|
||||
{
|
||||
path: Routes.HOME,
|
||||
@ -70,51 +149,13 @@ const routerConfig = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: Routes.PARTIES,
|
||||
name: 'Parties',
|
||||
element: <Party />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Parties />,
|
||||
},
|
||||
{
|
||||
path: ':party',
|
||||
element: <PartySingle />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: Routes.ASSETS,
|
||||
name: 'Assets',
|
||||
element: <Assets />,
|
||||
},
|
||||
{
|
||||
path: Routes.GENESIS,
|
||||
name: 'Genesis',
|
||||
element: <Genesis />,
|
||||
},
|
||||
{
|
||||
path: Routes.GOVERNANCE,
|
||||
name: 'Governance',
|
||||
element: <Governance />,
|
||||
},
|
||||
{
|
||||
path: Routes.MARKETS,
|
||||
name: 'Markets',
|
||||
element: <Markets />,
|
||||
},
|
||||
{
|
||||
path: Routes.NETWORK_PARAMETERS,
|
||||
name: 'NetworkParameters',
|
||||
element: <NetworkParameters />,
|
||||
},
|
||||
{
|
||||
path: Routes.VALIDATORS,
|
||||
name: 'Validators',
|
||||
element: <Validators />,
|
||||
},
|
||||
...partiesRoutes,
|
||||
...assetsRoutes,
|
||||
...genesisRoutes,
|
||||
...governanceRoutes,
|
||||
...marketsRoutes,
|
||||
...networkParametersRoutes,
|
||||
...validators,
|
||||
];
|
||||
|
||||
export default routerConfig;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import useFetch from '../../../hooks/use-fetch';
|
||||
import { TendermintBlockchainResponse } from '../../blocks/tendermint-blockchain-response';
|
||||
import { DATA_SOURCES } from '../../../config';
|
||||
import { RouteTitle } from '../../../components/route-title';
|
||||
import { BlocksTable, BlocksRefetch } from '../../../components/blocks';
|
||||
import { JumpToBlock } from '../../../components/jump-to-block';
|
||||
|
||||
@ -15,7 +16,7 @@ const Txs = () => {
|
||||
return (
|
||||
<>
|
||||
<section>
|
||||
<h1>Transactions</h1>
|
||||
<RouteTitle>Transactions</RouteTitle>
|
||||
<BlocksRefetch refetch={refetch} />
|
||||
<BlocksTable data={data} showTransactions={true} />
|
||||
</section>
|
||||
|
@ -44,10 +44,12 @@ const Validators = () => {
|
||||
return (
|
||||
<section>
|
||||
<h1>Validators</h1>
|
||||
<h2>Tendermint data</h2>
|
||||
<pre>{JSON.stringify(validators, null, ' ')}</pre>
|
||||
<h2>Vega data</h2>
|
||||
<pre>{JSON.stringify(data, null, ' ')}</pre>
|
||||
<h2 data-testid="tendermint-header">Tendermint data</h2>
|
||||
<pre data-testid="tendermint-data">
|
||||
{JSON.stringify(validators, null, ' ')}
|
||||
</pre>
|
||||
<h2 data-testid="vega-header">Vega data</h2>
|
||||
<pre data-testid="vega-data">{JSON.stringify(data, null, ' ')}</pre>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
@ -1,3 +1,5 @@
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { BrowserTracing } from '@sentry/tracing';
|
||||
import { StrictMode } from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
@ -5,6 +7,18 @@ import './styles.css';
|
||||
|
||||
import App from './app/app';
|
||||
|
||||
const dsn = process.env['NX_SENTRY_DSN'];
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (dsn) {
|
||||
Sentry.init({
|
||||
dsn,
|
||||
integrations: [new BrowserTracing()],
|
||||
tracesSampleRate: 0.1,
|
||||
environment: process.env['NODE_ENV'],
|
||||
});
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<StrictMode>
|
||||
<BrowserRouter>
|
||||
|
@ -1,26 +1,4 @@
|
||||
/* You can add global styles to this file, and also import other style files */
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
|
||||
.route-header {
|
||||
@apply font-alpha text-h3 uppercase mt-12 mb-28;
|
||||
}
|
||||
|
||||
.table-bordered-tr {
|
||||
@apply border-b border-neutral-500;
|
||||
}
|
||||
|
||||
.table-bordered-td {
|
||||
@apply py-4;
|
||||
}
|
||||
|
||||
/* Used for text, tel input */
|
||||
.form-input {
|
||||
@apply bg-neutral-800 border-white border px-8 py-4 placeholder-neutral-300;
|
||||
}
|
||||
|
||||
.form-submit {
|
||||
@apply border-white border px-28 py-4;
|
||||
}
|
||||
|
||||
@tailwind utilities;
|
||||
|
@ -5,7 +5,7 @@
|
||||
"next",
|
||||
"next/core-web-vitals"
|
||||
],
|
||||
"ignorePatterns": ["!**/*"],
|
||||
"ignorePatterns": ["!**/*", "__generated__"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
|
@ -1,13 +1,14 @@
|
||||
export function Vega({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg className="w-[76px] h-[16px]" viewBox="0 0 283.5 61.3">
|
||||
<path d="M26.6,53.1L44,0h8.8L31.2,61.3h-9.5L0,0h8.9L26.6,53.1z" />
|
||||
<path d="M89.6,33.3v21.1h34.3v6.9H81.3V0h41.7V7H89.6v19.3h29.8v6.9H89.6z" />
|
||||
<path
|
||||
d="M156.8,7.5h7.4V54h-7.4V7.5z M193.7,0v7.5h-29.5V0H193.7z M164.2,61.3V54h29.5v7.4H164.2z M201.1,54h-7.4V39.2H179v-7.4
|
||||
h22.1V54z M201.1,7.5v7.4h-7.4V7.5H201.1z"
|
||||
/>
|
||||
<path d="M283.5,61.3h-8.6L270.4,49h-30.8L235,61.3h-8.4L250.4,0h9.3L283.5,61.3z M254.8,7.4L242.2,42h25.6L255,7.4H254.8z" />
|
||||
<svg
|
||||
width="86"
|
||||
height="19"
|
||||
viewBox="0 0 86 19"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<path d="M8.05624 16.4619L13.3478 0.0158333H16.0045L9.45138 18.9881H6.58031L0 0.0158333H2.70328L8.05624 16.4619ZM27.1835 10.3067V16.8372H37.5787V18.9873H24.6884V0.0150417H37.3503V2.16442H27.1835V8.16288H36.2395V10.3067H27.1835ZM47.5793 2.31167H49.8165V16.716H47.5793V2.31167V2.31167ZM58.7676 0V2.31167H49.8157V0H58.7683H58.7676ZM49.8157 19V16.716H58.7683V19H49.8157ZM61.0055 16.716H58.7683V12.1481H54.2924V9.86021H61.0055V16.716ZM61.0055 2.31167V4.59483H58.7683V2.31167H61.0055V2.31167ZM86 18.9873H83.3946L82.0251 15.1668H72.6801L71.3106 18.9873H68.7635L75.9769 0.0150417H78.7936L86 18.9873V18.9873ZM77.3138 2.299L73.4694 13.0213H81.239L77.3674 2.29821H77.313L77.3138 2.299Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
@ -1,21 +1,18 @@
|
||||
import { useRouter } from 'next/router';
|
||||
import classNames from 'classnames';
|
||||
import { Vega } from '../icons/vega';
|
||||
import Link from 'next/link';
|
||||
import { AnchorButton } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export const Navbar = () => {
|
||||
const navClasses = classNames(
|
||||
'flex items-center',
|
||||
'border-neutral-200 border-b'
|
||||
);
|
||||
return (
|
||||
<nav className={navClasses}>
|
||||
<nav className="flex items-center">
|
||||
<Link href="/" passHref={true}>
|
||||
<a className="px-8">
|
||||
<Vega />
|
||||
<a className="px-[26px]">
|
||||
<Vega className="fill-black dark:fill-white" />
|
||||
</a>
|
||||
</Link>
|
||||
{[
|
||||
{ name: 'Trading', path: '/', exact: true },
|
||||
{ name: 'Portfolio', path: '/portfolio' },
|
||||
{ name: 'Markets', path: '/markets' },
|
||||
].map((route) => (
|
||||
@ -28,18 +25,17 @@ export const Navbar = () => {
|
||||
interface NavLinkProps {
|
||||
name: string;
|
||||
path: string;
|
||||
exact?: boolean;
|
||||
}
|
||||
|
||||
const NavLink = ({ name, path }: NavLinkProps) => {
|
||||
const NavLink = ({ name, path, exact }: NavLinkProps) => {
|
||||
const router = useRouter();
|
||||
const className = classNames('inline-block', 'p-8', {
|
||||
// Handle direct math and child page matches
|
||||
'text-vega-pink': router.asPath === path || router.asPath.startsWith(path),
|
||||
});
|
||||
|
||||
const isActive =
|
||||
router.asPath === path || (!exact && router.asPath.startsWith(path));
|
||||
return (
|
||||
<a
|
||||
className={className}
|
||||
<AnchorButton
|
||||
variant={isActive ? 'accent' : 'inline'}
|
||||
className="px-16 h-[38px] text-h4 uppercase border-0"
|
||||
href={path}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
@ -47,6 +43,6 @@ const NavLink = ({ name, path }: NavLinkProps) => {
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
</a>
|
||||
</AnchorButton>
|
||||
);
|
||||
};
|
||||
|
@ -1,20 +1,45 @@
|
||||
import { ApolloProvider } from '@apollo/client';
|
||||
import { AppProps } from 'next/app';
|
||||
import Head from 'next/head';
|
||||
import { useMemo } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { Navbar } from '../components/navbar';
|
||||
import { createClient } from '../lib/apollo-client';
|
||||
import { ThemeSwitcher } from '@vegaprotocol/ui-toolkit';
|
||||
import './styles.css';
|
||||
|
||||
function VegaTradingApp({ Component, pageProps }: AppProps) {
|
||||
const client = useMemo(() => createClient(process.env['NX_VEGA_URL']), []);
|
||||
useCallback(() => {
|
||||
if (
|
||||
localStorage.theme === 'dark' ||
|
||||
(!('theme' in localStorage) &&
|
||||
window.matchMedia('(prefers-color-scheme: dark)').matches)
|
||||
) {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
}, []);
|
||||
const setTheme = () => {
|
||||
localStorage.theme = document.documentElement.classList.toggle('dark')
|
||||
? 'dark'
|
||||
: undefined;
|
||||
};
|
||||
return (
|
||||
<ApolloProvider client={client}>
|
||||
<Head>
|
||||
<title>Welcome to trading!</title>
|
||||
<link
|
||||
rel="icon"
|
||||
href="https://vega.xyz/favicon-32x32.png"
|
||||
type="image/png"
|
||||
/>
|
||||
</Head>
|
||||
<div className="h-full grid grid-rows-[min-content,_1fr]">
|
||||
<Navbar />
|
||||
<div className="h-full dark:bg-black dark:text-white-60 bg-white text-black-60">
|
||||
<div className="flex items-center border-b-[7px] border-vega-yellow">
|
||||
<Navbar />
|
||||
<ThemeSwitcher onToggle={setTheme} className="ml-auto mr-8 -my-2" />
|
||||
</div>
|
||||
<main>
|
||||
<Component {...pageProps} />
|
||||
</main>
|
||||
|
@ -1,6 +1,4 @@
|
||||
import { EtherscanLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { Callout } from '@vegaprotocol/ui-toolkit';
|
||||
import { ReactHelpers } from '@vegaprotocol/react-helpers';
|
||||
import { Callout, Button } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export function Index() {
|
||||
/*
|
||||
@ -9,12 +7,20 @@ export function Index() {
|
||||
* Note: The corresponding styles are in the ./index.scss file.
|
||||
*/
|
||||
return (
|
||||
<div>
|
||||
<Callout title="Hello there" headingLevel={1}>
|
||||
Welcome trading 👋
|
||||
<div className="m-24 ">
|
||||
<Callout
|
||||
intent="help"
|
||||
title="This is what this thing does"
|
||||
iconName="endorsed"
|
||||
headingLevel={1}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<div>With a longer explaination</div>
|
||||
<Button className="block mt-8" variant="secondary">
|
||||
Action
|
||||
</Button>
|
||||
</div>
|
||||
</Callout>
|
||||
<EtherscanLink chainId={null} address="address" />
|
||||
<ReactHelpers />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -44,26 +44,26 @@ export const GridTabs = ({ children, group }: GridTabsProps) => {
|
||||
className="h-full grid grid-rows-[min-content_1fr]"
|
||||
onValueChange={(value) => setActiveTab(value)}
|
||||
>
|
||||
<Tabs.List className="flex gap-[2px] bg-neutral-200" role="tablist">
|
||||
<Tabs.List
|
||||
className="flex flex-nowrap gap-4 overflow-x-auto my-4"
|
||||
role="tablist"
|
||||
>
|
||||
{Children.map(children, (child) => {
|
||||
if (!isValidElement(child)) return null;
|
||||
const isActive = child.props.name === activeTab;
|
||||
const triggerClass = classNames(
|
||||
'py-4',
|
||||
'px-12',
|
||||
'border-t border-neutral-200',
|
||||
'capitalize',
|
||||
{
|
||||
'text-vega-pink': isActive,
|
||||
'bg-white': isActive,
|
||||
}
|
||||
);
|
||||
const triggerClass = classNames('py-4', 'px-12', 'capitalize', {
|
||||
'text-black dark:text-vega-yellow': isActive,
|
||||
'bg-white dark:bg-black': isActive,
|
||||
'text-black dark:text-white': !isActive,
|
||||
'bg-black-10 dark:bg-white-10': !isActive,
|
||||
});
|
||||
return (
|
||||
<Tabs.Trigger value={child.props.name} className={triggerClass}>
|
||||
{child.props.name}
|
||||
</Tabs.Trigger>
|
||||
);
|
||||
})}
|
||||
<div className="bg-black-10 dark:bg-white-10 grow"></div>
|
||||
</Tabs.List>
|
||||
<div className="h-full overflow-auto">
|
||||
{Children.map(children, (child) => {
|
||||
|
@ -18,7 +18,7 @@ export const TradeGrid = ({ market }: TradeGridProps) => {
|
||||
);
|
||||
return (
|
||||
<div className={wrapperClasses}>
|
||||
<header className="col-start-1 col-end-2 row-start-1 row-end-1 bg-white p-8">
|
||||
<header className="col-start-1 col-end-2 row-start-1 row-end-1 p-8">
|
||||
<h1>Market: {market.name}</h1>
|
||||
</header>
|
||||
<TradeGridChild className="col-start-1 col-end-2">
|
||||
@ -93,7 +93,7 @@ export const TradePanels = ({ market }: TradePanelsProps) => {
|
||||
|
||||
return (
|
||||
<div className="h-full grid grid-rows-[min-content_1fr_min-content]">
|
||||
<header className="bg-white p-8">
|
||||
<header className="p-8">
|
||||
<h1>Market: {market.name}</h1>
|
||||
</header>
|
||||
<div className="h-full">
|
||||
@ -103,18 +103,15 @@ export const TradePanels = ({ market }: TradePanelsProps) => {
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
<div className="flex flex-nowrap gap-2 bg-neutral-200 border-neutral-200 border-t overflow-x-auto">
|
||||
<div className="flex flex-nowrap gap-4 overflow-x-auto my-4">
|
||||
{Object.keys(TradingViews).map((key: TradingView) => {
|
||||
const className = classNames(
|
||||
'p-8',
|
||||
'border-t',
|
||||
'border-neutral-200',
|
||||
'capitalize',
|
||||
{
|
||||
'text-vega-pink': view === key,
|
||||
'bg-white': view === key,
|
||||
}
|
||||
);
|
||||
const isActive = view === key;
|
||||
const className = classNames('py-4', 'px-12', 'capitalize', {
|
||||
'text-black dark:text-vega-yellow': isActive,
|
||||
'bg-white dark:bg-black': isActive,
|
||||
'text-black dark:text-white': !isActive,
|
||||
'bg-black-10 dark:bg-white-10': !isActive,
|
||||
});
|
||||
return (
|
||||
<button
|
||||
onClick={() => setView(key)}
|
||||
@ -125,6 +122,7 @@ export const TradePanels = ({ market }: TradePanelsProps) => {
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
<div className="bg-black-10 dark:bg-white-10 grow"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -8,72 +8,48 @@ module.exports = {
|
||||
colors: {
|
||||
transparent: 'transparent',
|
||||
current: 'currentColor',
|
||||
black: '#000',
|
||||
white: '#FFF',
|
||||
|
||||
neutral: {
|
||||
// 250 - 23 = 227; (900-50) / 227 = 850 / 227 = 3.74449339207
|
||||
50: '#fafafa', // FA = 250
|
||||
100: '#ebebeb',
|
||||
150: '#dcdcdc',
|
||||
200: '#cdcdcd',
|
||||
250: '#bebebe',
|
||||
300: '#afafaf',
|
||||
350: '#a1a1a1',
|
||||
400: '#939393',
|
||||
450: '#858585',
|
||||
500: '#787878',
|
||||
550: '#6a6a6a',
|
||||
593: '#696969', // dark muted
|
||||
600: '#5d5d5d',
|
||||
650: '#515151',
|
||||
700: '#444444',
|
||||
753: '#3E3E3E', // dark -> 3F is muted
|
||||
750: '#383838',
|
||||
800: '#2d2d2d', // breakdown-background was 2C
|
||||
850: '#222222',
|
||||
900: '#171717', // 17 = 23
|
||||
white: {
|
||||
DEFAULT: '#FFF',
|
||||
'02': 'rgba(255, 255, 255, 0.02)',
|
||||
'05': 'rgba(255, 255, 255, 0.05)',
|
||||
10: 'rgba(255, 255, 255, 0.10)',
|
||||
25: 'rgba(255, 255, 255, 0.25)',
|
||||
40: 'rgba(255, 255, 255, 0.40)',
|
||||
60: 'rgba(255, 255, 255, 0.60)',
|
||||
80: 'rgba(255, 255, 255, 0.80)',
|
||||
95: 'rgba(255, 255, 255, 0.95)',
|
||||
100: 'rgba(255, 255, 255, 1.00)',
|
||||
},
|
||||
|
||||
'light-gray-50': '#F5F8FA', //off-white - https://blueprintjs.com/docs/#core/colors
|
||||
'gray-50': '#BFCCD6', // muted - https://blueprintjs.com/docs/#core/colors
|
||||
black: {
|
||||
DEFAULT: '#000',
|
||||
'02': 'rgba(0, 0, 0, 0.02)',
|
||||
'05': 'rgba(0, 0, 0, 0.05)',
|
||||
10: 'rgba(0, 0, 0, 0.10)',
|
||||
25: 'rgba(0, 0, 0, 0.25)',
|
||||
40: 'rgba(0, 0, 0, 0.40)',
|
||||
60: 'rgba(0, 0, 0, 0.60)',
|
||||
80: 'rgba(0, 0, 0, 0.80)',
|
||||
95: 'rgba(0, 0, 0, 0.95)',
|
||||
100: 'rgba(0, 0, 0, 1)',
|
||||
},
|
||||
blue: '#1DA2FB',
|
||||
coral: '#FF6057',
|
||||
// below colors are not defined as atoms
|
||||
vega: {
|
||||
yellow: '#EDFF22',
|
||||
pink: '#FF2D5E',
|
||||
green: '#00F780',
|
||||
},
|
||||
'vega-yellow-dark': '#474B0A', // yellow 0.3 opacity on black
|
||||
intent: {
|
||||
danger: '#FF261A',
|
||||
warning: '#FF7A1A',
|
||||
prompt: '#EDFF22',
|
||||
progress: '#FFF',
|
||||
success: '#26FF8A',
|
||||
help: '#494949',
|
||||
background: {
|
||||
danger: '#9E0025', // for white text
|
||||
},
|
||||
} /*,
|
||||
data: {
|
||||
red: {
|
||||
white: {
|
||||
50: '#FFFFFF',
|
||||
220: '#FF6057', // overlay FFF 80%
|
||||
390: '#FF6057', // overlay FFF 60%
|
||||
560: '#FF6057', // overlay FFF 40%
|
||||
730: '#FF6057', // overlay FFF 20%
|
||||
900: '#FF6057',
|
||||
},
|
||||
green: {
|
||||
50: '#30F68B',
|
||||
220: '#89DC50',
|
||||
475: '#F2BD09',
|
||||
730: '#FF8501',
|
||||
900: '#FF6057',
|
||||
},
|
||||
},
|
||||
},*/,
|
||||
},
|
||||
'intent-background': {
|
||||
danger: '#9E0025', // for white text
|
||||
},
|
||||
},
|
||||
spacing: {
|
||||
0: '0px',
|
||||
@ -81,18 +57,37 @@ module.exports = {
|
||||
4: '0.25rem',
|
||||
8: '0.5rem',
|
||||
12: '0.75rem',
|
||||
16: '1rem',
|
||||
20: '1.25rem',
|
||||
24: '1.5rem',
|
||||
28: '1.75rem',
|
||||
32: '2rem',
|
||||
44: '2.75rem',
|
||||
},
|
||||
backgroundColor: ({ theme }) => ({
|
||||
transparent: 'transparent',
|
||||
neutral: theme('colors.neutral'),
|
||||
dark: theme('colors.dark'),
|
||||
black: '#000',
|
||||
white: theme('colors.white'),
|
||||
danger: theme('colors.intent.background.danger'),
|
||||
'neutral-200': theme('colors.neutral.200'),
|
||||
}),
|
||||
opacity: {
|
||||
0: '0',
|
||||
2: '0.02',
|
||||
5: '0.05',
|
||||
10: '0.1',
|
||||
15: '0.15',
|
||||
20: '0.2',
|
||||
25: '0.25',
|
||||
30: '0.3',
|
||||
35: '0.35',
|
||||
40: '0.4',
|
||||
45: '0.45',
|
||||
50: '0.5',
|
||||
55: '0.55',
|
||||
60: '0.6',
|
||||
65: '0.65',
|
||||
70: '0.7',
|
||||
75: '0.75',
|
||||
80: '0.8',
|
||||
85: '0.85',
|
||||
90: '0.9',
|
||||
95: '0.95',
|
||||
100: '1',
|
||||
},
|
||||
borderWidth: {
|
||||
DEFAULT: '1px',
|
||||
1: '1px',
|
||||
@ -132,12 +127,12 @@ module.exports = {
|
||||
],
|
||||
},
|
||||
fontSize: {
|
||||
h1: ['72px', { lineHeight: '92px', letterSpacing: '-1%' }],
|
||||
h2: ['48px', { lineHeight: '64px', letterSpacing: '-1%' }],
|
||||
h3: ['32px', { lineHeight: '40px', letterSpacing: '-1%' }],
|
||||
h1: ['72px', { lineHeight: '92px', letterSpacing: '-0.01em' }],
|
||||
h2: ['48px', { lineHeight: '64px', letterSpacing: '-0.01em' }],
|
||||
h3: ['32px', { lineHeight: '40px', letterSpacing: '-0.01em' }],
|
||||
|
||||
h4: ['24px', { lineHeight: '36px', letterSpacing: '-1%' }],
|
||||
h5: ['18px', { lineHeight: '28px', letterSpacing: '-1%' }],
|
||||
h4: ['24px', { lineHeight: '36px', letterSpacing: '-0.01em' }],
|
||||
h5: ['18px', { lineHeight: '28px', letterSpacing: '-0.01em' }],
|
||||
|
||||
'body-large': ['16px', '24px'],
|
||||
body: ['14px', '20px'],
|
||||
@ -148,7 +143,9 @@ module.exports = {
|
||||
|
||||
extend: {
|
||||
boxShadow: {
|
||||
callout: '5px 5px 0 1px rgba(0, 0, 0, 0.05)',
|
||||
callout: '5px 5px 0 1px rgba(255, 255, 255, 0.05)',
|
||||
focus: '0px 0px 0px 1px #FFFFFF, 0px 0px 3px 2px #FFE600',
|
||||
'focus-dark': '0px 0px 0px 1px #000000, 0px 0px 3px 2px #FFE600',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -1,19 +1,29 @@
|
||||
import '../src/styles.scss';
|
||||
export const parameters = {
|
||||
actions: { argTypesRegex: '^on[A-Z].*' },
|
||||
themes: {
|
||||
/*themes: {
|
||||
default: 'dark',
|
||||
list: [
|
||||
{ name: 'dark', class: ['dark', 'bg-black'], color: '#000' },
|
||||
{ name: 'light', class: '', color: '#FFF' },
|
||||
],
|
||||
},
|
||||
},*/
|
||||
};
|
||||
|
||||
export const decorators = [
|
||||
(Story) => (
|
||||
<div className="dark bg-black">
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
(Story, context) =>
|
||||
context.parameters.themes === false ? (
|
||||
<div className="text-body">
|
||||
<Story />
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-body">
|
||||
<div className="dark bg-black p-16">
|
||||
<Story />
|
||||
</div>
|
||||
<div className="p-16">
|
||||
<Story />
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
];
|
||||
|
10
libs/ui-toolkit/src/components/button/button.spec.tsx
Normal file
10
libs/ui-toolkit/src/components/button/button.spec.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import { Button } from './button';
|
||||
|
||||
describe('Button', () => {
|
||||
it('should render successfully', () => {
|
||||
const { baseElement } = render(<Button>Label</Button>);
|
||||
expect(baseElement).toBeTruthy();
|
||||
});
|
||||
});
|
101
libs/ui-toolkit/src/components/button/button.stories.tsx
Normal file
101
libs/ui-toolkit/src/components/button/button.stories.tsx
Normal file
@ -0,0 +1,101 @@
|
||||
import { Story, Meta } from '@storybook/react';
|
||||
import { Button } from './button';
|
||||
|
||||
export default {
|
||||
component: Button,
|
||||
title: 'Button',
|
||||
} as Meta;
|
||||
|
||||
const Template: Story = (args) => (
|
||||
<>
|
||||
<div className="mb-8">
|
||||
<Button {...args} />
|
||||
</div>
|
||||
{args['variant'] !== 'inline' && <Button {...args} disabled />}
|
||||
</>
|
||||
);
|
||||
|
||||
export const Primary = Template.bind({});
|
||||
Primary.args = {
|
||||
children: 'Primary',
|
||||
};
|
||||
|
||||
export const Secondary = Template.bind({});
|
||||
Secondary.args = {
|
||||
children: 'Secondary',
|
||||
variant: 'secondary',
|
||||
};
|
||||
|
||||
export const Accent = Template.bind({});
|
||||
Accent.args = {
|
||||
children: 'Accent',
|
||||
variant: 'accent',
|
||||
};
|
||||
|
||||
export const Inline = Template.bind({});
|
||||
Inline.args = {
|
||||
children: 'Inline',
|
||||
variant: 'inline',
|
||||
};
|
||||
|
||||
export const NavAccent: Story = (args) => (
|
||||
<>
|
||||
<div className="mb-8">
|
||||
<Button variant="accent" className="px-4">
|
||||
Background
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mb-8">
|
||||
<Button variant="accent" className="px-4" prependIconName="menu-open">
|
||||
Background
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mb-8">
|
||||
<Button variant="accent" className="px-4" appendIconName="menu-closed">
|
||||
Background
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
export const NavInline: Story = (args) => (
|
||||
<>
|
||||
<div className="mb-8">
|
||||
<Button variant="inline" className="uppercase">
|
||||
Background
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mb-8">
|
||||
<Button
|
||||
variant="inline"
|
||||
className="uppercase"
|
||||
prependIconName="menu-open"
|
||||
>
|
||||
Background
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mb-8">
|
||||
<Button
|
||||
variant="inline"
|
||||
className="uppercase"
|
||||
appendIconName="menu-closed"
|
||||
>
|
||||
Background
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
export const IconPrepend = Template.bind({});
|
||||
IconPrepend.args = {
|
||||
children: 'Icon prepend',
|
||||
prependIconName: 'search',
|
||||
variant: 'accent',
|
||||
};
|
||||
|
||||
export const IconAppend = Template.bind({});
|
||||
IconAppend.args = {
|
||||
children: 'Icon append',
|
||||
appendIconName: 'search',
|
||||
variant: 'accent',
|
||||
};
|
165
libs/ui-toolkit/src/components/button/button.tsx
Normal file
165
libs/ui-toolkit/src/components/button/button.tsx
Normal file
@ -0,0 +1,165 @@
|
||||
import { AnchorHTMLAttributes, ButtonHTMLAttributes, forwardRef } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Icon, IconName } from '../icon';
|
||||
import {
|
||||
includesLeftPadding,
|
||||
includesRightPadding,
|
||||
includesBorderWidth,
|
||||
} from '../../utils/class-names';
|
||||
|
||||
interface CommonProps {
|
||||
children?: React.ReactNode;
|
||||
variant?: 'primary' | 'secondary' | 'accent' | 'inline';
|
||||
className?: string;
|
||||
prependIconName?: IconName;
|
||||
appendIconName?: IconName;
|
||||
}
|
||||
export interface ButtonProps
|
||||
extends ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
CommonProps {}
|
||||
|
||||
export interface AnchorButtonProps
|
||||
extends AnchorHTMLAttributes<HTMLAnchorElement>,
|
||||
CommonProps {}
|
||||
|
||||
const getClassName = (
|
||||
className: CommonProps['className'],
|
||||
variant: CommonProps['variant']
|
||||
) => {
|
||||
const paddingLeftProvided = includesLeftPadding(className);
|
||||
const paddingRightProvided = includesRightPadding(className);
|
||||
const borderWidthProvided = includesBorderWidth(className);
|
||||
return classNames(
|
||||
[
|
||||
'inline-flex',
|
||||
'items-center',
|
||||
'justify-center',
|
||||
'box-border',
|
||||
'h-28',
|
||||
'text-ui',
|
||||
'no-underline',
|
||||
'hover:underline',
|
||||
'disabled:no-underline',
|
||||
'transition-all',
|
||||
],
|
||||
{
|
||||
'pl-28': !(paddingLeftProvided || variant === 'inline'),
|
||||
'pr-28': !(paddingRightProvided || variant === 'inline'),
|
||||
|
||||
border: !borderWidthProvided,
|
||||
|
||||
'hover:border-black dark:hover:border-white': variant !== 'inline',
|
||||
'active:border-black dark:active:border-white': true,
|
||||
|
||||
'bg-black dark:bg-white': variant === 'primary',
|
||||
'border-black-60 dark:border-white-60':
|
||||
variant === 'primary' || variant === 'secondary',
|
||||
'text-white dark:text-black': variant === 'primary',
|
||||
'hover:bg-black-80 dark:hover:bg-white-80': variant === 'primary',
|
||||
'active:bg-white dark:active:bg-black':
|
||||
variant === 'primary' || variant === 'accent',
|
||||
'active:text-black dark:active:text-white':
|
||||
variant === 'primary' || variant === 'accent',
|
||||
|
||||
'bg-white dark:bg-black': variant === 'secondary',
|
||||
'text-black dark:text-white': variant === 'secondary',
|
||||
'hover:bg-black-25 dark:hover:bg-white-25': variant === 'secondary',
|
||||
'hover:text-black dark:hover:text-white':
|
||||
variant === 'secondary' || variant === 'accent',
|
||||
'active:bg-black dark:active:bg-white': variant === 'secondary',
|
||||
'active:text-white dark:active:text-black': variant === 'secondary',
|
||||
|
||||
uppercase: variant === 'accent',
|
||||
'bg-vega-yellow dark:bg-vega-yellow': variant === 'accent',
|
||||
'border-transparent dark:border-transparent':
|
||||
variant === 'accent' || variant === 'inline',
|
||||
'hover:bg-vega-yellow-dark dark:hover:bg-vega-yellow/30':
|
||||
variant === 'accent',
|
||||
'text-black dark:text-black': variant === 'accent',
|
||||
'hover:text-white dark:hover:text-white': variant === 'accent',
|
||||
|
||||
'pl-4': variant === 'inline' && !paddingLeftProvided,
|
||||
'pr-4': variant === 'inline' && !paddingRightProvided,
|
||||
'border-0': variant === 'inline' && !borderWidthProvided,
|
||||
underline: variant === 'inline',
|
||||
'hover:no-underline': variant === 'inline',
|
||||
'hover:border-transparent dark:hover:border-transparent':
|
||||
variant === 'inline',
|
||||
'active:border-transparent dark:active:border-transparent':
|
||||
variant === 'inline',
|
||||
'active:text-black dark:active:text-vega-yellow': variant === 'inline',
|
||||
'text-black-95 dark:text-white-95': variant === 'inline',
|
||||
'hover:text-black hover:dark:text-white': variant === 'inline',
|
||||
|
||||
'disabled:bg-black-10 dark:disabled:bg-white-10': variant !== 'inline',
|
||||
'disabled:text-black-60 dark:disabled:text-white-60':
|
||||
variant !== 'inline',
|
||||
'disabled:border-black-25 dark:disabled:border-white-25':
|
||||
variant !== 'inline',
|
||||
},
|
||||
className
|
||||
);
|
||||
};
|
||||
|
||||
const getContent = (
|
||||
children: React.ReactNode,
|
||||
prependIconName?: IconName,
|
||||
appendIconName?: IconName
|
||||
) => {
|
||||
const iconName = prependIconName || appendIconName;
|
||||
if (iconName === undefined) {
|
||||
return children;
|
||||
}
|
||||
const iconClassName = classNames(['fill-current'], {
|
||||
'mr-8': prependIconName,
|
||||
'ml-8': appendIconName,
|
||||
});
|
||||
const icon = <Icon name={iconName} className={iconClassName} size={16} />;
|
||||
return (
|
||||
<>
|
||||
{prependIconName && icon}
|
||||
{children}
|
||||
{appendIconName && icon}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
(
|
||||
{
|
||||
variant = 'primary',
|
||||
children,
|
||||
className,
|
||||
prependIconName,
|
||||
appendIconName,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
return (
|
||||
<button ref={ref} className={getClassName(className, variant)} {...props}>
|
||||
{getContent(children, prependIconName, appendIconName)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export const AnchorButton = forwardRef<HTMLAnchorElement, AnchorButtonProps>(
|
||||
(
|
||||
{
|
||||
variant = 'primary',
|
||||
children,
|
||||
className,
|
||||
prependIconName,
|
||||
appendIconName,
|
||||
...prosp
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
return (
|
||||
<a ref={ref} className={getClassName(className, variant)} {...prosp}>
|
||||
{getContent(children, prependIconName, appendIconName)}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
);
|
1
libs/ui-toolkit/src/components/button/index.ts
Normal file
1
libs/ui-toolkit/src/components/button/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './button';
|
@ -1,58 +0,0 @@
|
||||
@import '../../styles/colors';
|
||||
|
||||
.callout {
|
||||
display: flex;
|
||||
padding: 14px;
|
||||
border: 1px solid $white;
|
||||
box-shadow: 3px 3px 0px $white;
|
||||
margin: 12px 0;
|
||||
|
||||
p {
|
||||
margin: 0 0 10px 0;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// VARIATIONS
|
||||
&--error {
|
||||
box-shadow: 5px 5px 0px $vega-red3;
|
||||
}
|
||||
|
||||
&--success {
|
||||
box-shadow: 5px 5px 0px $vega-green3;
|
||||
}
|
||||
|
||||
&--warn {
|
||||
box-shadow: 5px 5px 0px $vega-orange3;
|
||||
}
|
||||
|
||||
&--action {
|
||||
box-shadow: 5px 5px 0px $vega-yellow3;
|
||||
}
|
||||
|
||||
&__content {
|
||||
width: 100%;
|
||||
|
||||
> :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__title,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
margin-right: 10px;
|
||||
color: $white;
|
||||
}
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
|
||||
import { Callout } from '.';
|
||||
import { Callout } from './callout';
|
||||
import { Button } from '../button';
|
||||
|
||||
export default {
|
||||
title: 'Callout',
|
||||
@ -9,37 +10,61 @@ export default {
|
||||
} as ComponentMeta<typeof Callout>;
|
||||
|
||||
const Template: ComponentStory<typeof Callout> = (args) => (
|
||||
<Callout {...args}>Content</Callout>
|
||||
<Callout {...args} />
|
||||
);
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
children: 'Content',
|
||||
};
|
||||
|
||||
export const Danger = Template.bind({});
|
||||
Danger.args = {
|
||||
intent: 'danger',
|
||||
children: 'Content',
|
||||
};
|
||||
|
||||
export const Warning = Template.bind({});
|
||||
Warning.args = {
|
||||
intent: 'warning',
|
||||
children: 'Content',
|
||||
};
|
||||
|
||||
export const Prompt = Template.bind({});
|
||||
Prompt.args = {
|
||||
intent: 'prompt',
|
||||
children: 'Content',
|
||||
};
|
||||
|
||||
export const Progress = Template.bind({});
|
||||
Progress.args = {
|
||||
intent: 'progress',
|
||||
children: 'Content',
|
||||
};
|
||||
|
||||
export const Success = Template.bind({});
|
||||
Success.args = {
|
||||
intent: 'success',
|
||||
children: 'Content',
|
||||
};
|
||||
|
||||
export const Help = Template.bind({});
|
||||
Help.args = {
|
||||
intent: 'help',
|
||||
children: 'Content',
|
||||
};
|
||||
|
||||
export const IconAndContent = Template.bind({});
|
||||
IconAndContent.args = {
|
||||
intent: 'help',
|
||||
title: 'This is what this thing does',
|
||||
iconName: 'endorsed',
|
||||
children: (
|
||||
<div className="flex flex-col">
|
||||
<div>With a longer explaination</div>
|
||||
<Button className="block mt-8" variant="secondary">
|
||||
Action
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
@ -8,8 +8,10 @@ test('It renders content within callout', () => {
|
||||
});
|
||||
|
||||
test('It renders title and icon', () => {
|
||||
render(<Callout icon={<div data-testid="icon" />} title="title" />);
|
||||
expect(screen.getByTestId('icon')).toBeInTheDocument();
|
||||
render(<Callout iconName="endorsed" title="title" />);
|
||||
expect(
|
||||
screen.getByTestId('callout').querySelector('svg')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('title')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@ -33,7 +35,7 @@ intents.map((intent) =>
|
||||
test(`Applies class for progress`, () => {
|
||||
render(<Callout intent="progress" />);
|
||||
expect(screen.getByTestId('callout')).toHaveClass(
|
||||
'shadow-intent-black',
|
||||
'dark:shadow-intent-progress'
|
||||
'shadow-black',
|
||||
'dark:shadow-white'
|
||||
);
|
||||
});
|
||||
|
@ -1,48 +1,55 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Icon, IconName } from '../icon';
|
||||
|
||||
export const Callout = ({
|
||||
children,
|
||||
title,
|
||||
intent = 'help',
|
||||
icon,
|
||||
headingLevel,
|
||||
}: {
|
||||
export interface CalloutProps {
|
||||
children?: React.ReactNode;
|
||||
title?: React.ReactElement | string;
|
||||
intent?: 'danger' | 'warning' | 'prompt' | 'progress' | 'success' | 'help';
|
||||
icon?: React.ReactNode;
|
||||
iconName?: IconName;
|
||||
headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
|
||||
}) => {
|
||||
}
|
||||
|
||||
export function Callout({
|
||||
children,
|
||||
title,
|
||||
intent = 'help',
|
||||
iconName,
|
||||
headingLevel,
|
||||
}: CalloutProps) {
|
||||
const className = classNames(
|
||||
'shadow-callout',
|
||||
'border',
|
||||
'border-black',
|
||||
'dark:border-white',
|
||||
'text-body-large',
|
||||
'dark:text-white',
|
||||
'p-8',
|
||||
{
|
||||
'shadow-intent-danger': intent === 'danger',
|
||||
'shadow-intent-warning': intent === 'warning',
|
||||
'shadow-intent-prompt': intent === 'prompt',
|
||||
'shadow-intent-black dark:shadow-intent-progress': intent === 'progress',
|
||||
'shadow-black dark:shadow-white': intent === 'progress',
|
||||
'shadow-intent-success': intent === 'success',
|
||||
'shadow-intent-help': intent === 'help',
|
||||
flex: icon,
|
||||
flex: !!iconName,
|
||||
}
|
||||
);
|
||||
const TitleTag: keyof JSX.IntrinsicElements = headingLevel
|
||||
? `h${headingLevel}`
|
||||
: 'div';
|
||||
const icon = iconName && (
|
||||
<Icon name={iconName} className="fill-current ml-8 mr-16 mt-8" size={20} />
|
||||
);
|
||||
const body = (
|
||||
<div className="body-large dark:text-white">
|
||||
<>
|
||||
{title && <TitleTag className="text-h5">{title}</TitleTag>}
|
||||
{children}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<div data-testid="callout" className={className}>
|
||||
{icon && <div className="">{icon}</div>}
|
||||
{icon}
|
||||
{icon ? <div className="grow">{body}</div> : body}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import * as React from 'react';
|
||||
import { EtherscanLink } from '.';
|
||||
import { EthereumChainIds } from '../../utils/web3';
|
||||
|
||||
|
10
libs/ui-toolkit/src/components/icon/icon.spec.tsx
Normal file
10
libs/ui-toolkit/src/components/icon/icon.spec.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import { Icon } from './icon';
|
||||
|
||||
describe('Icon', () => {
|
||||
it('should render successfully', () => {
|
||||
const { baseElement } = render(<Icon name="add" />);
|
||||
expect(baseElement).toBeTruthy();
|
||||
});
|
||||
});
|
12
libs/ui-toolkit/src/components/icon/icon.stories.tsx
Normal file
12
libs/ui-toolkit/src/components/icon/icon.stories.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import { Story, Meta } from '@storybook/react';
|
||||
import { Icon } from './icon';
|
||||
|
||||
export default {
|
||||
component: Icon,
|
||||
title: 'Icon',
|
||||
} as Meta;
|
||||
|
||||
const Template: Story = (args) => <Icon {...args} name="warning-sign" />;
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {};
|
30
libs/ui-toolkit/src/components/icon/icon.tsx
Normal file
30
libs/ui-toolkit/src/components/icon/icon.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import { IconSvgPaths20, IconSvgPaths16, IconName } from '@blueprintjs/icons';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export type { IconName } from '@blueprintjs/icons';
|
||||
|
||||
interface IconProps {
|
||||
name: IconName;
|
||||
className?: string;
|
||||
size?: 16 | 20 | 24 | 32 | 48 | 64;
|
||||
}
|
||||
|
||||
export const Icon = ({ size = 16, name, className }: IconProps) => {
|
||||
const effectiveClassName = classNames(
|
||||
{
|
||||
'w-20': size === 20,
|
||||
'h-20': size === 20,
|
||||
'w-16': size === 16,
|
||||
'h-16': size === 16,
|
||||
},
|
||||
className
|
||||
);
|
||||
const viewbox = size <= 16 ? '0 0 16 16' : '0 0 20 20';
|
||||
return (
|
||||
<svg className={effectiveClassName} viewBox={viewbox}>
|
||||
{(size <= 16 ? IconSvgPaths16 : IconSvgPaths20)[name].map((d, key) => (
|
||||
<path fillRule="evenodd" clipRule="evenodd" d={d} key={key} />
|
||||
))}
|
||||
</svg>
|
||||
);
|
||||
};
|
1
libs/ui-toolkit/src/components/icon/index.ts
Normal file
1
libs/ui-toolkit/src/components/icon/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './icon';
|
1
libs/ui-toolkit/src/components/input-error/index.ts
Normal file
1
libs/ui-toolkit/src/components/input-error/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './input-error';
|
@ -0,0 +1,10 @@
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import { InputError } from './input-error';
|
||||
|
||||
describe('InputError', () => {
|
||||
it('should render successfully', () => {
|
||||
const { baseElement } = render(<InputError />);
|
||||
expect(baseElement).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,20 @@
|
||||
import { Story, Meta } from '@storybook/react';
|
||||
import { InputError } from './input-error';
|
||||
|
||||
export default {
|
||||
component: InputError,
|
||||
title: 'InputError',
|
||||
} as Meta;
|
||||
|
||||
const Template: Story = (args) => <InputError {...args} />;
|
||||
|
||||
export const Danger = Template.bind({});
|
||||
Danger.args = {
|
||||
children: 'An error that might have happened',
|
||||
};
|
||||
|
||||
export const Warning = Template.bind({});
|
||||
Warning.args = {
|
||||
intent: 'warning',
|
||||
children: 'Something that might be an issue',
|
||||
};
|
41
libs/ui-toolkit/src/components/input-error/input-error.tsx
Normal file
41
libs/ui-toolkit/src/components/input-error/input-error.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
import classNames from 'classnames';
|
||||
import { Icon } from '../icon';
|
||||
|
||||
interface InputErrorProps {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
intent?: 'danger' | 'warning';
|
||||
}
|
||||
|
||||
export const InputError = ({
|
||||
intent = 'danger',
|
||||
className,
|
||||
children,
|
||||
}: InputErrorProps) => {
|
||||
const effectiveClassName = classNames(
|
||||
[
|
||||
'inline-flex',
|
||||
'items-center',
|
||||
'box-border',
|
||||
'h-28',
|
||||
'border-l-4',
|
||||
'text-black-95 dark:text-white-95',
|
||||
'text-ui',
|
||||
],
|
||||
{
|
||||
'border-intent-danger': intent === 'danger',
|
||||
'border-intent-warning': intent === 'warning',
|
||||
},
|
||||
className
|
||||
);
|
||||
const iconClassName = classNames(['mx-8'], {
|
||||
'fill-intent-danger': intent === 'danger',
|
||||
'fill-intent-warning': intent === 'warning',
|
||||
});
|
||||
return (
|
||||
<div className={effectiveClassName}>
|
||||
<Icon name="warning-sign" className={iconClassName} />
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
1
libs/ui-toolkit/src/components/input/index.ts
Normal file
1
libs/ui-toolkit/src/components/input/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './input';
|
10
libs/ui-toolkit/src/components/input/input.spec.tsx
Normal file
10
libs/ui-toolkit/src/components/input/input.spec.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import { Input } from './input';
|
||||
|
||||
describe('Input', () => {
|
||||
it('should render successfully', () => {
|
||||
const { baseElement } = render(<Input />);
|
||||
expect(baseElement).toBeTruthy();
|
||||
});
|
||||
});
|
29
libs/ui-toolkit/src/components/input/input.stories.tsx
Normal file
29
libs/ui-toolkit/src/components/input/input.stories.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import { Story, Meta } from '@storybook/react';
|
||||
import { Input } from './input';
|
||||
export default {
|
||||
component: Input,
|
||||
title: 'Input',
|
||||
} as Meta;
|
||||
|
||||
const Template: Story = (args) => <Input {...args} value="I type words" />;
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {};
|
||||
|
||||
export const WithError = Template.bind({});
|
||||
WithError.args = {
|
||||
hasError: true,
|
||||
};
|
||||
|
||||
export const Disabled = Template.bind({});
|
||||
Disabled.args = {
|
||||
disabled: true,
|
||||
};
|
||||
|
||||
export const IconPrepend: Story = () => (
|
||||
<Input value="I type words" prependIconName="search" />
|
||||
);
|
||||
|
||||
export const IconAppend: Story = () => (
|
||||
<Input value="I type words and even more words" appendIconName="search" />
|
||||
);
|
98
libs/ui-toolkit/src/components/input/input.tsx
Normal file
98
libs/ui-toolkit/src/components/input/input.tsx
Normal file
@ -0,0 +1,98 @@
|
||||
import { InputHTMLAttributes, forwardRef } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Icon, IconName } from '../icon';
|
||||
import {
|
||||
includesLeftPadding,
|
||||
includesRightPadding,
|
||||
} from '../../utils/class-names';
|
||||
|
||||
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
|
||||
hasError?: boolean;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
prependIconName?: IconName;
|
||||
appendIconName?: IconName;
|
||||
}
|
||||
export const inputClassNames = ({
|
||||
hasError,
|
||||
className,
|
||||
}: {
|
||||
hasError?: boolean;
|
||||
className?: string;
|
||||
}) => {
|
||||
return classNames(
|
||||
[
|
||||
'inline-flex',
|
||||
'items-center',
|
||||
'box-border',
|
||||
'border',
|
||||
'bg-clip-padding',
|
||||
'border-black-60 dark:border-white-60',
|
||||
'bg-black-25 dark:bg-white-25',
|
||||
'text-black-60 dark:text-white-60',
|
||||
'text-ui',
|
||||
'focus-visible:shadow-focus dark:focus-visible:shadow-focus-dark',
|
||||
'focus-visible:outline-0',
|
||||
'disabled:bg-black-10 disabled:dark:bg-white-10',
|
||||
],
|
||||
{
|
||||
'pl-8': !includesLeftPadding(className),
|
||||
'pr-8': !includesRightPadding(className),
|
||||
'border-vega-pink dark:border-vega-pink': hasError,
|
||||
},
|
||||
className
|
||||
);
|
||||
};
|
||||
|
||||
export const inputStyle = ({
|
||||
style,
|
||||
disabled,
|
||||
}: {
|
||||
style?: React.CSSProperties;
|
||||
disabled?: boolean;
|
||||
}) =>
|
||||
disabled
|
||||
? {
|
||||
...style,
|
||||
backgroundImage:
|
||||
'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAAXNSR0IArs4c6QAAACNJREFUGFdjtLS0/M8ABcePH2eEsRlJl4BpBdHIuuFmEi0BABqjEQVjx/LTAAAAAElFTkSuQmCC)',
|
||||
}
|
||||
: style;
|
||||
|
||||
export const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
({ prependIconName, appendIconName, className, ...props }, ref) => {
|
||||
className = `${className} h-28`;
|
||||
if (prependIconName) {
|
||||
className += ' pl-28';
|
||||
}
|
||||
if (appendIconName) {
|
||||
className += ' pr-28';
|
||||
}
|
||||
const input = (
|
||||
<input
|
||||
{...props}
|
||||
ref={ref}
|
||||
className={classNames(inputClassNames({ className, ...props }))}
|
||||
/>
|
||||
);
|
||||
const iconName = prependIconName || appendIconName;
|
||||
if (iconName !== undefined) {
|
||||
const iconClassName = classNames(
|
||||
['fill-black-60 dark:fill-white-60', 'absolute', 'z-10'],
|
||||
{
|
||||
'left-8': prependIconName,
|
||||
'right-8': appendIconName,
|
||||
}
|
||||
);
|
||||
const icon = <Icon name={iconName} className={iconClassName} size={16} />;
|
||||
return (
|
||||
<div className="inline-flex items-center relative">
|
||||
{prependIconName && icon}
|
||||
{input}
|
||||
{appendIconName && icon}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return input;
|
||||
}
|
||||
);
|
1
libs/ui-toolkit/src/components/select/index.ts
Normal file
1
libs/ui-toolkit/src/components/select/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './select';
|
10
libs/ui-toolkit/src/components/select/select.spec.tsx
Normal file
10
libs/ui-toolkit/src/components/select/select.spec.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import { Select } from './select';
|
||||
|
||||
describe('Select', () => {
|
||||
it('should render successfully', () => {
|
||||
const { baseElement } = render(<Select />);
|
||||
expect(baseElement).toBeTruthy();
|
||||
});
|
||||
});
|
26
libs/ui-toolkit/src/components/select/select.stories.tsx
Normal file
26
libs/ui-toolkit/src/components/select/select.stories.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import { Story, Meta } from '@storybook/react';
|
||||
import { Select } from './select';
|
||||
|
||||
export default {
|
||||
component: Select,
|
||||
title: 'Select',
|
||||
} as Meta;
|
||||
|
||||
const Template: Story = (args) => (
|
||||
<Select {...args}>
|
||||
<option value="Only option">Only option</option>
|
||||
</Select>
|
||||
);
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {};
|
||||
|
||||
export const WithError = Template.bind({});
|
||||
WithError.args = {
|
||||
hasError: true,
|
||||
};
|
||||
|
||||
export const Disabled = Template.bind({});
|
||||
Disabled.args = {
|
||||
disabled: true,
|
||||
};
|
20
libs/ui-toolkit/src/components/select/select.tsx
Normal file
20
libs/ui-toolkit/src/components/select/select.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { SelectHTMLAttributes, forwardRef } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { inputClassNames } from '../input/input';
|
||||
|
||||
export interface SelectProps extends SelectHTMLAttributes<HTMLSelectElement> {
|
||||
hasError?: boolean;
|
||||
className?: string;
|
||||
value?: string | number;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const Select = forwardRef<HTMLSelectElement, SelectProps>(
|
||||
(props, ref) => (
|
||||
<select
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={classNames(inputClassNames(props), 'h-28')}
|
||||
/>
|
||||
)
|
||||
);
|
1
libs/ui-toolkit/src/components/text-area/index.ts
Normal file
1
libs/ui-toolkit/src/components/text-area/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './text-area';
|
10
libs/ui-toolkit/src/components/text-area/text-area.spec.tsx
Normal file
10
libs/ui-toolkit/src/components/text-area/text-area.spec.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import { TextArea } from './text-area';
|
||||
|
||||
describe('TextArea', () => {
|
||||
it('should render successfully', () => {
|
||||
const { baseElement } = render(<TextArea />);
|
||||
expect(baseElement).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,26 @@
|
||||
import { Story, Meta } from '@storybook/react';
|
||||
import { TextArea } from './text-area';
|
||||
|
||||
export default {
|
||||
component: TextArea,
|
||||
title: 'TextArea',
|
||||
} as Meta;
|
||||
|
||||
const Template: Story = (args) => (
|
||||
<TextArea {...args} className="h-48">
|
||||
I type words
|
||||
</TextArea>
|
||||
);
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {};
|
||||
|
||||
export const WithError = Template.bind({});
|
||||
WithError.args = {
|
||||
hasError: true,
|
||||
};
|
||||
|
||||
export const Disabled = Template.bind({});
|
||||
Disabled.args = {
|
||||
disabled: true,
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user