diff --git a/package.json b/package.json index 88420c0..c200637 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "@cosmjs/encoding": "^0.31.0", "@cosmjs/proto-signing": "^0.31.0", "@cosmjs/stargate": "^0.31.0", - "@dydxprotocol/abacus": "^0.2.28", + "@dydxprotocol/abacus": "^0.2.37", "@cosmjs/tendermint-rpc": "^0.31.0", "@dydxprotocol/v4-client": "^0.29.0", "@ethersproject/providers": "^5.7.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 83de3b2..a7f94ab 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,9 +1,5 @@ lockfileVersion: '6.0' -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - dependencies: '@0xsquid/sdk': specifier: ^1.7.2 @@ -27,8 +23,8 @@ dependencies: specifier: ^0.31.0 version: 0.31.0 '@dydxprotocol/abacus': - specifier: ^0.2.28 - version: 0.2.28 + specifier: ^0.2.37 + version: 0.2.37 '@dydxprotocol/v4-client': specifier: ^0.29.0 version: 0.29.0 @@ -1143,8 +1139,8 @@ packages: resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==} dev: true - /@dydxprotocol/abacus@0.2.28: - resolution: {integrity: sha512-mGmhwQM8aiV/cD+d3lvW76ysoPN7uVsDyAiVcRdzGqgj+gbM0IIJDYXG14xHruOTi+gK1Aga5MUz2pdvmPDhYA==} + /@dydxprotocol/abacus@0.2.37: + resolution: {integrity: sha512-amoq9aVXo+lVTQGu2jsyy5/0bniOMEXpyJDmaAr2TpbrtQtw2WgyAP+se9Q5wt3yh0Mzdtsh7P2h090kH7/bZQ==} dev: false /@dydxprotocol/dydxjs@0.3.0: @@ -8277,7 +8273,7 @@ packages: resolution: {integrity: sha512-WIdaQ8uW1vIbYvNnAVunkC6yxTrneJC7VQ5UUQ0kuw8b0C0A39KTIpoQHCfc8tV7o9vF4niwRhdXEdfAgQEsQQ==} dependencies: cosmos-directory-types: 0.0.6 - node-fetch-native: 1.2.0 + node-fetch-native: 1.4.0 dev: false /cosmos-directory-types@0.0.6: @@ -12462,8 +12458,8 @@ packages: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} - /node-fetch-native@1.2.0: - resolution: {integrity: sha512-5IAMBTl9p6PaAjYCnMv5FmqIF6GcZnawAVnzaCG0rX2aYZJ4CxEkZNtVPuTRug7fL7wyM5BQYTlAzcyMPi6oTQ==} + /node-fetch-native@1.4.0: + resolution: {integrity: sha512-F5kfEj95kX8tkDhUCYdV8dg3/8Olx/94zB8+ZNthFs6Bz31UpUi8Xh40TN3thLwXgrwXry1pEg9lJ++tLWTcqA==} dev: false /node-fetch@2.6.12: @@ -15855,3 +15851,7 @@ packages: release-it: 15.11.0 semver: 7.5.1 dev: false + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false diff --git a/src/components/CopyButton.stories.tsx b/src/components/CopyButton.stories.tsx new file mode 100644 index 0000000..71f4070 --- /dev/null +++ b/src/components/CopyButton.stories.tsx @@ -0,0 +1,28 @@ +import type { Story } from '@ladle/react'; + +import { CopyButton, type CopyButtonProps } from '@/components/CopyButton'; + +import { StoryWrapper } from '.ladle/components'; + +export const CopyButtonStory: Story = (args) => ( + + + +); + +CopyButtonStory.args = { + value: 'some text to copy', +}; + +CopyButtonStory.argTypes = { + shownAsText: { + options: [true, false], + control: { type: 'select' }, + defaultValue: false, + }, + children: { + options: ['some text to copy'], + control: { type: 'select' }, + defaultValue: undefined, + } +}; diff --git a/src/components/CopyButton.tsx b/src/components/CopyButton.tsx new file mode 100644 index 0000000..5da154e --- /dev/null +++ b/src/components/CopyButton.tsx @@ -0,0 +1,65 @@ +import { useState } from 'react'; +import styled, { css, type AnyStyledComponent } from 'styled-components'; + +import { ButtonAction } from '@/constants/buttons'; +import { STRING_KEYS } from '@/constants/localization'; + +import { useStringGetter } from '@/hooks'; +import { layoutMixins } from '@/styles/layoutMixins'; + +import { Button, ButtonProps } from './Button'; +import { Icon, IconName } from './Icon'; + +export type CopyButtonProps = { + value?: string; + shownAsText?: boolean; + children?: React.ReactNode; +} & ButtonProps; + +export const CopyButton = ({ value, shownAsText, children, ...buttonProps }: CopyButtonProps) => { + const stringGetter = useStringGetter(); + const [copied, setCopied] = useState(false); + + const onCopy = () => { + if (!value) return; + + setCopied(true); + navigator.clipboard.writeText(value); + setTimeout(() => setCopied(false), 500); + }; + + return shownAsText ? ( + + {children} + + + ) : ( + + ); +}; + +const Styled: Record = {}; + +Styled.InlineRow = styled.div<{ copied: boolean }>` + ${layoutMixins.inlineRow} + cursor: pointer; + + ${({ copied }) => + copied + ? css` + filter: brightness(0.8); + ` + : css` + &:hover { + filter: brightness(1.1); + text-decoration: underline; + } + `} +`; diff --git a/src/constants/abacus.ts b/src/constants/abacus.ts index 55c0ad7..551274c 100644 --- a/src/constants/abacus.ts +++ b/src/constants/abacus.ts @@ -112,6 +112,8 @@ export type SubaccountFill = Abacus.exchange.dydx.abacus.output.SubaccountFill; export type SubaccountFundingPayment = Abacus.exchange.dydx.abacus.output.SubaccountFundingPayment; export type SubaccountFundingPayments = Abacus.exchange.dydx.abacus.output.SubaccountFundingPayment[]; +export type SubaccountTransfer = Abacus.exchange.dydx.abacus.output.SubaccountTransfer; +export type SubaccountTransfers = Abacus.exchange.dydx.abacus.output.SubaccountTransfer[]; // ------ Historical PnL ------ // export type SubAccountHistoricalPNL = Abacus.exchange.dydx.abacus.output.SubaccountHistoricalPNL; diff --git a/src/constants/localization/app.ts b/src/constants/localization/app.ts index 93914c9..a392340 100644 --- a/src/constants/localization/app.ts +++ b/src/constants/localization/app.ts @@ -177,6 +177,7 @@ export const APP_STRING_KEYS = { RECEIVE: 'GENERAL.RECEIVE', RECENT: 'GENERAL.RECENT', RECENT_TRADES_SHORT: 'GENERAL.RECENT_TRADES_SHORT', + RECIPIENT: 'GENERAL.RECIPIENT', REFERRAL_CODE: 'GENERAL.REFERRAL_CODE', REFERRALS: 'GENERAL.REFERRALS', REFERRER_PERCENT_OFF: 'GENERAL.REFERRER_PERCENT_OFF', @@ -188,6 +189,7 @@ export const APP_STRING_KEYS = { SELECT_NETWORK: 'GENERAL.SELECT_NETWORK', SELL: 'GENERAL.SELL', SEND: 'GENERAL.SEND', + SENDER: 'GENERAL.SENDER', SHARE: 'GENERAL.SHARE', SHORT_POSITION_SHORT: 'GENERAL.SHORT_POSITION_SHORT', SIDE: 'GENERAL.SIDE', diff --git a/src/lib/abacus/stateNotification.ts b/src/lib/abacus/stateNotification.ts index ef08749..fbadc75 100644 --- a/src/lib/abacus/stateNotification.ts +++ b/src/lib/abacus/stateNotification.ts @@ -19,6 +19,7 @@ import { setFundingPayments, setHistoricalPnl, setSubaccount, + setTransfers, setWallet, } from '@/state/account'; @@ -102,6 +103,11 @@ class AbacusStateNotifier implements AbacusStateNotificationProtocol { dispatch(setFundingPayments(fundingPayments)); } + if (changes.has(Changes.transfers)) { + const transfers = updatedState.subaccountTransfers(subaccountId)?.toArray() || []; + dispatch(setTransfers(transfers)); + } + if (changes.has(Changes.historicalPnl)) { const historicalPnl = updatedState.subaccountHistoricalPnl(subaccountId)?.toArray() || []; diff --git a/src/localization/en/app.json b/src/localization/en/app.json index 41a0027..a492d6b 100644 --- a/src/localization/en/app.json +++ b/src/localization/en/app.json @@ -182,6 +182,7 @@ "RECEIVE": "Receive", "RECENT": "Recent", "RECENT_TRADES_SHORT": "Trades", + "RECIPIENT": "Recipient", "REFERRAL_CODE": "Referral Code", "REFERRALS": "Referrals", "REFERRER_PERCENT_OFF": "{DISCOUNT}% off", @@ -193,6 +194,7 @@ "SELECT_NETWORK": "Select Network", "SELL": "Sell", "SEND": "Send", + "SENDER": "Sender", "SHARE": "Share", "SHORT_POSITION_SHORT": "Short", "SIDE": "Side", diff --git a/src/pages/portfolio/History.tsx b/src/pages/portfolio/History.tsx index 72fffa2..78f85aa 100644 --- a/src/pages/portfolio/History.tsx +++ b/src/pages/portfolio/History.tsx @@ -28,13 +28,14 @@ export const History = () => { label:

{stringGetter({ key: STRING_KEYS.TRADES })}

, href: HistoryRoute.Trades, }, + { + value: HistoryRoute.Transfers, + label:

{stringGetter({ key: STRING_KEYS.TRANSFERS })}

, + href: HistoryRoute.Transfers, + tag: 'USDC', + }, // TODO - TRCL-1693 - // { - // value: HistoryRoute.Transfers, - // label:

{stringGetter({ key: STRING_KEYS.TRANSFERS })}

, - // href: HistoryRoute.Transfers, - // }, - // { // value: HistoryRoute.Payments, // label:

{stringGetter({ key: STRING_KEYS.PAYMENTS })}

, // href: HistoryRoute.Payments, diff --git a/src/pages/portfolio/Portfolio.tsx b/src/pages/portfolio/Portfolio.tsx index 227af41..d8969b8 100644 --- a/src/pages/portfolio/Portfolio.tsx +++ b/src/pages/portfolio/Portfolio.tsx @@ -9,6 +9,7 @@ import { useBreakpoints, useDocumentTitle, useStringGetter } from '@/hooks'; import { FillsTable, FillsTableColumnKey } from '@/views/tables/FillsTable'; import { FundingPaymentsTable } from '@/views/tables/FundingPaymentsTable'; +import { TransferHistoryTable } from '@/views/tables/TransferHistoryTable'; import { Icon, IconName } from '@/components/Icon'; import { NavigationMenu } from '@/components/NavigationMenu'; import { WithSidebar } from '@/components/WithSidebar'; @@ -61,7 +62,10 @@ export default () => { /> } /> - } /> + } + /> } @@ -118,8 +122,6 @@ export default () => { }, ], }, - // TODO(aforaleka) Add back subitems when there are clearer designs - // or when transfers and payments are ready ]} /> ) diff --git a/src/pages/portfolio/PortfolioNavMobile.tsx b/src/pages/portfolio/PortfolioNavMobile.tsx index ebfaf5a..0ce1a65 100644 --- a/src/pages/portfolio/PortfolioNavMobile.tsx +++ b/src/pages/portfolio/PortfolioNavMobile.tsx @@ -40,12 +40,12 @@ export const PortfolioNavMobile = () => { label: stringGetter({ key: STRING_KEYS.TRADES }), description: stringGetter({ key: STRING_KEYS.TRADES_DESCRIPTION }), }, - // TODO: TRCL-1693 - re-enable when Payments and Transfers are ready - // { - // value: `${AppRoute.Portfolio}/${PortfolioRoute.History}/${HistoryRoute.Transfers}`, - // label: stringGetter({ key: STRING_KEYS.TRANSFERS }), - // description: stringGetter({ key: STRING_KEYS.TRANSFERS_DESCRIPTION }), - // }, + { + value: `${AppRoute.Portfolio}/${PortfolioRoute.History}/${HistoryRoute.Transfers}`, + label: stringGetter({ key: STRING_KEYS.TRANSFERS }), + description: stringGetter({ key: STRING_KEYS.TRANSFERS_DESCRIPTION }), + }, + // TODO: TRCL-1693 - re-enable when Payments are ready // { // value: `${AppRoute.Portfolio}/${PortfolioRoute.History}/${HistoryRoute.Payments}`, // label: stringGetter({ key: STRING_KEYS.PAYMENTS }), diff --git a/src/state/account.ts b/src/state/account.ts index b722363..5fab6e3 100644 --- a/src/state/account.ts +++ b/src/state/account.ts @@ -9,6 +9,7 @@ import type { SubaccountFundingPayments, Wallet, SubaccountOrder, + SubaccountTransfers, HistoricalPnlPeriods, SubAccountHistoricalPNLs, } from '@/constants/abacus'; @@ -22,6 +23,7 @@ import { getLocalStorage } from '@/lib/localStorage'; export type AccountState = { fills?: SubaccountFills; fundingPayments?: SubaccountFundingPayments; + transfers?: SubaccountTransfers; clearedOrderIds?: string[]; uncommittedOrderClientIds?: number[]; hasUnseenFillUpdates: boolean; @@ -38,6 +40,7 @@ export type AccountState = { const initialState: AccountState = { fills: undefined, fundingPayments: undefined, + transfers: undefined, clearedOrderIds: undefined, uncommittedOrderClientIds: undefined, hasUnseenFillUpdates: false, @@ -81,6 +84,9 @@ export const accountSlice = createSlice({ setFundingPayments: (state, action: PayloadAction) => { state.fundingPayments = action.payload; }, + setTransfers: (state, action: PayloadAction) => { + state.transfers = action.payload; + }, clearOrder: (state, action: PayloadAction) => ({ ...state, clearedOrderIds: [...(state.clearedOrderIds || []), action.payload], @@ -148,6 +154,7 @@ export const accountSlice = createSlice({ export const { setFills, setFundingPayments, + setTransfers, clearOrder, setOnboardingGuard, setOnboardingState, diff --git a/src/state/accountSelectors.ts b/src/state/accountSelectors.ts index c16f039..29aa72e 100644 --- a/src/state/accountSelectors.ts +++ b/src/state/accountSelectors.ts @@ -193,6 +193,12 @@ export const getCurrentMarketFills = createSelector( !currentMarketId ? [] : marketFills[currentMarketId] ); +/** + * @param state + * @returns list of transfers for the currently connected subaccount + */ +export const getSubaccountTransfers = (state: RootState) => state.account?.transfers; + /** * @param state * @returns list of funding payments for the currently connected subaccount diff --git a/src/styles/tradeViewMixins.ts b/src/styles/tradeViewMixins.ts index fa64498..a847b81 100644 --- a/src/styles/tradeViewMixins.ts +++ b/src/styles/tradeViewMixins.ts @@ -18,5 +18,9 @@ export const tradeViewMixins: Record< tbody { font: var(--font-small-book); } + + thead tr { + box-shadow: none; + } `, }; diff --git a/src/views/dialogs/MnemonicExportDialog.tsx b/src/views/dialogs/MnemonicExportDialog.tsx index b654170..a53c7e0 100644 --- a/src/views/dialogs/MnemonicExportDialog.tsx +++ b/src/views/dialogs/MnemonicExportDialog.tsx @@ -9,7 +9,7 @@ import { breakpoints } from '@/styles'; import { layoutMixins } from '@/styles/layoutMixins'; import { AlertMessage } from '@/components/AlertMessage'; -import { Button } from '@/components/Button'; +import { CopyButton } from '@/components/CopyButton'; import { Dialog } from '@/components/Dialog'; import { Checkbox } from '@/components/Checkbox'; import { Icon, IconName } from '@/components/Icon'; @@ -30,21 +30,12 @@ export const MnemonicExportDialog = ({ setIsOpen }: ElementProps) => { const [hasAcknowledged, setHasAcknowledged] = useState(false); const [currentStep, setCurrentStep] = useState(MnemonicExportStep.AcknowledgeRisk); const [isShowing, setIsShowing] = useState(false); - const [copied, setCopied] = useState(false); const stringGetter = useStringGetter(); const { hdKey } = useAccounts(); const { mnemonic } = hdKey ?? {}; - const onCopy = () => { - setCopied(true); - if (mnemonic) { - navigator.clipboard.writeText(mnemonic); - } - setTimeout(() => setCopied(false), 500); - }; - const title = { [MnemonicExportStep.AcknowledgeRisk]: stringGetter({ key: STRING_KEYS.REVEAL_SECRET_PHRASE }), [MnemonicExportStep.DisplayMnemonic]: stringGetter({ key: STRING_KEYS.EXPORT_SECRET_PHRASE }), @@ -116,10 +107,7 @@ export const MnemonicExportDialog = ({ setIsOpen }: ElementProps) => { } > - + ), diff --git a/src/views/dialogs/ReceiveDialog.tsx b/src/views/dialogs/ReceiveDialog.tsx index 0f8db5e..b9546fa 100644 --- a/src/views/dialogs/ReceiveDialog.tsx +++ b/src/views/dialogs/ReceiveDialog.tsx @@ -1,7 +1,6 @@ import { useState } from 'react'; import styled, { type AnyStyledComponent } from 'styled-components'; -import { ButtonAction } from '@/constants/buttons'; import { STRING_KEYS } from '@/constants/localization'; import { DydxChainAsset } from '@/constants/wallets'; @@ -10,9 +9,8 @@ import { layoutMixins } from '@/styles/layoutMixins'; import { useAccounts, useStringGetter } from '@/hooks'; import { AssetIcon } from '@/components/AssetIcon'; -import { Button } from '@/components/Button'; +import { CopyButton } from '@/components/CopyButton'; import { Dialog } from '@/components/Dialog'; -import { Icon, IconName } from '@/components/Icon'; import { QrCode } from '@/components/QrCode'; import { SelectItem, SelectMenu } from '@/components/SelectMenu'; import { WithDetailsReceipt } from '@/components/WithDetailsReceipt'; @@ -31,14 +29,6 @@ export const ReceiveDialog = ({ selectedAsset = DydxChainAsset.DYDX, setIsOpen } const { dydxAddress } = useAccounts(); const [asset, setAsset] = useState(selectedAsset); - const [copied, setCopied] = useState(false); - - const onCopy = () => { - if (!dydxAddress) return; - setCopied(true); - navigator.clipboard.writeText(dydxAddress); - setTimeout(() => setCopied(false), 500); - }; const assetOptions = [ { @@ -89,10 +79,7 @@ export const ReceiveDialog = ({ selectedAsset = DydxChainAsset.DYDX, setIsOpen } > - + )} diff --git a/src/views/tables/TransferHistoryTable.tsx b/src/views/tables/TransferHistoryTable.tsx new file mode 100644 index 0000000..cf7c9c3 --- /dev/null +++ b/src/views/tables/TransferHistoryTable.tsx @@ -0,0 +1,195 @@ +import styled, { type AnyStyledComponent, css } from 'styled-components'; +import { shallowEqual, useDispatch, useSelector } from 'react-redux'; +import type { ColumnSize } from '@react-types/table'; + +import { type SubaccountTransfer } from '@/constants/abacus'; +import { ButtonAction } from '@/constants/buttons'; +import { DialogTypes } from '@/constants/dialogs'; +import { STRING_KEYS, StringGetterFunction } from '@/constants/localization'; + +import { useBreakpoints, useStringGetter } from '@/hooks'; + +import { layoutMixins } from '@/styles/layoutMixins'; +import { tradeViewMixins } from '@/styles/tradeViewMixins'; + +import { Button } from '@/components/Button'; +import { CopyButton } from '@/components/CopyButton'; +import { Icon } from '@/components/Icon'; +import { Link } from '@/components/Link'; +import { Output, OutputType } from '@/components/Output'; +import { Table, TableCell, TableColumnHeader, type ColumnDef } from '@/components/Table'; +import { OnboardingTriggerButton } from '@/views/dialogs/OnboardingTriggerButton'; + +import { getSubaccountTransfers } from '@/state/accountSelectors'; +import { calculateCanAccountTrade } from '@/state/accountCalculators'; +import { openDialog } from '@/state/dialogs'; + +import { truncateAddress } from '@/lib/wallet'; + +const MOBILE_TRANSFERS_PER_PAGE = 50; + +export enum TransferHistoryTableColumnKey { + Time = 'Time', + Action = 'Action', + SenderRecipient = 'Sender-Recipient', + Amount = 'Amount', + TxHash = 'TxHash', +} + +const getTransferHistoryTableColumnDef = ({ + key, + stringGetter, + width, +}: { + key: TransferHistoryTableColumnKey; + isTablet?: boolean; + stringGetter: StringGetterFunction; + width?: ColumnSize; +}): ColumnDef => ({ + width, + ...( + { + [TransferHistoryTableColumnKey.Time]: { + columnKey: TransferHistoryTableColumnKey.Time, + getCellValue: (row) => row.updatedAtMilliseconds, + label: stringGetter({ key: STRING_KEYS.TIME }), + renderCell: ({ updatedAtMilliseconds }) => ( + + ), + }, + [TransferHistoryTableColumnKey.Action]: { + columnKey: TransferHistoryTableColumnKey.Action, + getCellValue: (row) => row.resources.typeStringKey, + label: stringGetter({ key: STRING_KEYS.ACTION }), + renderCell: ({ resources }) => + resources.typeStringKey && stringGetter({ key: resources.typeStringKey }), + }, + [TransferHistoryTableColumnKey.SenderRecipient]: { + columnKey: TransferHistoryTableColumnKey.SenderRecipient, + getCellValue: (row) => `${row.fromAddress}-${row.toAddress}`, + label: ( + + {stringGetter({ key: STRING_KEYS.SENDER })} + {stringGetter({ key: STRING_KEYS.RECIPIENT })} + + ), + renderCell: ({ fromAddress, toAddress }) => ( + + + {fromAddress ? truncateAddress(fromAddress) : '-'} + {' '} + + {toAddress ? truncateAddress(toAddress) : '-'} + + + ), + }, + [TransferHistoryTableColumnKey.Amount]: { + columnKey: TransferHistoryTableColumnKey.Amount, + getCellValue: (row) => row.amount, + label: stringGetter({ key: STRING_KEYS.AMOUNT }), + renderCell: ({ amount }) => , + }, + [TransferHistoryTableColumnKey.TxHash]: { + columnKey: TransferHistoryTableColumnKey.TxHash, + getCellValue: (row) => row.transactionHash, + label: stringGetter({ key: STRING_KEYS.TRANSACTION }), + renderCell: ({ transactionHash, resources }) => + transactionHash ? ( + + {truncateAddress(transactionHash, '')} + + ) : ( + '-' + ), + }, + } as Record> + )[key], +}); + +type ElementProps = { + columnKeys?: TransferHistoryTableColumnKey[]; + columnWidths?: Partial>; +}; + +type StyleProps = { + withOuterBorder?: boolean; + withInnerBorders?: boolean; +}; + +export const TransferHistoryTable = ({ + columnKeys = Object.values(TransferHistoryTableColumnKey), + columnWidths, + withOuterBorder, + withInnerBorders = true, +}: ElementProps & StyleProps) => { + const stringGetter = useStringGetter(); + const dispatch = useDispatch(); + const { isMobile, isTablet } = useBreakpoints(); + + const canAccountTrade = useSelector(calculateCanAccountTrade, shallowEqual); + + const transfers = useSelector(getSubaccountTransfers, shallowEqual) ?? []; + + return ( + row.id} + columns={columnKeys.map((key: TransferHistoryTableColumnKey) => + getTransferHistoryTableColumnDef({ + key, + isTablet, + stringGetter, + width: columnWidths?.[key], + }) + )} + slotEmpty={ + <> + {stringGetter({ key: STRING_KEYS.TRANSFERS_EMPTY_STATE })} + {canAccountTrade ? ( + + ) : ( + + )} + + } + selectionBehavior="replace" + withOuterBorder={withOuterBorder} + withInnerBorders={withInnerBorders} + withScrollSnapColumns + withScrollSnapRows + /> + ); +}; + +const Styled: Record = {}; + +Styled.Table = styled(Table)` + ${tradeViewMixins.horizontalTable} +`; + +Styled.InlineRow = styled.div` + ${layoutMixins.inlineRow} +`; + +Styled.Icon = styled(Icon)` + font-size: 3em; +`; + +Styled.TimeOutput = styled(Output)` + color: var(--color-text-0); +`; + +Styled.TxHash = styled(Link)` + justify-content: flex-end; +`;