Atomic components stories (#1422)

This commit is contained in:
claudiaHash 2022-05-23 14:29:37 +03:00 committed by GitHub
parent 9027fc1307
commit 6d2dea8c9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
63 changed files with 10240 additions and 4715 deletions

View File

@ -127,4 +127,5 @@ jobs:
restore-keys: ${{ runner.os }}-${{ matrix.node }}-storybook-${{ env.cache-name }}- restore-keys: ${{ runner.os }}-${{ matrix.node }}-storybook-${{ env.cache-name }}-
- run: npm ci - run: npm ci
- run: npm run pregenerate
- run: npm run storybook:build - run: npm run storybook:build

View File

@ -0,0 +1,13 @@
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // deprecated
removeListener: jest.fn(), // deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn()
}))
})

View File

@ -1 +1,2 @@
import '@testing-library/jest-dom/extend-expect' import '@testing-library/jest-dom/extend-expect'
import './__mocks__/matchMedia'

View File

@ -1,5 +1,5 @@
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin') const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin')
const webpack = require('webpack')
module.exports = { module.exports = {
core: { builder: 'webpack5' }, core: { builder: 'webpack5' },
stories: ['../src/**/*.stories.tsx'], stories: ['../src/**/*.stories.tsx'],
@ -47,6 +47,12 @@ module.exports = {
}) })
config.resolve.fallback = fallback config.resolve.fallback = fallback
config.plugins = (config.plugins || []).concat([
new webpack.ProvidePlugin({
process: 'process/browser',
Buffer: ['buffer', 'Buffer']
})
])
return config return config
} }
} }

View File

@ -13,7 +13,7 @@ export const parameters = {
controls: { controls: {
matchers: { matchers: {
color: /(background|color)$/i, color: /(background|color)$/i,
date: /Date$/ date: /date$/
} }
} }
} }

13792
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -18,8 +18,8 @@
"deploy:s3": "bash scripts/deploy-s3.sh", "deploy:s3": "bash scripts/deploy-s3.sh",
"postinstall": "husky install", "postinstall": "husky install",
"codegen:apollo": "apollo client:codegen --endpoint=https://v4.subgraph.rinkeby.oceanprotocol.com/subgraphs/name/oceanprotocol/ocean-subgraph --target typescript --tsFileExtension=d.ts --outputFlat src/@types/subgraph/", "codegen:apollo": "apollo client:codegen --endpoint=https://v4.subgraph.rinkeby.oceanprotocol.com/subgraphs/name/oceanprotocol/ocean-subgraph --target typescript --tsFileExtension=d.ts --outputFlat src/@types/subgraph/",
"storybook": "start-storybook -p 6006 --quiet", "storybook": "cross-env NODE_ENV=test start-storybook -p 6006 --quiet",
"storybook:build": "build-storybook" "storybook:build": "cross-env NODE_ENV=test build-storybook"
}, },
"dependencies": { "dependencies": {
"@coingecko/cryptoformat": "^0.4.4", "@coingecko/cryptoformat": "^0.4.4",
@ -58,7 +58,7 @@
"react-modal": "^3.15.1", "react-modal": "^3.15.1",
"react-paginate": "^8.1.3", "react-paginate": "^8.1.3",
"react-spring": "^9.4.5", "react-spring": "^9.4.5",
"react-tabs": "^3.2.3", "react-tabs": "^5.1.0",
"react-toastify": "^8.2.0", "react-toastify": "^8.2.0",
"remark": "^13.0.0", "remark": "^13.0.0",
"remark-gfm": "^1.0.0", "remark-gfm": "^1.0.0",
@ -73,41 +73,40 @@
"yup": "^0.32.11" "yup": "^0.32.11"
}, },
"devDependencies": { "devDependencies": {
"@storybook/addon-essentials": "^6.4.22", "@storybook/addon-essentials": "^6.5.4",
"@storybook/addon-storyshots": "^6.4.22", "@storybook/addon-storyshots": "^6.5.4",
"@storybook/builder-webpack5": "^6.4.22", "@storybook/builder-webpack5": "^6.5.4",
"@storybook/manager-webpack5": "^6.4.22", "@storybook/manager-webpack5": "^6.5.4",
"@storybook/react": "^6.4.22", "@storybook/react": "^6.5.4",
"@storybook/testing-library": "^0.0.11", "@storybook/testing-library": "^0.0.11",
"@storybook/testing-react": "^1.2.4", "@storybook/testing-react": "^1.3.0",
"@svgr/webpack": "^6.2.1", "@svgr/webpack": "^6.2.1",
"@testing-library/jest-dom": "^5.16.4", "@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.2.0", "@testing-library/react": "^13.2.0",
"@types/chart.js": "^2.9.37", "@types/chart.js": "^2.9.37",
"@types/d3": "^7.1.0",
"@types/js-cookie": "^3.0.1", "@types/js-cookie": "^3.0.1",
"@types/loadable__component": "^5.13.1", "@types/loadable__component": "^5.13.1",
"@types/lodash.debounce": "^4.0.3", "@types/lodash.debounce": "^4.0.3",
"@types/lodash.omit": "^4.5.6", "@types/lodash.omit": "^4.5.6",
"@types/node": "^17.0.13", "@types/node": "^17.0.35",
"@types/react": "^18.0.9", "@types/react": "^18.0.9",
"@types/react-dom": "^18.0.3", "@types/react-dom": "^18.0.4",
"@types/react-modal": "^3.13.1", "@types/react-modal": "^3.13.1",
"@types/react-paginate": "^7.1.1", "@types/react-paginate": "^7.1.1",
"@types/react-tabs": "^2.3.4",
"@types/remove-markdown": "^0.3.1", "@types/remove-markdown": "^0.3.1",
"@types/yup": "^0.29.13", "@types/yup": "^0.29.13",
"@typescript-eslint/eslint-plugin": "^5.23.0", "@typescript-eslint/eslint-plugin": "^5.25.0",
"@typescript-eslint/parser": "^5.23.0", "@typescript-eslint/parser": "^5.25.0",
"apollo": "^2.33.9", "apollo": "^2.33.9",
"eslint": "^8.15.0", "cross-env": "^7.0.3",
"eslint": "^8.16.0",
"eslint-config-oceanprotocol": "^2.0.1", "eslint-config-oceanprotocol": "^2.0.1",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-jest-dom": "^4.0.1", "eslint-plugin-jest-dom": "^4.0.2",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.29.4", "eslint-plugin-react": "^7.30.0",
"eslint-plugin-react-hooks": "^4.5.0", "eslint-plugin-react-hooks": "^4.5.0",
"eslint-plugin-testing-library": "^5.4.0", "eslint-plugin-testing-library": "^5.5.0",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"https-browserify": "^1.0.0", "https-browserify": "^1.0.0",
"husky": "^8.0.1", "husky": "^8.0.1",

View File

@ -58,11 +58,11 @@
} }
.radio { .radio {
composes: radio from '@shared/FormInput/InputElement.module.css'; composes: radio from '@shared/FormInput/InputRadio.module.css';
} }
.checkbox { .checkbox {
composes: checkbox from '@shared/FormInput/InputElement.module.css'; composes: checkbox from '@shared/FormInput/InputRadio.module.css';
} }
.title { .title {

View File

@ -76,10 +76,10 @@ export default function AssetSelection({
<div className={styles.row} key={asset.did}> <div className={styles.row} key={asset.did}>
<input <input
id={slugify(asset.did)} id={slugify(asset.did)}
type={multiple ? 'checkbox' : 'radio'}
className={styleClassesInput} className={styleClassesInput}
defaultChecked={asset.checked}
{...props} {...props}
defaultChecked={asset.checked}
type={multiple ? 'checkbox' : 'radio'}
disabled={disabled} disabled={disabled}
value={asset.did} value={asset.did}
/> />

View File

@ -45,11 +45,11 @@ export default function BoxSelection({
<div key={option.name}> <div key={option.name}>
<input <input
id={option.name} id={option.name}
type="radio"
className={styleClassesInput}
defaultChecked={option.checked} defaultChecked={option.checked}
onChange={(event) => handleChange(event)} onChange={(event) => handleChange(event)}
{...props} {...props}
type="radio"
className={styleClassesInput}
disabled={disabled} disabled={disabled}
value={option.value ? option.value : option.name} value={option.value ? option.value : option.name}
name={name} name={name}

View File

@ -81,92 +81,12 @@
font-family: var(--font-family-base); font-family: var(--font-family-base);
} }
.radioGroup {
margin-top: calc(var(--spacer) / 2);
}
.radioWrap {
position: relative;
}
.radioLabel {
margin: 0;
padding: 0;
font-weight: var(--font-weight-bold);
font-size: var(--font-size-small);
padding-left: 0.5rem;
}
.algorithmLabel { .algorithmLabel {
display: grid; display: grid;
gap: var(--spacer); gap: var(--spacer);
grid-template-columns: 2fr 1fr; grid-template-columns: 2fr 1fr;
} }
.radio,
.checkbox {
composes: input;
position: relative;
padding: 0;
width: 18px;
height: 18px;
min-height: 0;
display: inline-block;
vertical-align: middle;
margin-top: -2px;
}
.radio::after,
.checkbox::after {
content: '';
display: block;
left: 0;
top: 0;
position: absolute;
opacity: 0;
transition: transform 0.3s ease-out, opacity 0.2s;
}
.radio:checked,
.checkbox:checked {
border-color: var(--color-primary);
background: var(--color-primary);
}
.radio:focus,
.checkbox:focus {
box-shadow: 0 0 0 var(--color-primary);
}
.radio:checked::after,
.checkbox:checked::after {
opacity: 1;
}
.radio,
.radio::after {
border-radius: 50%;
}
.radio::after {
width: 8px;
height: 8px;
top: 4px;
left: 4px;
background: var(--brand-white);
}
.checkbox::after {
width: 6px;
height: 9px;
border: 2px solid var(--brand-white);
border-top: 0;
border-left: 0;
left: 5px;
top: 2px;
transform: rotate(40deg);
}
.prefixGroup, .prefixGroup,
.postfixGroup { .postfixGroup {
display: inline-flex; display: inline-flex;

View File

@ -1,5 +1,4 @@
import React, { ReactElement } from 'react' import React, { ReactElement } from 'react'
import slugify from 'slugify'
import styles from './InputElement.module.css' import styles from './InputElement.module.css'
import { InputProps } from '.' import { InputProps } from '.'
import FilesInput from '../FormFields/FilesInput' import FilesInput from '../FormFields/FilesInput'
@ -11,15 +10,20 @@ import AssetSelection, {
AssetSelectionAsset AssetSelectionAsset
} from '../FormFields/AssetSelection' } from '../FormFields/AssetSelection'
import Nft from '../FormFields/Nft' import Nft from '../FormFields/Nft'
import InputRadio from './InputRadio'
const cx = classNames.bind(styles) const cx = classNames.bind(styles)
const DefaultInput = ({ const DefaultInput = ({
size, size,
className, className,
// We filter out all props which are not allowed
// to be passed to HTML input so these stay unused.
/* eslint-disable @typescript-eslint/no-unused-vars */
prefix, prefix,
postfix, postfix,
additionalComponent, additionalComponent,
/* eslint-enable @typescript-eslint/no-unused-vars */
...props ...props
}: InputProps) => ( }: InputProps) => (
<input <input
@ -30,27 +34,28 @@ const DefaultInput = ({
) )
export default function InputElement({ export default function InputElement({
type,
options, options,
sortOptions, sortOptions,
name,
prefix, prefix,
postfix, postfix,
size, size,
field, field,
label,
multiple, multiple,
disabled, // We filter out all props which are not allowed
// to be passed to HTML input so these stay unused.
/* eslint-disable @typescript-eslint/no-unused-vars */
label,
help, help,
prominentHelp, prominentHelp,
form, form,
additionalComponent, additionalComponent,
disclaimer, disclaimer,
disclaimerValues, disclaimerValues,
/* eslint-enable @typescript-eslint/no-unused-vars */
...props ...props
}: InputProps): ReactElement { }: InputProps): ReactElement {
const styleClasses = cx({ select: true, [size]: size }) const styleClasses = cx({ select: true, [size]: size })
switch (type) { switch (props.type) {
case 'select': { case 'select': {
const sortedOptions = const sortedOptions =
!sortOptions && sortOptions === false !sortOptions && sortOptions === false
@ -60,10 +65,9 @@ export default function InputElement({
) )
return ( return (
<select <select
id={name} id={props.name}
className={styleClasses} className={styleClasses}
{...props} {...props}
disabled={disabled}
multiple={multiple} multiple={multiple}
> >
{field !== undefined && field.value === '' && <option value="" />} {field !== undefined && field.value === '' && <option value="" />}
@ -77,39 +81,12 @@ export default function InputElement({
) )
} }
case 'textarea': case 'textarea':
return ( return <textarea id={props.name} className={styles.textarea} {...props} />
<textarea
name={name}
id={name}
className={styles.textarea}
{...props}
/>
)
case 'radio': case 'radio':
case 'checkbox': case 'checkbox':
return ( return <InputRadio options={options} inputSize={size} {...props} />
<div className={styles.radioGroup}>
{options &&
(options as string[]).map((option: string, index: number) => (
<div className={styles.radioWrap} key={index}>
<input
className={styles[type]}
id={slugify(option)}
type={type}
name={name}
defaultChecked={props.defaultChecked}
{...props}
/>
<label
className={cx({ [styles.radioLabel]: true, [size]: size })}
htmlFor={slugify(option)}
>
{option}
</label>
</div>
))}
</div>
)
case 'assetSelection': case 'assetSelection':
return ( return (
<AssetSelection <AssetSelection
@ -118,28 +95,27 @@ export default function InputElement({
{...props} {...props}
/> />
) )
case 'assetSelectionMultiple': case 'assetSelectionMultiple':
return ( return (
<AssetSelection <AssetSelection
assets={options as unknown as AssetSelectionAsset[]} assets={options as unknown as AssetSelectionAsset[]}
multiple multiple
disabled={disabled}
{...field} {...field}
{...props} {...props}
/> />
) )
case 'files': case 'files':
return <FilesInput name={name} {...field} {...props} /> return <FilesInput {...field} {...props} />
case 'providerUrl': case 'providerUrl':
return <CustomProvider name={name} {...field} {...props} /> return <CustomProvider {...field} {...props} />
case 'nft': case 'nft':
return <Nft name={name} {...field} {...props} /> return <Nft {...field} {...props} />
case 'datatoken': case 'datatoken':
return <Datatoken name={name} {...field} {...props} /> return <Datatoken {...field} {...props} />
case 'boxSelection': case 'boxSelection':
return ( return (
<BoxSelection <BoxSelection
name={name}
options={options as unknown as BoxSelectionOption[]} options={options as unknown as BoxSelectionOption[]}
{...field} {...field}
{...props} {...props}
@ -152,10 +128,8 @@ export default function InputElement({
<div className={cx({ prefix: true, [size]: size })}>{prefix}</div> <div className={cx({ prefix: true, [size]: size })}>{prefix}</div>
)} )}
<DefaultInput <DefaultInput
name={name} type={props.type || 'text'}
type={type || 'text'}
size={size} size={size}
disabled={disabled}
{...field} {...field}
{...props} {...props}
/> />
@ -165,10 +139,8 @@ export default function InputElement({
</div> </div>
) : ( ) : (
<DefaultInput <DefaultInput
name={name} type={props.type || 'text'}
type={type || 'text'}
size={size} size={size}
disabled={disabled}
{...field} {...field}
{...props} {...props}
/> />

View File

@ -0,0 +1,79 @@
.radioGroup {
margin-top: calc(var(--spacer) / 2);
}
.radioWrap {
position: relative;
}
.radioLabel {
margin: 0;
padding: 0;
font-weight: var(--font-weight-bold);
font-size: var(--font-size-small);
padding-left: 0.5rem;
}
.radio,
.checkbox {
composes: input from './InputElement.module.css';
position: relative;
padding: 0;
width: 18px;
height: 18px;
min-height: 0;
display: inline-block;
vertical-align: middle;
margin-top: -2px;
}
.radio:focus,
.checkbox:focus {
box-shadow: 0 0 0 var(--color-primary);
}
.radio::after,
.checkbox::after {
content: '';
display: block;
left: 0;
top: 0;
position: absolute;
opacity: 0;
transition: transform 0.3s ease-out, opacity 0.2s;
}
.radio,
.radio::after {
border-radius: 50%;
}
.radio::after {
width: 8px;
height: 8px;
top: 4px;
left: 4px;
background: var(--brand-white);
}
.checkbox::after {
width: 6px;
height: 9px;
border: 2px solid var(--brand-white);
border-top: 0;
border-left: 0;
left: 5px;
top: 2px;
transform: rotate(40deg);
}
.radio:checked,
.checkbox:checked {
border-color: var(--color-primary);
background: var(--color-primary);
}
.radio:checked::after,
.checkbox:checked::after {
opacity: 1;
}

View File

@ -0,0 +1,41 @@
import React, { InputHTMLAttributes, ReactElement } from 'react'
import slugify from 'slugify'
import classNames from 'classnames/bind'
import styles from './InputRadio.module.css'
const cx = classNames.bind(styles)
interface InputRadioProps extends InputHTMLAttributes<HTMLInputElement> {
options: string[]
inputSize?: string
}
export default function InputRadio({
options,
inputSize,
...props
}: InputRadioProps): ReactElement {
return (
<div className={styles.radioGroup}>
{options &&
(options as string[]).map((option: string, index: number) => (
<div className={styles.radioWrap} key={index}>
<input
{...props}
className={styles[props.type]}
id={slugify(option)}
/>
<label
className={cx({
[styles.radioLabel]: true,
[inputSize]: inputSize
})}
htmlFor={slugify(option)}
>
{option}
</label>
</div>
))}
</div>
)
}

View File

@ -264,6 +264,7 @@ export default function PoolTransactions({
minimal ? transactions?.length >= 4 : transactions?.length >= 9 minimal ? transactions?.length >= 4 : transactions?.length >= 9
} }
paginationPerPage={minimal ? 5 : 10} paginationPerPage={minimal ? 5 : 10}
emptyMessage={chainIds.length === 0 ? 'No network selected' : null}
/> />
) : ( ) : (
<div>Please connect your Web3 wallet.</div> <div>Please connect your Web3 wallet.</div>

View File

@ -0,0 +1,20 @@
import React from 'react'
import { ComponentStory, ComponentMeta } from '@storybook/react'
import Badge, { BadgeProps } from '@shared/atoms/Badge'
export default {
title: 'Component/@shared/atoms/Badge',
component: Badge
} as ComponentMeta<typeof Badge>
const Template: ComponentStory<typeof Badge> = (args) => <Badge {...args} />
interface Props {
args: BadgeProps
}
export const Default: Props = Template.bind({})
Default.args = {
label: 'Badge label'
}

View File

@ -1,16 +1,15 @@
import React, { ReactElement } from 'react' import React, { ReactElement } from 'react'
import styles from './Badge.module.css' import styles from './index.module.css'
import classNames from 'classnames/bind' import classNames from 'classnames/bind'
const cx = classNames.bind(styles) const cx = classNames.bind(styles)
export default function Badge({ export interface BadgeProps {
label,
className
}: {
label: string label: string
className?: string className?: string
}): ReactElement { }
export default function Badge({ label, className }: BadgeProps): ReactElement {
const styleClasses = cx({ const styleClasses = cx({
badge: true, badge: true,
[className]: className [className]: className

View File

@ -0,0 +1,22 @@
import React from 'react'
import { ComponentStory, ComponentMeta } from '@storybook/react'
import Blockies, { BlockiesProps } from '@shared/atoms/Blockies'
export default {
title: 'Component/@shared/atoms/Blockies',
component: Blockies
} as ComponentMeta<typeof Blockies>
const Template: ComponentStory<typeof Blockies> = (args) => (
<Blockies {...args} />
)
interface Props {
args: BlockiesProps
}
export const Default: Props = Template.bind({})
Default.args = {
accountId: '0x1xxxxxxxxxx3Exxxxxx7xxxxxxxxxxxxF1fd'
}

View File

@ -1,14 +1,16 @@
import { toDataUrl } from 'myetherwallet-blockies' import { toDataUrl } from 'myetherwallet-blockies'
import React, { ReactElement } from 'react' import React, { ReactElement } from 'react'
import styles from './Blockies.module.css' import styles from './index.module.css'
export interface BlockiesProps {
accountId: string
className?: string
}
export default function Blockies({ export default function Blockies({
accountId, accountId,
className className
}: { }: BlockiesProps): ReactElement {
accountId: string
className?: string
}): ReactElement {
if (!accountId) return null if (!accountId) return null
const blockies = toDataUrl(accountId) const blockies = toDataUrl(accountId)

View File

@ -0,0 +1,43 @@
import React from 'react'
import { ComponentStory, ComponentMeta } from '@storybook/react'
import Container, { ContainerProps } from '@shared/atoms/Container'
export default {
title: 'Component/@shared/atoms/Container',
component: Container
} as ComponentMeta<typeof Container>
const Template: ComponentStory<typeof Container> = (args) => (
<Container {...args} />
)
interface Props {
args: ContainerProps
}
export const Default: Props = Template.bind({})
Default.args = {
children: (
<>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris aliquam
facilisis molestie. Integer eget congue turpis, in pharetra lectus. Sed
urna dolor, porttitor luctus mauris eget, lacinia consectetur eros. Duis
consequat, turpis et porttitor cursus, ante lacus placerat arcu, vel
pellentesque enim orci ac sem.
</>
)
}
export const Narrow: Props = Template.bind({})
Narrow.args = {
children: (
<>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris aliquam
facilisis molestie. Integer eget congue turpis, in pharetra lectus. Sed
urna dolor, porttitor luctus mauris eget, lacinia consectetur eros. Duis
consequat, turpis et porttitor cursus, ante lacus placerat arcu, vel
pellentesque enim orci ac sem.
</>
),
narrow: true
}

View File

@ -1,18 +1,20 @@
import React, { ReactElement, ReactNode } from 'react' import React, { ReactElement, ReactNode } from 'react'
import classNames from 'classnames/bind' import classNames from 'classnames/bind'
import styles from './Container.module.css' import styles from './index.module.css'
const cx = classNames.bind(styles) const cx = classNames.bind(styles)
export interface ContainerProps {
children: ReactNode
narrow?: boolean
className?: string
}
export default function Container({ export default function Container({
children, children,
narrow, narrow,
className className
}: { }: ContainerProps): ReactElement {
children: ReactNode
narrow?: boolean
className?: string
}): ReactElement {
const styleClasses = cx({ const styleClasses = cx({
container: true, container: true,
narrow, narrow,

View File

@ -0,0 +1,15 @@
import React from 'react'
import { ComponentStory, ComponentMeta } from '@storybook/react'
import Copy from '@shared/atoms/Copy'
export default {
title: 'Component/@shared/atoms/Copy',
component: Copy
} as ComponentMeta<typeof Copy>
const Template: ComponentStory<typeof Copy> = (args) => <Copy {...args} />
export const Default = Template.bind({})
Default.args = {
text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris aliquam facilisis molestie.'
}

View File

@ -0,0 +1,27 @@
import React from 'react'
import { render, act, screen, fireEvent } from '@testing-library/react'
import { Default } from './index.stories'
jest.useFakeTimers()
describe('Copy', () => {
test('should change class on click', () => {
render(<Default {...Default.args} />)
const element = screen.getByTitle('Copy to clipboard')
fireEvent.click(element)
expect(element).toHaveClass('copied')
})
test('should remove class after timer end', () => {
render(<Default {...Default.args} />)
const element = screen.getByTitle('Copy to clipboard')
fireEvent.click(element)
act(() => {
jest.advanceTimersToNextTimer()
})
expect(element).not.toHaveClass('copied')
})
})

View File

@ -1,9 +1,13 @@
import React, { ReactElement, useEffect, useState } from 'react' import React, { ReactElement, useEffect, useState } from 'react'
import styles from './Copy.module.css' import styles from './index.module.css'
import IconCopy from '@images/copy.svg' import IconCopy from '@images/copy.svg'
import Clipboard from 'react-clipboard.js' import Clipboard from 'react-clipboard.js'
export default function Copy({ text }: { text: string }): ReactElement { export interface CopyProps {
text: string
}
export default function Copy({ text }: CopyProps): ReactElement {
const [isCopied, setIsCopied] = useState(false) const [isCopied, setIsCopied] = useState(false)
// Clear copy success style after 5 sec. // Clear copy success style after 5 sec.

View File

@ -0,0 +1,45 @@
import React from 'react'
import { ComponentStory, ComponentMeta } from '@storybook/react'
import { ListItem } from '@shared/atoms/Lists'
export default {
title: 'Component/@shared/atoms/Lists',
component: ListItem
} as ComponentMeta<typeof ListItem>
const Template: ComponentStory<typeof ListItem> = (args) => (
<ListItem {...args} />
)
const items = [
'List item short',
'List item long ipsum dolor sit amet, consectetur adipiscing elit. Mauris aliquam facilisis molestie',
'List item long ipsum dolor sit amet, consectetur adipiscing elit',
'List item short',
'List item long ipsum dolor sit amet, consectetur adipiscing elit. Mauris aliquam facilisis molestie',
'List item long ipsum dolor sit amet, consectetur adipiscing elit'
]
export const Unordered = Template.bind({})
Unordered.decorators = [
() => (
<ul>
{items.map((item, key) => (
<Template key={key}>{item}</Template>
))}
</ul>
)
]
export const Ordered = Template.bind({})
Ordered.decorators = [
() => (
<ol>
{items.map((item, key) => (
<Template ol key={key}>
{item}
</Template>
))}
</ol>
)
]

View File

@ -1,13 +1,12 @@
import React, { ReactElement, ReactNode } from 'react' import React, { ReactElement, ReactNode } from 'react'
import styles from './Lists.module.css' import styles from './index.module.css'
export function ListItem({ export interface ListItemProps {
children, children?: ReactNode
ol
}: {
children: ReactNode
ol?: boolean ol?: boolean
}): ReactElement { }
export function ListItem({ children, ol }: ListItemProps): ReactElement {
const classes = ol const classes = ol
? `${styles.item} ${styles.olItem}` ? `${styles.item} ${styles.olItem}`
: `${styles.item} ${styles.ulItem}` : `${styles.item} ${styles.ulItem}`

View File

@ -0,0 +1,23 @@
import React from 'react'
import { ComponentStory, ComponentMeta } from '@storybook/react'
import Loader, { LoaderProps } from '@shared/atoms/Loader'
export default {
title: 'Component/@shared/atoms/Loader',
component: Loader
} as ComponentMeta<typeof Loader>
const Template: ComponentStory<typeof Loader> = (args) => <Loader {...args} />
interface Props {
args: LoaderProps
}
export const Default: Props = Template.bind({})
Default.args = {}
export const WithMessage: Props = Template.bind({})
WithMessage.args = {
message: 'Loading...'
}

View File

@ -1,11 +1,11 @@
import React, { ReactElement } from 'react' import React, { ReactElement } from 'react'
import styles from './Loader.module.css' import styles from './index.module.css'
export default function Loader({ export interface LoaderProps {
message
}: {
message?: string message?: string
}): ReactElement { }
export default function Loader({ message }: LoaderProps): ReactElement {
return ( return (
<div className={styles.loaderWrap}> <div className={styles.loaderWrap}>
<span className={styles.loader} /> <span className={styles.loader} />

View File

@ -14,7 +14,10 @@ interface Props {
args: LogoProps args: LogoProps
} }
export const Primary: Props = Template.bind({}) export const Default: Props = Template.bind({})
Primary.args = { Default.args = {}
export const WithoutWordmark: Props = Template.bind({})
WithoutWordmark.args = {
noWordmark: true noWordmark: true
} }

View File

@ -14,7 +14,7 @@
} }
.modal { .modal {
composes: box from './Box.module.css'; composes: box from '../Box.module.css';
padding: var(--spacer); padding: var(--spacer);
margin: var(--spacer) auto; margin: var(--spacer) auto;
max-width: var(--break-point--small); max-width: var(--break-point--small);

View File

@ -0,0 +1,32 @@
import React from 'react'
import { ComponentStory, ComponentMeta } from '@storybook/react'
import Button from '@shared/atoms/Button'
import Modal, { ModalProps } from '@shared/atoms/Modal'
import { useArgs } from '@storybook/client-api'
export default {
title: 'Component/@shared/atoms/Modal',
component: Modal
} as ComponentMeta<typeof Modal>
const Template: ComponentStory<typeof Modal> = (args: ModalProps) => {
const [{ isOpen }, updateArgs] = useArgs()
const handleClose = () => updateArgs({ isOpen: !isOpen })
return (
<>
<Button style="primary" onClick={() => updateArgs({ isOpen: !isOpen })}>
Open Modal
</Button>
<Modal {...args} onToggleModal={handleClose}>
<a>This is a modal</a>
</Modal>
</>
)
}
interface Props {
args: ModalProps
}
export const Default: Props = Template.bind({})

View File

@ -1,6 +1,6 @@
import React, { ReactElement, ReactNode } from 'react' import React, { ReactElement, ReactNode } from 'react'
import ReactModal from 'react-modal' import ReactModal from 'react-modal'
import styles from './Modal.module.css' import styles from './index.module.css'
if (process.env.NODE_ENV !== 'test') ReactModal.setAppElement('#__next') if (process.env.NODE_ENV !== 'test') ReactModal.setAppElement('#__next')

View File

@ -0,0 +1,28 @@
import React from 'react'
import { ComponentStory, ComponentMeta } from '@storybook/react'
import Status, { StatusProps } from '@shared/atoms/Status'
export default {
title: 'Component/@shared/atoms/Status',
component: Status
} as ComponentMeta<typeof Status>
const Template: ComponentStory<typeof Status> = (args) => <Status {...args} />
interface Props {
args: StatusProps
}
export const Default: Props = Template.bind({})
Default.args = {}
export const Warning: Props = Template.bind({})
Warning.args = {
state: 'warning'
}
export const Error: Props = Template.bind({})
Error.args = {
state: 'error'
}

View File

@ -1,16 +1,18 @@
import React, { ReactElement } from 'react' import React, { ReactElement } from 'react'
import classNames from 'classnames/bind' import classNames from 'classnames/bind'
import styles from './Status.module.css' import styles from './index.module.css'
export interface StatusProps {
state?: string
className?: string
}
const cx = classNames.bind(styles) const cx = classNames.bind(styles)
export default function Status({ export default function Status({
state, state,
className className
}: { }: StatusProps): ReactElement {
state?: string
className?: string
}): ReactElement {
const styleClasses = cx({ const styleClasses = cx({
status: true, status: true,
warning: state === 'warning', warning: state === 'warning',

View File

@ -0,0 +1,82 @@
import React from 'react'
import { ComponentStory, ComponentMeta } from '@storybook/react'
import Table, { TableProps } from '@shared/atoms/Table'
export default {
title: 'Component/@shared/atoms/Table',
component: Table
} as ComponentMeta<typeof Table>
const Template: ComponentStory<typeof Table> = (args) => <Table {...args} />
interface Props {
args: TableProps
}
const columns = [
{
name: 'Name',
selector: (row: any) => row.name,
maxWidth: '45rem',
grow: 1
},
{
name: 'Symbol',
selector: (row: any) => row.symbol,
maxWidth: '10rem'
},
{
name: 'Price',
selector: (row: any) => row.price,
right: true
}
]
const data = [
{
name: 'Title asset',
symbol: 'DATA-70',
price: '1.011'
},
{
name: 'Title asset Title asset Title asset Title asset Title asset',
symbol: 'DATA-71',
price: '1.011'
},
{
name: 'Title asset',
symbol: 'DATA-72',
price: '1.011'
},
{
name: 'Title asset Title asset Title asset Title asset Title asset Title asset Title asset Title asset Title asset Title asset',
symbol: 'DATA-71',
price: '1.011'
}
]
export const WithData: Props = Template.bind({})
WithData.args = {
columns,
data
}
export const WithPagination: Props = Template.bind({})
WithPagination.args = {
columns,
data: data.flatMap((i) => [i, i, i])
}
export const Loading: Props = Template.bind({})
Loading.args = {
isLoading: true,
columns: [],
data: []
}
export const Empty: Props = Template.bind({})
Empty.args = {
emptyMessage: 'I am empty',
columns: [],
data: []
}

View File

@ -1,11 +1,10 @@
import React, { ReactElement, ReactNode } from 'react' import React, { ReactElement, ReactNode } from 'react'
import DataTable, { IDataTableProps } from 'react-data-table-component' import DataTable, { IDataTableProps } from 'react-data-table-component'
import Loader from './Loader' import Loader from '../Loader'
import Pagination from '@shared/Pagination' import Pagination from '@shared/Pagination'
import styles from './Table.module.css' import styles from './index.module.css'
import { useUserPreferences } from '@context/UserPreferences'
interface TableProps extends IDataTableProps { export interface TableProps extends IDataTableProps {
isLoading?: boolean isLoading?: boolean
emptyMessage?: string emptyMessage?: string
sortField?: string sortField?: string
@ -14,14 +13,7 @@ interface TableProps extends IDataTableProps {
} }
function Empty({ message }: { message?: string }): ReactElement { function Empty({ message }: { message?: string }): ReactElement {
const { chainIds } = useUserPreferences() return <div className={styles.empty}>{message || 'No results found'}</div>
return (
<div className={styles.empty}>
{chainIds.length === 0
? 'No network selected'
: message || 'No results found'}
</div>
)
} }
export default function Table({ export default function Table({

View File

@ -8,11 +8,10 @@
.tab { .tab {
display: inline-block; display: inline-block;
padding: calc(var(--spacer) / 12) var(--spacer); padding: calc(var(--spacer) / 8) var(--spacer);
font-weight: var(--font-weight-bold); font-weight: var(--font-weight-bold);
font-size: var(--font-size-small); font-size: var(--font-size-small);
text-transform: uppercase; text-transform: uppercase;
cursor: pointer;
color: var(--color-secondary); color: var(--color-secondary);
background-color: var(--background-body); background-color: var(--background-body);
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
@ -20,6 +19,11 @@
min-width: 90px; min-width: 90px;
} }
.tab,
.tab label {
cursor: pointer;
}
.tab:first-child { .tab:first-child {
border-top-left-radius: var(--border-radius); border-top-left-radius: var(--border-radius);
border-bottom-left-radius: var(--border-radius); border-bottom-left-radius: var(--border-radius);
@ -53,3 +57,7 @@
padding: var(--spacer); padding: var(--spacer);
} }
} }
.radio {
composes: radio from '../../FormInput/InputRadio.module.css';
}

View File

@ -0,0 +1,52 @@
import React from 'react'
import { ComponentStory, ComponentMeta } from '@storybook/react'
import Tabs, { TabsProps } from '@shared/atoms/Tabs'
export default {
title: 'Component/@shared/atoms/Tabs',
component: Tabs
} as ComponentMeta<typeof Tabs>
const Template: ComponentStory<typeof Tabs> = (args) => <Tabs {...args} />
interface Props {
args: TabsProps
}
const items = [
{
title: 'First tab',
content: 'this is the content for the first tab'
},
{
title: 'Second tab',
content: 'this is the content for the second tab'
},
{
title: 'Third tab',
content: 'this is the content for the third tab'
}
]
export const Default = Template.bind({})
Default.args = {
items
}
export const WithRadio: Props = Template.bind({})
WithRadio.args = {
items,
showRadio: true
}
export const WithDefaultIndex: Props = Template.bind({})
WithDefaultIndex.args = {
items,
defaultIndex: 1
}
export const LotsOfTabs: Props = Template.bind({})
LotsOfTabs.args = {
items: items.flatMap((i) => [i, i, i])
}

View File

@ -0,0 +1,21 @@
import React from 'react'
import { render, screen, fireEvent } from '@testing-library/react'
import { Default } from './index.stories'
describe('Tabs', () => {
test('should be able to change', async () => {
render(<Default {...Default.args} />)
fireEvent.click(screen.getByText('Second tab'))
const secondTab = await screen.findByText(/content for the second tab/i)
expect(secondTab).toBeInTheDocument()
})
test('should fire custom change handler', async () => {
const handler = jest.fn()
render(<Default {...Default.args} handleTabChange={handler} />)
fireEvent.click(screen.getByText('Second tab'))
expect(handler).toBeCalledTimes(1)
})
})

View File

@ -1,7 +1,7 @@
import React, { ReactElement, ReactNode } from 'react' import React, { ReactElement, ReactNode, useState } from 'react'
import { Tab, Tabs as ReactTabs, TabList, TabPanel } from 'react-tabs' import { Tab, Tabs as ReactTabs, TabList, TabPanel } from 'react-tabs'
import InputElement from '@shared/FormInput/InputElement' import styles from './index.module.css'
import styles from './Tabs.module.css' import InputRadio from '@shared/FormInput/InputRadio'
export interface TabsItem { export interface TabsItem {
title: string title: string
@ -9,37 +9,36 @@ export interface TabsItem {
disabled?: boolean disabled?: boolean
} }
export interface TabsProps {
items: TabsItem[]
className?: string
handleTabChange?: (tabName: string) => void
defaultIndex?: number
showRadio?: boolean
}
export default function Tabs({ export default function Tabs({
items, items,
className, className,
handleTabChange, handleTabChange,
defaultIndex, defaultIndex,
showRadio showRadio
}: { }: TabsProps): ReactElement {
items: TabsItem[]
className?: string
handleTabChange?: (tabName: string) => void
defaultIndex?: number
showRadio?: boolean
}): ReactElement {
return ( return (
<ReactTabs <ReactTabs className={`${className || ''}`} defaultIndex={defaultIndex}>
className={`${className && className}`}
defaultIndex={defaultIndex}
>
<TabList className={styles.tabList}> <TabList className={styles.tabList}>
{items.map((item, index) => ( {items.map((item, index) => (
<Tab <Tab
className={styles.tab} className={styles.tab}
key={item.title} key={index}
onClick={handleTabChange ? () => handleTabChange(item.title) : null} onClick={handleTabChange ? () => handleTabChange(item.title) : null}
disabled={item.disabled} disabled={item.disabled}
> >
{showRadio ? ( {showRadio ? (
<InputElement <InputRadio
name={item.title} name={item.title}
type="radio" type="radio"
checked={defaultIndex === index} checked={index === defaultIndex}
options={[item.title]} options={[item.title]}
readOnly readOnly
/> />
@ -50,8 +49,8 @@ export default function Tabs({
))} ))}
</TabList> </TabList>
<div className={styles.tabContent}> <div className={styles.tabContent}>
{items.map((item) => ( {items.map((item, index) => (
<TabPanel key={item.title}>{item.content}</TabPanel> <TabPanel key={index}>{item.content}</TabPanel>
))} ))}
</div> </div>
</ReactTabs> </ReactTabs>

View File

@ -0,0 +1,40 @@
import React from 'react'
import { ComponentStory, ComponentMeta } from '@storybook/react'
import Tags, { TagsProps } from '@shared/atoms/Tags'
export default {
title: 'Component/@shared/atoms/Tags',
component: Tags
} as ComponentMeta<typeof Tags>
const Template: ComponentStory<typeof Tags> = (args) => <Tags {...args} />
interface Props {
args: TagsProps
}
export const Default: Props = Template.bind({})
Default.args = {
items: [' tag1 ', ' tag2 ', ' tag3 '],
className: 'custom-class'
}
export const MaxNumberOfTags: Props = Template.bind({})
MaxNumberOfTags.args = {
items: [' tag1 ', ' tag2 ', ' tag3 '],
max: 2
}
export const ShowMore: Props = Template.bind({})
ShowMore.args = {
items: [' tag1 ', ' tag2 ', ' tag3 '],
max: 2,
showMore: true
}
export const WithoutLinks: Props = Template.bind({})
WithoutLinks.args = {
items: [' tag1 ', ' tag2 ', ' tag3 '],
noLinks: true
}

View File

@ -1,8 +1,8 @@
import React, { ReactElement } from 'react' import React, { ReactElement } from 'react'
import Link from 'next/link' import Link from 'next/link'
import styles from './Tags.module.css' import styles from './index.module.css'
declare type TagsProps = { export interface TagsProps {
items: string[] items: string[]
max?: number max?: number
showMore?: boolean showMore?: boolean

View File

@ -0,0 +1,37 @@
import React from 'react'
import { ComponentStory, ComponentMeta } from '@storybook/react'
import Time, { TimeProps } from '@shared/atoms/Time'
export default {
title: 'Component/@shared/atoms/Time',
component: Time
} as ComponentMeta<typeof Time>
const Template: ComponentStory<typeof Time> = (args) => <Time {...args} />
interface Props {
args: TimeProps
}
export const Default: Props = Template.bind({})
Default.args = {
date: '2022-05-02T11:50:28.000Z'
}
export const Relative: Props = Template.bind({})
Relative.args = {
date: '2022-05-02T11:50:28.000Z',
relative: true
}
export const IsUnix: Props = Template.bind({})
IsUnix.args = {
date: '1652448367',
isUnix: true
}
export const Undefined: Props = Template.bind({})
Undefined.args = {
date: null
}

View File

@ -1,19 +1,21 @@
import React, { ReactElement, useEffect, useState } from 'react' import React, { ReactElement, useEffect, useState } from 'react'
import { format, formatDistance } from 'date-fns' import { format, formatDistance } from 'date-fns'
export interface TimeProps {
date: string
relative?: boolean
isUnix?: boolean
displayFormat?: string
className?: string
}
export default function Time({ export default function Time({
date, date,
relative, relative,
isUnix, isUnix,
displayFormat, displayFormat,
className className
}: { }: TimeProps): ReactElement {
date: string
relative?: boolean
isUnix?: boolean
displayFormat?: string
className?: string
}): ReactElement {
const [dateIso, setDateIso] = useState<string>() const [dateIso, setDateIso] = useState<string>()
const [dateNew, setDateNew] = useState<Date>() const [dateNew, setDateNew] = useState<Date>()

View File

@ -3,7 +3,7 @@
} }
.content { .content {
composes: box from './Box.module.css'; composes: box from '../Box.module.css';
padding: calc(var(--spacer) / 4); padding: calc(var(--spacer) / 4);
max-width: 25rem; max-width: 25rem;
font-size: var(--font-size-small); font-size: var(--font-size-small);

View File

@ -0,0 +1,51 @@
import React from 'react'
import { ComponentStory, ComponentMeta } from '@storybook/react'
import { TippyProps } from '@tippyjs/react'
import Tooltip from '@shared/atoms/Tooltip'
export default {
title: 'Component/@shared/atoms/Tooltip',
component: Tooltip
} as ComponentMeta<typeof Tooltip>
const Template: ComponentStory<typeof Tooltip> = (args) => <Tooltip {...args} />
interface Props {
args: TippyProps
}
export const Default: Props = Template.bind({})
Default.args = {
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris aliquam facilisis molestie.'
}
export const WithContentOpened: Props = Template.bind({})
WithContentOpened.args = {
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris aliquam facilisis molestie.',
showOnCreate: true
}
export const WithCustomTriggerElement: Props = Template.bind({})
WithCustomTriggerElement.args = {
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris aliquam facilisis molestie.',
children: <a>Tooltip trigger</a>
}
export const WithCustomTriggerEvent: Props = Template.bind({})
WithCustomTriggerEvent.args = {
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris aliquam facilisis molestie.',
children: <button>Click here</button>,
trigger: 'on click'
}
export const Disabled: Props = Template.bind({})
Disabled.args = {
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris aliquam facilisis molestie.',
children: <a>Tooltip disabled</a>,
disabled: true
}

View File

@ -1,14 +1,8 @@
import React, { ReactElement, ReactNode } from 'react' import React, { ReactElement } from 'react'
import classNames from 'classnames/bind'
import loadable from '@loadable/component'
import { useSpring, animated } from 'react-spring' import { useSpring, animated } from 'react-spring'
import styles from './Tooltip.module.css' import stylesTooltip from './index.module.css'
import Info from '@images/info.svg' import Info from '@images/info.svg'
import { Placement } from 'tippy.js' import Tippy, { TippyProps } from '@tippyjs/react/headless'
const cx = classNames.bind(styles)
const Tippy = loadable(() => import('@tippyjs/react/headless'))
const animation = { const animation = {
config: { tension: 400, friction: 20 }, config: { tension: 400, friction: 20 },
@ -19,28 +13,15 @@ const animation = {
// Forward ref for Tippy.js // Forward ref for Tippy.js
// eslint-disable-next-line // eslint-disable-next-line
const DefaultTrigger = React.forwardRef((props, ref: any) => { const DefaultTrigger = React.forwardRef((props, ref: any) => {
return <Info className={styles.icon} ref={ref} /> return <Info className={stylesTooltip.icon} ref={ref} />
}) })
export default function Tooltip({ export default function Tooltip(props: TippyProps): ReactElement {
content, const { content, children, trigger, disabled, className, placement } = props
children, const [styles, api] = useSpring(() => animation.from)
trigger,
disabled,
className,
placement
}: {
content: ReactNode
children?: ReactNode
trigger?: string
disabled?: boolean
className?: string
placement?: Placement
}): ReactElement {
const [props, setSpring] = useSpring(() => animation.from)
function onMount() { function onMount() {
setSpring({ api.start({
...animation.to, ...animation.to,
onRest: (): void => null, onRest: (): void => null,
config: animation.config config: animation.config
@ -48,17 +29,14 @@ export default function Tooltip({
} }
function onHide({ unmount }: { unmount: () => void }) { function onHide({ unmount }: { unmount: () => void }) {
setSpring({ api.start({
...animation.from, ...animation.from,
onRest: unmount, onRest: unmount,
config: { ...animation.config, clamp: true } config: { ...animation.config, clamp: true }
}) })
} }
const styleClasses = cx({ const styleClasses = `${stylesTooltip.tooltip} ${className || ''}`
tooltip: true,
[className]: className
})
return ( return (
<Tippy <Tippy
@ -68,23 +46,21 @@ export default function Tooltip({
trigger={trigger || 'mouseenter focus'} trigger={trigger || 'mouseenter focus'}
disabled={disabled || null} disabled={disabled || null}
placement={placement || 'auto'} placement={placement || 'auto'}
render={(attrs: any) => ( render={(attrs) => (
<animated.div style={props}> <animated.div style={styles}>
<div className={styles.content} {...attrs}> <div className={stylesTooltip.content} {...attrs}>
{content} {content}
<div className={styles.arrow} data-popper-arrow /> <div className={stylesTooltip.arrow} data-popper-arrow />
</div> </div>
</animated.div> </animated.div>
)} )}
appendTo={ appendTo={
typeof document !== 'undefined' && document.querySelector('body') typeof document !== 'undefined' && document.querySelector('body')
} }
animation
onMount={onMount} onMount={onMount}
onHide={onHide} onHide={onHide}
fallback={ // animation
<div className={styleClasses}>{children || <DefaultTrigger />}</div> {...props}
}
> >
<div className={styleClasses}>{children || <DefaultTrigger />}</div> <div className={styleClasses}>{children || <DefaultTrigger />}</div>
</Tippy> </Tippy>

View File

@ -1,5 +1,5 @@
.radioWrap { .radioWrap {
composes: radioWrap from '@shared/FormInput/InputElement.module.css'; composes: radioWrap from '@shared/FormInput/InputRadio.module.css';
padding: calc(var(--spacer) / 6) calc(var(--spacer) / 3); padding: calc(var(--spacer) / 6) calc(var(--spacer) / 3);
border-bottom: 1px solid var(--border-color); border-bottom: 1px solid var(--border-color);
} }
@ -9,14 +9,14 @@
} }
.input { .input {
composes: checkbox from '@shared/FormInput/InputElement.module.css'; composes: checkbox from '@shared/FormInput/InputRadio.module.css';
vertical-align: baseline; vertical-align: baseline;
margin-right: calc(var(--spacer) / 3); margin-right: calc(var(--spacer) / 3);
margin-top: 0; margin-top: 0;
} }
.radioLabel { .radioLabel {
composes: radioLabel from '@shared/FormInput/InputElement.module.css'; composes: radioLabel from '@shared/FormInput/InputRadio.module.css';
font-weight: var(--font-weight-base); font-weight: var(--font-weight-base);
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -46,14 +46,16 @@ export default function Networks(): ReactElement {
trigger="click focus" trigger="click focus"
className={`${stylesIndex.preferences} ${styles.networks}`} className={`${stylesIndex.preferences} ${styles.networks}`}
> >
<Network aria-label="Networks" className={stylesIndex.icon} /> <>
<Caret aria-hidden="true" className={stylesIndex.caret} /> <Network aria-label="Networks" className={stylesIndex.icon} />
<Caret aria-hidden="true" className={stylesIndex.caret} />
<div className={styles.chainsSelected}> <div className={styles.chainsSelected}>
{chainIds.map((chainId) => ( {chainIds.map((chainId) => (
<span className={styles.chainsSelectedIndicator} key={chainId} /> <span className={styles.chainsSelectedIndicator} key={chainId} />
))} ))}
</div> </div>
</>
</Tooltip> </Tooltip>
) )
} }

View File

@ -28,8 +28,10 @@ export default function UserPreferences(): ReactElement {
trigger="click focus" trigger="click focus"
className={styles.preferences} className={styles.preferences}
> >
<Cog aria-label="Preferences" className={styles.icon} /> <>
<Caret aria-hidden="true" className={styles.caret} /> <Cog aria-label="Preferences" className={styles.icon} />
<Caret aria-hidden="true" className={styles.caret} />
</>
</Tooltip> </Tooltip>
) )
} }

View File

@ -27,7 +27,7 @@ const columns = [
selector: function getAssetRow(row: AssetExtended) { selector: function getAssetRow(row: AssetExtended) {
return ( return (
<Tooltip content={row.datatokens[0].name}> <Tooltip content={row.datatokens[0].name}>
{row.datatokens[0].symbol} <>{row.datatokens[0].symbol}</>
</Tooltip> </Tooltip>
) )
}, },
@ -96,7 +96,11 @@ export default function Bookmarks(): ReactElement {
columns={columns} columns={columns}
data={pinned} data={pinned}
isLoading={isLoading} isLoading={isLoading}
emptyMessage="Your bookmarks will appear here." emptyMessage={
chainIds.length === 0
? 'No network selected'
: 'Your bookmarks will appear here.'
}
noTableHead noTableHead
/> />
) )

View File

@ -123,6 +123,7 @@ export default function ComputeJobs({
isLoading={isLoading} isLoading={isLoading}
defaultSortField="row.dateCreated" defaultSortField="row.dateCreated"
defaultSortAsc={false} defaultSortAsc={false}
emptyMessage={chainIds.length === 0 ? 'No network selected' : null}
/> />
</> </>
) : ( ) : (

View File

@ -4,7 +4,7 @@ import Time from '@shared/atoms/Time'
import AssetTitle from '@shared/AssetList/AssetListTitle' import AssetTitle from '@shared/AssetList/AssetListTitle'
import NetworkName from '@shared/NetworkName' import NetworkName from '@shared/NetworkName'
import { useProfile } from '@context/Profile' import { useProfile } from '@context/Profile'
import { useUserPreferences } from '@context/UserPreferences'
const columns = [ const columns = [
{ {
name: 'Data Set', name: 'Data Set',
@ -38,6 +38,7 @@ export default function ComputeDownloads({
accountId: string accountId: string
}): ReactElement { }): ReactElement {
const { downloads, isDownloadsLoading } = useProfile() const { downloads, isDownloadsLoading } = useProfile()
const { chainIds } = useUserPreferences()
return accountId ? ( return accountId ? (
<Table <Table
@ -45,6 +46,7 @@ export default function ComputeDownloads({
data={downloads} data={downloads}
paginationPerPage={10} paginationPerPage={10}
isLoading={isDownloadsLoading} isLoading={isDownloadsLoading}
emptyMessage={chainIds.length === 0 ? 'No network selected' : null}
/> />
) : ( ) : (
<div>Please connect your Web3 wallet.</div> <div>Please connect your Web3 wallet.</div>

View File

@ -109,6 +109,7 @@ export default function PoolShares({
isLoading={loading} isLoading={loading}
sortField="userLiquidity" sortField="userLiquidity"
sortAsc={false} sortAsc={false}
emptyMessage={chainIds.length === 0 ? 'No network selected' : null}
/> />
) : ( ) : (
<div>Please connect your Web3 wallet.</div> <div>Please connect your Web3 wallet.</div>