mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
Atomic components stories (#1422)
This commit is contained in:
parent
9027fc1307
commit
6d2dea8c9d
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
@ -127,4 +127,5 @@ jobs:
|
||||
restore-keys: ${{ runner.os }}-${{ matrix.node }}-storybook-${{ env.cache-name }}-
|
||||
|
||||
- run: npm ci
|
||||
- run: npm run pregenerate
|
||||
- run: npm run storybook:build
|
||||
|
13
.jest/__mocks__/matchMedia.js
Normal file
13
.jest/__mocks__/matchMedia.js
Normal 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()
|
||||
}))
|
||||
})
|
@ -1 +1,2 @@
|
||||
import '@testing-library/jest-dom/extend-expect'
|
||||
import './__mocks__/matchMedia'
|
||||
|
@ -1,5 +1,5 @@
|
||||
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin')
|
||||
|
||||
const webpack = require('webpack')
|
||||
module.exports = {
|
||||
core: { builder: 'webpack5' },
|
||||
stories: ['../src/**/*.stories.tsx'],
|
||||
@ -47,6 +47,12 @@ module.exports = {
|
||||
})
|
||||
config.resolve.fallback = fallback
|
||||
|
||||
config.plugins = (config.plugins || []).concat([
|
||||
new webpack.ProvidePlugin({
|
||||
process: 'process/browser',
|
||||
Buffer: ['buffer', 'Buffer']
|
||||
})
|
||||
])
|
||||
return config
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ export const parameters = {
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/
|
||||
date: /date$/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
13808
package-lock.json
generated
13808
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
37
package.json
37
package.json
@ -18,8 +18,8 @@
|
||||
"deploy:s3": "bash scripts/deploy-s3.sh",
|
||||
"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/",
|
||||
"storybook": "start-storybook -p 6006 --quiet",
|
||||
"storybook:build": "build-storybook"
|
||||
"storybook": "cross-env NODE_ENV=test start-storybook -p 6006 --quiet",
|
||||
"storybook:build": "cross-env NODE_ENV=test build-storybook"
|
||||
},
|
||||
"dependencies": {
|
||||
"@coingecko/cryptoformat": "^0.4.4",
|
||||
@ -58,7 +58,7 @@
|
||||
"react-modal": "^3.15.1",
|
||||
"react-paginate": "^8.1.3",
|
||||
"react-spring": "^9.4.5",
|
||||
"react-tabs": "^3.2.3",
|
||||
"react-tabs": "^5.1.0",
|
||||
"react-toastify": "^8.2.0",
|
||||
"remark": "^13.0.0",
|
||||
"remark-gfm": "^1.0.0",
|
||||
@ -73,41 +73,40 @@
|
||||
"yup": "^0.32.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@storybook/addon-essentials": "^6.4.22",
|
||||
"@storybook/addon-storyshots": "^6.4.22",
|
||||
"@storybook/builder-webpack5": "^6.4.22",
|
||||
"@storybook/manager-webpack5": "^6.4.22",
|
||||
"@storybook/react": "^6.4.22",
|
||||
"@storybook/addon-essentials": "^6.5.4",
|
||||
"@storybook/addon-storyshots": "^6.5.4",
|
||||
"@storybook/builder-webpack5": "^6.5.4",
|
||||
"@storybook/manager-webpack5": "^6.5.4",
|
||||
"@storybook/react": "^6.5.4",
|
||||
"@storybook/testing-library": "^0.0.11",
|
||||
"@storybook/testing-react": "^1.2.4",
|
||||
"@storybook/testing-react": "^1.3.0",
|
||||
"@svgr/webpack": "^6.2.1",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/react": "^13.2.0",
|
||||
"@types/chart.js": "^2.9.37",
|
||||
"@types/d3": "^7.1.0",
|
||||
"@types/js-cookie": "^3.0.1",
|
||||
"@types/loadable__component": "^5.13.1",
|
||||
"@types/lodash.debounce": "^4.0.3",
|
||||
"@types/lodash.omit": "^4.5.6",
|
||||
"@types/node": "^17.0.13",
|
||||
"@types/node": "^17.0.35",
|
||||
"@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-paginate": "^7.1.1",
|
||||
"@types/react-tabs": "^2.3.4",
|
||||
"@types/remove-markdown": "^0.3.1",
|
||||
"@types/yup": "^0.29.13",
|
||||
"@typescript-eslint/eslint-plugin": "^5.23.0",
|
||||
"@typescript-eslint/parser": "^5.23.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.25.0",
|
||||
"@typescript-eslint/parser": "^5.25.0",
|
||||
"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-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-react": "^7.29.4",
|
||||
"eslint-plugin-react": "^7.30.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",
|
||||
"https-browserify": "^1.0.0",
|
||||
"husky": "^8.0.1",
|
||||
|
@ -58,11 +58,11 @@
|
||||
}
|
||||
|
||||
.radio {
|
||||
composes: radio from '@shared/FormInput/InputElement.module.css';
|
||||
composes: radio from '@shared/FormInput/InputRadio.module.css';
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
composes: checkbox from '@shared/FormInput/InputElement.module.css';
|
||||
composes: checkbox from '@shared/FormInput/InputRadio.module.css';
|
||||
}
|
||||
|
||||
.title {
|
||||
|
@ -76,10 +76,10 @@ export default function AssetSelection({
|
||||
<div className={styles.row} key={asset.did}>
|
||||
<input
|
||||
id={slugify(asset.did)}
|
||||
type={multiple ? 'checkbox' : 'radio'}
|
||||
className={styleClassesInput}
|
||||
defaultChecked={asset.checked}
|
||||
{...props}
|
||||
defaultChecked={asset.checked}
|
||||
type={multiple ? 'checkbox' : 'radio'}
|
||||
disabled={disabled}
|
||||
value={asset.did}
|
||||
/>
|
||||
|
@ -45,11 +45,11 @@ export default function BoxSelection({
|
||||
<div key={option.name}>
|
||||
<input
|
||||
id={option.name}
|
||||
type="radio"
|
||||
className={styleClassesInput}
|
||||
defaultChecked={option.checked}
|
||||
onChange={(event) => handleChange(event)}
|
||||
{...props}
|
||||
type="radio"
|
||||
className={styleClassesInput}
|
||||
disabled={disabled}
|
||||
value={option.value ? option.value : option.name}
|
||||
name={name}
|
||||
|
@ -81,92 +81,12 @@
|
||||
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 {
|
||||
display: grid;
|
||||
gap: var(--spacer);
|
||||
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,
|
||||
.postfixGroup {
|
||||
display: inline-flex;
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import slugify from 'slugify'
|
||||
import styles from './InputElement.module.css'
|
||||
import { InputProps } from '.'
|
||||
import FilesInput from '../FormFields/FilesInput'
|
||||
@ -11,15 +10,20 @@ import AssetSelection, {
|
||||
AssetSelectionAsset
|
||||
} from '../FormFields/AssetSelection'
|
||||
import Nft from '../FormFields/Nft'
|
||||
import InputRadio from './InputRadio'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
const DefaultInput = ({
|
||||
size,
|
||||
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,
|
||||
postfix,
|
||||
additionalComponent,
|
||||
/* eslint-enable @typescript-eslint/no-unused-vars */
|
||||
...props
|
||||
}: InputProps) => (
|
||||
<input
|
||||
@ -30,27 +34,28 @@ const DefaultInput = ({
|
||||
)
|
||||
|
||||
export default function InputElement({
|
||||
type,
|
||||
options,
|
||||
sortOptions,
|
||||
name,
|
||||
prefix,
|
||||
postfix,
|
||||
size,
|
||||
field,
|
||||
label,
|
||||
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,
|
||||
prominentHelp,
|
||||
form,
|
||||
additionalComponent,
|
||||
disclaimer,
|
||||
disclaimerValues,
|
||||
/* eslint-enable @typescript-eslint/no-unused-vars */
|
||||
...props
|
||||
}: InputProps): ReactElement {
|
||||
const styleClasses = cx({ select: true, [size]: size })
|
||||
switch (type) {
|
||||
switch (props.type) {
|
||||
case 'select': {
|
||||
const sortedOptions =
|
||||
!sortOptions && sortOptions === false
|
||||
@ -60,10 +65,9 @@ export default function InputElement({
|
||||
)
|
||||
return (
|
||||
<select
|
||||
id={name}
|
||||
id={props.name}
|
||||
className={styleClasses}
|
||||
{...props}
|
||||
disabled={disabled}
|
||||
multiple={multiple}
|
||||
>
|
||||
{field !== undefined && field.value === '' && <option value="" />}
|
||||
@ -77,39 +81,12 @@ export default function InputElement({
|
||||
)
|
||||
}
|
||||
case 'textarea':
|
||||
return (
|
||||
<textarea
|
||||
name={name}
|
||||
id={name}
|
||||
className={styles.textarea}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
return <textarea id={props.name} className={styles.textarea} {...props} />
|
||||
|
||||
case 'radio':
|
||||
case 'checkbox':
|
||||
return (
|
||||
<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>
|
||||
)
|
||||
return <InputRadio options={options} inputSize={size} {...props} />
|
||||
|
||||
case 'assetSelection':
|
||||
return (
|
||||
<AssetSelection
|
||||
@ -118,28 +95,27 @@ export default function InputElement({
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
case 'assetSelectionMultiple':
|
||||
return (
|
||||
<AssetSelection
|
||||
assets={options as unknown as AssetSelectionAsset[]}
|
||||
multiple
|
||||
disabled={disabled}
|
||||
{...field}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
case 'files':
|
||||
return <FilesInput name={name} {...field} {...props} />
|
||||
return <FilesInput {...field} {...props} />
|
||||
case 'providerUrl':
|
||||
return <CustomProvider name={name} {...field} {...props} />
|
||||
return <CustomProvider {...field} {...props} />
|
||||
case 'nft':
|
||||
return <Nft name={name} {...field} {...props} />
|
||||
return <Nft {...field} {...props} />
|
||||
case 'datatoken':
|
||||
return <Datatoken name={name} {...field} {...props} />
|
||||
return <Datatoken {...field} {...props} />
|
||||
case 'boxSelection':
|
||||
return (
|
||||
<BoxSelection
|
||||
name={name}
|
||||
options={options as unknown as BoxSelectionOption[]}
|
||||
{...field}
|
||||
{...props}
|
||||
@ -152,10 +128,8 @@ export default function InputElement({
|
||||
<div className={cx({ prefix: true, [size]: size })}>{prefix}</div>
|
||||
)}
|
||||
<DefaultInput
|
||||
name={name}
|
||||
type={type || 'text'}
|
||||
type={props.type || 'text'}
|
||||
size={size}
|
||||
disabled={disabled}
|
||||
{...field}
|
||||
{...props}
|
||||
/>
|
||||
@ -165,10 +139,8 @@ export default function InputElement({
|
||||
</div>
|
||||
) : (
|
||||
<DefaultInput
|
||||
name={name}
|
||||
type={type || 'text'}
|
||||
type={props.type || 'text'}
|
||||
size={size}
|
||||
disabled={disabled}
|
||||
{...field}
|
||||
{...props}
|
||||
/>
|
||||
|
79
src/components/@shared/FormInput/InputRadio.module.css
Normal file
79
src/components/@shared/FormInput/InputRadio.module.css
Normal 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;
|
||||
}
|
41
src/components/@shared/FormInput/InputRadio.tsx
Normal file
41
src/components/@shared/FormInput/InputRadio.tsx
Normal 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>
|
||||
)
|
||||
}
|
@ -264,6 +264,7 @@ export default function PoolTransactions({
|
||||
minimal ? transactions?.length >= 4 : transactions?.length >= 9
|
||||
}
|
||||
paginationPerPage={minimal ? 5 : 10}
|
||||
emptyMessage={chainIds.length === 0 ? 'No network selected' : null}
|
||||
/>
|
||||
) : (
|
||||
<div>Please connect your Web3 wallet.</div>
|
||||
|
20
src/components/@shared/atoms/Badge/index.stories.tsx
Normal file
20
src/components/@shared/atoms/Badge/index.stories.tsx
Normal 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'
|
||||
}
|
@ -1,16 +1,15 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import styles from './Badge.module.css'
|
||||
import styles from './index.module.css'
|
||||
import classNames from 'classnames/bind'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
export default function Badge({
|
||||
label,
|
||||
className
|
||||
}: {
|
||||
export interface BadgeProps {
|
||||
label: string
|
||||
className?: string
|
||||
}): ReactElement {
|
||||
}
|
||||
|
||||
export default function Badge({ label, className }: BadgeProps): ReactElement {
|
||||
const styleClasses = cx({
|
||||
badge: true,
|
||||
[className]: className
|
22
src/components/@shared/atoms/Blockies/index.stories.tsx
Normal file
22
src/components/@shared/atoms/Blockies/index.stories.tsx
Normal 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'
|
||||
}
|
@ -1,14 +1,16 @@
|
||||
import { toDataUrl } from 'myetherwallet-blockies'
|
||||
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({
|
||||
accountId,
|
||||
className
|
||||
}: {
|
||||
accountId: string
|
||||
className?: string
|
||||
}): ReactElement {
|
||||
}: BlockiesProps): ReactElement {
|
||||
if (!accountId) return null
|
||||
const blockies = toDataUrl(accountId)
|
||||
|
43
src/components/@shared/atoms/Container/index.stories.tsx
Normal file
43
src/components/@shared/atoms/Container/index.stories.tsx
Normal 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
|
||||
}
|
@ -1,18 +1,20 @@
|
||||
import React, { ReactElement, ReactNode } from 'react'
|
||||
import classNames from 'classnames/bind'
|
||||
import styles from './Container.module.css'
|
||||
import styles from './index.module.css'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
export interface ContainerProps {
|
||||
children: ReactNode
|
||||
narrow?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
export default function Container({
|
||||
children,
|
||||
narrow,
|
||||
className
|
||||
}: {
|
||||
children: ReactNode
|
||||
narrow?: boolean
|
||||
className?: string
|
||||
}): ReactElement {
|
||||
}: ContainerProps): ReactElement {
|
||||
const styleClasses = cx({
|
||||
container: true,
|
||||
narrow,
|
15
src/components/@shared/atoms/Copy/index.stories.tsx
Normal file
15
src/components/@shared/atoms/Copy/index.stories.tsx
Normal 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.'
|
||||
}
|
27
src/components/@shared/atoms/Copy/index.test.tsx
Normal file
27
src/components/@shared/atoms/Copy/index.test.tsx
Normal 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')
|
||||
})
|
||||
})
|
@ -1,9 +1,13 @@
|
||||
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 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)
|
||||
|
||||
// Clear copy success style after 5 sec.
|
45
src/components/@shared/atoms/Lists/index.stories.tsx
Normal file
45
src/components/@shared/atoms/Lists/index.stories.tsx
Normal 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>
|
||||
)
|
||||
]
|
@ -1,13 +1,12 @@
|
||||
import React, { ReactElement, ReactNode } from 'react'
|
||||
import styles from './Lists.module.css'
|
||||
import styles from './index.module.css'
|
||||
|
||||
export function ListItem({
|
||||
children,
|
||||
ol
|
||||
}: {
|
||||
children: ReactNode
|
||||
export interface ListItemProps {
|
||||
children?: ReactNode
|
||||
ol?: boolean
|
||||
}): ReactElement {
|
||||
}
|
||||
|
||||
export function ListItem({ children, ol }: ListItemProps): ReactElement {
|
||||
const classes = ol
|
||||
? `${styles.item} ${styles.olItem}`
|
||||
: `${styles.item} ${styles.ulItem}`
|
23
src/components/@shared/atoms/Loader/index.stories.tsx
Normal file
23
src/components/@shared/atoms/Loader/index.stories.tsx
Normal 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...'
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import styles from './Loader.module.css'
|
||||
import styles from './index.module.css'
|
||||
|
||||
export default function Loader({
|
||||
message
|
||||
}: {
|
||||
export interface LoaderProps {
|
||||
message?: string
|
||||
}): ReactElement {
|
||||
}
|
||||
|
||||
export default function Loader({ message }: LoaderProps): ReactElement {
|
||||
return (
|
||||
<div className={styles.loaderWrap}>
|
||||
<span className={styles.loader} />
|
@ -14,7 +14,10 @@ interface Props {
|
||||
args: LogoProps
|
||||
}
|
||||
|
||||
export const Primary: Props = Template.bind({})
|
||||
Primary.args = {
|
||||
export const Default: Props = Template.bind({})
|
||||
Default.args = {}
|
||||
|
||||
export const WithoutWordmark: Props = Template.bind({})
|
||||
WithoutWordmark.args = {
|
||||
noWordmark: true
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
}
|
||||
|
||||
.modal {
|
||||
composes: box from './Box.module.css';
|
||||
composes: box from '../Box.module.css';
|
||||
padding: var(--spacer);
|
||||
margin: var(--spacer) auto;
|
||||
max-width: var(--break-point--small);
|
32
src/components/@shared/atoms/Modal/index.stories.tsx
Normal file
32
src/components/@shared/atoms/Modal/index.stories.tsx
Normal 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({})
|
@ -1,6 +1,6 @@
|
||||
import React, { ReactElement, ReactNode } from 'react'
|
||||
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')
|
||||
|
28
src/components/@shared/atoms/Status/index.stories.tsx
Normal file
28
src/components/@shared/atoms/Status/index.stories.tsx
Normal 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'
|
||||
}
|
@ -1,16 +1,18 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
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)
|
||||
|
||||
export default function Status({
|
||||
state,
|
||||
className
|
||||
}: {
|
||||
state?: string
|
||||
className?: string
|
||||
}): ReactElement {
|
||||
}: StatusProps): ReactElement {
|
||||
const styleClasses = cx({
|
||||
status: true,
|
||||
warning: state === 'warning',
|
82
src/components/@shared/atoms/Table/index.stories.tsx
Normal file
82
src/components/@shared/atoms/Table/index.stories.tsx
Normal 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: []
|
||||
}
|
@ -1,11 +1,10 @@
|
||||
import React, { ReactElement, ReactNode } from 'react'
|
||||
import DataTable, { IDataTableProps } from 'react-data-table-component'
|
||||
import Loader from './Loader'
|
||||
import Loader from '../Loader'
|
||||
import Pagination from '@shared/Pagination'
|
||||
import styles from './Table.module.css'
|
||||
import { useUserPreferences } from '@context/UserPreferences'
|
||||
import styles from './index.module.css'
|
||||
|
||||
interface TableProps extends IDataTableProps {
|
||||
export interface TableProps extends IDataTableProps {
|
||||
isLoading?: boolean
|
||||
emptyMessage?: string
|
||||
sortField?: string
|
||||
@ -14,14 +13,7 @@ interface TableProps extends IDataTableProps {
|
||||
}
|
||||
|
||||
function Empty({ message }: { message?: string }): ReactElement {
|
||||
const { chainIds } = useUserPreferences()
|
||||
return (
|
||||
<div className={styles.empty}>
|
||||
{chainIds.length === 0
|
||||
? 'No network selected'
|
||||
: message || 'No results found'}
|
||||
</div>
|
||||
)
|
||||
return <div className={styles.empty}>{message || 'No results found'}</div>
|
||||
}
|
||||
|
||||
export default function Table({
|
@ -8,11 +8,10 @@
|
||||
|
||||
.tab {
|
||||
display: inline-block;
|
||||
padding: calc(var(--spacer) / 12) var(--spacer);
|
||||
padding: calc(var(--spacer) / 8) var(--spacer);
|
||||
font-weight: var(--font-weight-bold);
|
||||
font-size: var(--font-size-small);
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
color: var(--color-secondary);
|
||||
background-color: var(--background-body);
|
||||
border: 1px solid var(--border-color);
|
||||
@ -20,6 +19,11 @@
|
||||
min-width: 90px;
|
||||
}
|
||||
|
||||
.tab,
|
||||
.tab label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tab:first-child {
|
||||
border-top-left-radius: var(--border-radius);
|
||||
border-bottom-left-radius: var(--border-radius);
|
||||
@ -53,3 +57,7 @@
|
||||
padding: var(--spacer);
|
||||
}
|
||||
}
|
||||
|
||||
.radio {
|
||||
composes: radio from '../../FormInput/InputRadio.module.css';
|
||||
}
|
52
src/components/@shared/atoms/Tabs/index.stories.tsx
Normal file
52
src/components/@shared/atoms/Tabs/index.stories.tsx
Normal 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])
|
||||
}
|
21
src/components/@shared/atoms/Tabs/index.test.tsx
Normal file
21
src/components/@shared/atoms/Tabs/index.test.tsx
Normal 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)
|
||||
})
|
||||
})
|
@ -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 InputElement from '@shared/FormInput/InputElement'
|
||||
import styles from './Tabs.module.css'
|
||||
import styles from './index.module.css'
|
||||
import InputRadio from '@shared/FormInput/InputRadio'
|
||||
|
||||
export interface TabsItem {
|
||||
title: string
|
||||
@ -9,37 +9,36 @@ export interface TabsItem {
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export interface TabsProps {
|
||||
items: TabsItem[]
|
||||
className?: string
|
||||
handleTabChange?: (tabName: string) => void
|
||||
defaultIndex?: number
|
||||
showRadio?: boolean
|
||||
}
|
||||
|
||||
export default function Tabs({
|
||||
items,
|
||||
className,
|
||||
handleTabChange,
|
||||
defaultIndex,
|
||||
showRadio
|
||||
}: {
|
||||
items: TabsItem[]
|
||||
className?: string
|
||||
handleTabChange?: (tabName: string) => void
|
||||
defaultIndex?: number
|
||||
showRadio?: boolean
|
||||
}): ReactElement {
|
||||
}: TabsProps): ReactElement {
|
||||
return (
|
||||
<ReactTabs
|
||||
className={`${className && className}`}
|
||||
defaultIndex={defaultIndex}
|
||||
>
|
||||
<ReactTabs className={`${className || ''}`} defaultIndex={defaultIndex}>
|
||||
<TabList className={styles.tabList}>
|
||||
{items.map((item, index) => (
|
||||
<Tab
|
||||
className={styles.tab}
|
||||
key={item.title}
|
||||
key={index}
|
||||
onClick={handleTabChange ? () => handleTabChange(item.title) : null}
|
||||
disabled={item.disabled}
|
||||
>
|
||||
{showRadio ? (
|
||||
<InputElement
|
||||
<InputRadio
|
||||
name={item.title}
|
||||
type="radio"
|
||||
checked={defaultIndex === index}
|
||||
checked={index === defaultIndex}
|
||||
options={[item.title]}
|
||||
readOnly
|
||||
/>
|
||||
@ -50,8 +49,8 @@ export default function Tabs({
|
||||
))}
|
||||
</TabList>
|
||||
<div className={styles.tabContent}>
|
||||
{items.map((item) => (
|
||||
<TabPanel key={item.title}>{item.content}</TabPanel>
|
||||
{items.map((item, index) => (
|
||||
<TabPanel key={index}>{item.content}</TabPanel>
|
||||
))}
|
||||
</div>
|
||||
</ReactTabs>
|
40
src/components/@shared/atoms/Tags/index.stories.tsx
Normal file
40
src/components/@shared/atoms/Tags/index.stories.tsx
Normal 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
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
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[]
|
||||
max?: number
|
||||
showMore?: boolean
|
37
src/components/@shared/atoms/Time/index.stories.tsx
Normal file
37
src/components/@shared/atoms/Time/index.stories.tsx
Normal 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
|
||||
}
|
@ -1,19 +1,21 @@
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import { format, formatDistance } from 'date-fns'
|
||||
|
||||
export interface TimeProps {
|
||||
date: string
|
||||
relative?: boolean
|
||||
isUnix?: boolean
|
||||
displayFormat?: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
export default function Time({
|
||||
date,
|
||||
relative,
|
||||
isUnix,
|
||||
displayFormat,
|
||||
className
|
||||
}: {
|
||||
date: string
|
||||
relative?: boolean
|
||||
isUnix?: boolean
|
||||
displayFormat?: string
|
||||
className?: string
|
||||
}): ReactElement {
|
||||
}: TimeProps): ReactElement {
|
||||
const [dateIso, setDateIso] = useState<string>()
|
||||
const [dateNew, setDateNew] = useState<Date>()
|
||||
|
@ -3,7 +3,7 @@
|
||||
}
|
||||
|
||||
.content {
|
||||
composes: box from './Box.module.css';
|
||||
composes: box from '../Box.module.css';
|
||||
padding: calc(var(--spacer) / 4);
|
||||
max-width: 25rem;
|
||||
font-size: var(--font-size-small);
|
51
src/components/@shared/atoms/Tooltip/index.stories.tsx
Normal file
51
src/components/@shared/atoms/Tooltip/index.stories.tsx
Normal 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
|
||||
}
|
@ -1,14 +1,8 @@
|
||||
import React, { ReactElement, ReactNode } from 'react'
|
||||
import classNames from 'classnames/bind'
|
||||
import loadable from '@loadable/component'
|
||||
import React, { ReactElement } from 'react'
|
||||
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 { Placement } from 'tippy.js'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
const Tippy = loadable(() => import('@tippyjs/react/headless'))
|
||||
import Tippy, { TippyProps } from '@tippyjs/react/headless'
|
||||
|
||||
const animation = {
|
||||
config: { tension: 400, friction: 20 },
|
||||
@ -19,28 +13,15 @@ const animation = {
|
||||
// Forward ref for Tippy.js
|
||||
// eslint-disable-next-line
|
||||
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({
|
||||
content,
|
||||
children,
|
||||
trigger,
|
||||
disabled,
|
||||
className,
|
||||
placement
|
||||
}: {
|
||||
content: ReactNode
|
||||
children?: ReactNode
|
||||
trigger?: string
|
||||
disabled?: boolean
|
||||
className?: string
|
||||
placement?: Placement
|
||||
}): ReactElement {
|
||||
const [props, setSpring] = useSpring(() => animation.from)
|
||||
export default function Tooltip(props: TippyProps): ReactElement {
|
||||
const { content, children, trigger, disabled, className, placement } = props
|
||||
const [styles, api] = useSpring(() => animation.from)
|
||||
|
||||
function onMount() {
|
||||
setSpring({
|
||||
api.start({
|
||||
...animation.to,
|
||||
onRest: (): void => null,
|
||||
config: animation.config
|
||||
@ -48,17 +29,14 @@ export default function Tooltip({
|
||||
}
|
||||
|
||||
function onHide({ unmount }: { unmount: () => void }) {
|
||||
setSpring({
|
||||
api.start({
|
||||
...animation.from,
|
||||
onRest: unmount,
|
||||
config: { ...animation.config, clamp: true }
|
||||
})
|
||||
}
|
||||
|
||||
const styleClasses = cx({
|
||||
tooltip: true,
|
||||
[className]: className
|
||||
})
|
||||
const styleClasses = `${stylesTooltip.tooltip} ${className || ''}`
|
||||
|
||||
return (
|
||||
<Tippy
|
||||
@ -68,23 +46,21 @@ export default function Tooltip({
|
||||
trigger={trigger || 'mouseenter focus'}
|
||||
disabled={disabled || null}
|
||||
placement={placement || 'auto'}
|
||||
render={(attrs: any) => (
|
||||
<animated.div style={props}>
|
||||
<div className={styles.content} {...attrs}>
|
||||
render={(attrs) => (
|
||||
<animated.div style={styles}>
|
||||
<div className={stylesTooltip.content} {...attrs}>
|
||||
{content}
|
||||
<div className={styles.arrow} data-popper-arrow />
|
||||
<div className={stylesTooltip.arrow} data-popper-arrow />
|
||||
</div>
|
||||
</animated.div>
|
||||
)}
|
||||
appendTo={
|
||||
typeof document !== 'undefined' && document.querySelector('body')
|
||||
}
|
||||
animation
|
||||
onMount={onMount}
|
||||
onHide={onHide}
|
||||
fallback={
|
||||
<div className={styleClasses}>{children || <DefaultTrigger />}</div>
|
||||
}
|
||||
// animation
|
||||
{...props}
|
||||
>
|
||||
<div className={styleClasses}>{children || <DefaultTrigger />}</div>
|
||||
</Tippy>
|
@ -1,5 +1,5 @@
|
||||
.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);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
@ -9,14 +9,14 @@
|
||||
}
|
||||
|
||||
.input {
|
||||
composes: checkbox from '@shared/FormInput/InputElement.module.css';
|
||||
composes: checkbox from '@shared/FormInput/InputRadio.module.css';
|
||||
vertical-align: baseline;
|
||||
margin-right: calc(var(--spacer) / 3);
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.radioLabel {
|
||||
composes: radioLabel from '@shared/FormInput/InputElement.module.css';
|
||||
composes: radioLabel from '@shared/FormInput/InputRadio.module.css';
|
||||
font-weight: var(--font-weight-base);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -46,6 +46,7 @@ export default function Networks(): ReactElement {
|
||||
trigger="click focus"
|
||||
className={`${stylesIndex.preferences} ${styles.networks}`}
|
||||
>
|
||||
<>
|
||||
<Network aria-label="Networks" className={stylesIndex.icon} />
|
||||
<Caret aria-hidden="true" className={stylesIndex.caret} />
|
||||
|
||||
@ -54,6 +55,7 @@ export default function Networks(): ReactElement {
|
||||
<span className={styles.chainsSelectedIndicator} key={chainId} />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
@ -28,8 +28,10 @@ export default function UserPreferences(): ReactElement {
|
||||
trigger="click focus"
|
||||
className={styles.preferences}
|
||||
>
|
||||
<>
|
||||
<Cog aria-label="Preferences" className={styles.icon} />
|
||||
<Caret aria-hidden="true" className={styles.caret} />
|
||||
</>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ const columns = [
|
||||
selector: function getAssetRow(row: AssetExtended) {
|
||||
return (
|
||||
<Tooltip content={row.datatokens[0].name}>
|
||||
{row.datatokens[0].symbol}
|
||||
<>{row.datatokens[0].symbol}</>
|
||||
</Tooltip>
|
||||
)
|
||||
},
|
||||
@ -96,7 +96,11 @@ export default function Bookmarks(): ReactElement {
|
||||
columns={columns}
|
||||
data={pinned}
|
||||
isLoading={isLoading}
|
||||
emptyMessage="Your bookmarks will appear here."
|
||||
emptyMessage={
|
||||
chainIds.length === 0
|
||||
? 'No network selected'
|
||||
: 'Your bookmarks will appear here.'
|
||||
}
|
||||
noTableHead
|
||||
/>
|
||||
)
|
||||
|
@ -123,6 +123,7 @@ export default function ComputeJobs({
|
||||
isLoading={isLoading}
|
||||
defaultSortField="row.dateCreated"
|
||||
defaultSortAsc={false}
|
||||
emptyMessage={chainIds.length === 0 ? 'No network selected' : null}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
|
@ -4,7 +4,7 @@ import Time from '@shared/atoms/Time'
|
||||
import AssetTitle from '@shared/AssetList/AssetListTitle'
|
||||
import NetworkName from '@shared/NetworkName'
|
||||
import { useProfile } from '@context/Profile'
|
||||
|
||||
import { useUserPreferences } from '@context/UserPreferences'
|
||||
const columns = [
|
||||
{
|
||||
name: 'Data Set',
|
||||
@ -38,6 +38,7 @@ export default function ComputeDownloads({
|
||||
accountId: string
|
||||
}): ReactElement {
|
||||
const { downloads, isDownloadsLoading } = useProfile()
|
||||
const { chainIds } = useUserPreferences()
|
||||
|
||||
return accountId ? (
|
||||
<Table
|
||||
@ -45,6 +46,7 @@ export default function ComputeDownloads({
|
||||
data={downloads}
|
||||
paginationPerPage={10}
|
||||
isLoading={isDownloadsLoading}
|
||||
emptyMessage={chainIds.length === 0 ? 'No network selected' : null}
|
||||
/>
|
||||
) : (
|
||||
<div>Please connect your Web3 wallet.</div>
|
||||
|
@ -109,6 +109,7 @@ export default function PoolShares({
|
||||
isLoading={loading}
|
||||
sortField="userLiquidity"
|
||||
sortAsc={false}
|
||||
emptyMessage={chainIds.length === 0 ? 'No network selected' : null}
|
||||
/>
|
||||
) : (
|
||||
<div>Please connect your Web3 wallet.</div>
|
||||
|
Loading…
Reference in New Issue
Block a user