1
0
mirror of https://github.com/oceanprotocol/market.git synced 2024-12-02 05:57:29 +01:00

Merge pull request #48 from oceanprotocol/feature/tooltip

Tooltips
This commit is contained in:
Matthias Kretschmann 2020-08-06 14:28:43 +02:00 committed by GitHub
commit 53d35828e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 213 additions and 128 deletions

View File

@ -17,7 +17,7 @@ const appConfig = {
network, network,
infuraProjectId: process.env.GATSBY_INFURA_PROJECT_ID || 'xxx', infuraProjectId: process.env.GATSBY_INFURA_PROJECT_ID || 'xxx',
marketFeeAddress: process.env.GATSBY_MARKET_FEE_ADDRESS || '0xxxx', marketFeeAddress: process.env.GATSBY_MARKET_FEE_ADDRESS || '0xxxx',
marketFeeAmount: process.env.GATSBY_MARKET_FEE_AMOUNT || '0.03' // in % marketFeeAmount: process.env.GATSBY_MARKET_FEE_AMOUNT || '0.1' // in %
} }
module.exports = { module.exports = {

View File

@ -89,5 +89,19 @@
} }
], ],
"success": "Asset Created!" "success": "Asset Created!"
},
"price": {
"simple": {
"title": "Simple: Fixed",
"info": "Set your price for accessing this data set. A Data Token contract for this data set, worth the entered amount of OCEAN will be created."
},
"advanced": {
"title": "Advanced: Dynamic",
"info": "Let's create a decentralized, automated market for your data set. A Data Token contract for this data set worth the entered amount of OCEAN will be created. Additionally, you will provide liquidity into a Data Token/OCEAN liquidity pool with Balancer.",
"tooltips": {
"poolInfo": "Help me",
"liquidityProviderFee": "Help me"
}
}
} }
} }

View File

@ -1,4 +1,4 @@
import React from 'react' import React, { ReactElement, ReactNode } from 'react'
import styles from './Label.module.css' import styles from './Label.module.css'
const Label = ({ const Label = ({
@ -7,9 +7,9 @@ const Label = ({
...props ...props
}: { }: {
required?: boolean required?: boolean
children: string children: ReactNode
htmlFor: string htmlFor: string
}) => ( }): ReactElement => (
<label <label
className={`${styles.label} ${required && styles.required}`} className={`${styles.label} ${required && styles.required}`}
title={required ? 'Required' : ''} title={required ? 'Required' : ''}

View File

@ -1,4 +1,4 @@
import React from 'react' import React, { ReactElement } from 'react'
import ReactMarkdown from 'react-markdown' import ReactMarkdown from 'react-markdown'
const Markdown = ({ const Markdown = ({
@ -7,7 +7,7 @@ const Markdown = ({
}: { }: {
text: string text: string
className?: string className?: string
}) => { }): ReactElement => {
// fix react-markdown \n transformation // fix react-markdown \n transformation
// https://github.com/rexxars/react-markdown/issues/105#issuecomment-351585313 // https://github.com/rexxars/react-markdown/issues/105#issuecomment-351585313
const textCleaned = text.replace(/\\n/g, '\n ') const textCleaned = text.replace(/\\n/g, '\n ')

View File

@ -0,0 +1,51 @@
.tooltip {
display: inline-block;
}
.content {
composes: box from './Box.module.css';
padding: calc(var(--spacer) / 4);
min-width: 20rem;
max-width: 25rem;
font-size: var(--font-size-small);
}
.icon {
width: 15px;
height: 15px;
cursor: help;
display: inline-block;
margin-bottom: -0.1rem;
margin-left: calc(var(--spacer) / 6);
fill: var(--brand-grey-light);
}
.arrow,
.arrow::before {
position: absolute;
width: 1.2rem;
height: 1.2rem;
z-index: -1;
}
.arrow::before {
content: '';
transform: rotate(45deg);
background: var(--brand-grey-lighter);
}
.content[data-placement*='top'] > .arrow {
bottom: -4px;
}
.content[data-placement*='bottom'] > .arrow {
top: -4px;
}
.content[data-placement*='left'] > .arrow {
right: -4px;
}
.content[data-placement*='right'] > .arrow {
left: -4px;
}

View File

@ -1,24 +1,78 @@
import React, { ReactElement, forwardRef } from 'react' import React, { ReactElement, ReactNode } from 'react'
import Tippy from '@tippyjs/react' import loadable from '@loadable/component'
import { useSpring, animated } from 'react-spring'
import styles from './Tooltip.module.css'
import { ReactComponent as Info } from '../../images/info.svg'
export default function Tooltip({ const Tippy = loadable(() => import('@tippyjs/react/headless'))
content,
children const animation = {
}: { config: { tension: 400, friction: 20 },
content: string from: { transform: 'scale(0.5) translateY(-3rem)' },
children: ReactElement to: { transform: 'scale(1) translateY(0)' }
}) {
return (
<Tippy content={content}>
<CustomWrapper>{children}</CustomWrapper>
</Tippy>
)
} }
// Forward ref for Tippy.js // Forward ref for Tippy.js
// eslint-disable-next-line // eslint-disable-next-line
const CustomWrapper = forwardRef( const DefaultTrigger = React.forwardRef((props, ref: any) => {
({ children }: { children: ReactElement }, ref: any) => { return <Info className={styles.icon} ref={ref} />
return <div ref={ref}>{children}</div> })
export default function Tooltip({
content,
children,
trigger,
disabled
}: {
content: ReactNode
children?: ReactNode
trigger?: string
disabled?: boolean
}): ReactElement {
const [props, setSpring] = useSpring(() => animation.from)
function onMount() {
setSpring({
transform: 'scale(1) translateY(0)',
onRest: (): void => null,
config: animation.config
})
} }
)
function onHide({ unmount }: { unmount: any }) {
setSpring({
...animation.from,
onRest: unmount,
config: { ...animation.config, clamp: true }
})
}
return (
<Tippy
interactive
interactiveBorder={5}
zIndex={1}
trigger={trigger || 'mouseenter focus'}
disabled={disabled || null}
render={(attrs: any) => (
<animated.div style={props}>
<div className={styles.content} {...attrs}>
{content}
<div className={styles.arrow} data-popper-arrow />
</div>
</animated.div>
)}
appendTo={
typeof document !== 'undefined' && document.querySelector('body')
}
animation
onMount={onMount}
onHide={onHide}
fallback={
<div className={styles.tooltip}>{children || <DefaultTrigger />}</div>
}
>
<div className={styles.tooltip}>{children || <DefaultTrigger />}</div>
</Tippy>
)
}

View File

@ -1,4 +1,5 @@
import React, { ReactElement, useState, ChangeEvent, useEffect } from 'react' import React, { ReactElement, useState, ChangeEvent, useEffect } from 'react'
import { graphql, useStaticQuery } from 'gatsby'
import stylesIndex from './index.module.css' import stylesIndex from './index.module.css'
import styles from './Advanced.module.css' import styles from './Advanced.module.css'
import FormHelp from '../../../atoms/Input/Help' import FormHelp from '../../../atoms/Input/Help'
@ -10,19 +11,22 @@ import { isCorrectNetwork } from '../../../../utils/wallet'
import { useSiteMetadata } from '../../../../hooks/useSiteMetadata' import { useSiteMetadata } from '../../../../hooks/useSiteMetadata'
import InputElement from '../../../atoms/Input/InputElement' import InputElement from '../../../atoms/Input/InputElement'
import Label from '../../../atoms/Input/Label' import Label from '../../../atoms/Input/Label'
import Tooltip from '../../../atoms/Tooltip'
export default function Advanced({ export default function Advanced({
ocean, ocean,
tokensToMint, tokensToMint,
weightOnDataToken, weightOnDataToken,
liquidityProviderFee, liquidityProviderFee,
onOceanChange onOceanChange,
content
}: { }: {
ocean: string ocean: string
tokensToMint: number tokensToMint: number
weightOnDataToken: string weightOnDataToken: string
liquidityProviderFee: string liquidityProviderFee: string
onOceanChange: (event: ChangeEvent<HTMLInputElement>) => void onOceanChange: (event: ChangeEvent<HTMLInputElement>) => void
content: any
}): ReactElement { }): ReactElement {
const { appConfig } = useSiteMetadata() const { appConfig } = useSiteMetadata()
const { account, balance, chainId, refreshBalance } = useOcean() const { account, balance, chainId, refreshBalance } = useOcean()
@ -61,10 +65,7 @@ export default function Advanced({
return ( return (
<div className={stylesIndex.content}> <div className={stylesIndex.content}>
<div className={styles.advanced}> <div className={styles.advanced}>
<FormHelp className={stylesIndex.help}> <FormHelp className={stylesIndex.help}>{content.info}</FormHelp>
{`Let's create a decentralized, automated market for your data set. A Data Token contract for this data set worth the entered amount of OCEAN will be created. Additionally, you will provide liquidity into a Data Token/OCEAN
liquidity pool with Balancer.`}
</FormHelp>
<aside className={styles.wallet}> <aside className={styles.wallet}>
{balance && balance.ocean && ( {balance && balance.ocean && (
@ -75,7 +76,10 @@ export default function Advanced({
<Wallet /> <Wallet />
</aside> </aside>
<h4 className={styles.title}>Data Token Liquidity Pool</h4> <h4 className={styles.title}>
Data Token Liquidity Pool{' '}
<Tooltip content={content.tooltips.poolInfo} />
</h4>
<div className={styles.tokens}> <div className={styles.tokens}>
<Coin <Coin
@ -95,7 +99,10 @@ export default function Advanced({
</div> </div>
<footer className={styles.summary}> <footer className={styles.summary}>
<Label htmlFor="liquidityProviderFee">Liquidity Provider Fee</Label> <Label htmlFor="liquidityProviderFee">
Liquidity Provider Fee{' '}
<Tooltip content={content.tooltips.liquidityProviderFee} />
</Label>
<InputElement <InputElement
value={liquidityProviderFee} value={liquidityProviderFee}
name="liquidityProviderFee" name="liquidityProviderFee"

View File

@ -8,18 +8,17 @@ import Conversion from '../../../atoms/Price/Conversion'
export default function Simple({ export default function Simple({
ocean, ocean,
onChange onChange,
content
}: { }: {
ocean: string ocean: string
onChange: (event: ChangeEvent<HTMLInputElement>) => void onChange: (event: ChangeEvent<HTMLInputElement>) => void
content: any
}): ReactElement { }): ReactElement {
return ( return (
<div className={stylesIndex.content}> <div className={stylesIndex.content}>
<div className={styles.simple}> <div className={styles.simple}>
<FormHelp className={stylesIndex.help}> <FormHelp className={stylesIndex.help}>{content.info}</FormHelp>
Set your price for accessing this data set. A Data Token contract for
this data set, worth the entered amount of OCEAN will be created.
</FormHelp>
<div className={styles.form}> <div className={styles.form}>
<Label htmlFor="ocean">Ocean Tokens</Label> <Label htmlFor="ocean">Ocean Tokens</Label>

View File

@ -1,4 +1,5 @@
import React, { ReactElement, useState, ChangeEvent, useEffect } from 'react' import React, { ReactElement, useState, ChangeEvent, useEffect } from 'react'
import { graphql, useStaticQuery } from 'gatsby'
import { InputProps } from '../../../atoms/Input' import { InputProps } from '../../../atoms/Input'
import styles from './index.module.css' import styles from './index.module.css'
import Tabs from '../../../atoms/Tabs' import Tabs from '../../../atoms/Tabs'
@ -6,7 +7,37 @@ import Simple from './Simple'
import Advanced from './Advanced' import Advanced from './Advanced'
import { useField } from 'formik' import { useField } from 'formik'
const query = graphql`
query PriceFieldQuery {
content: allFile(filter: { relativePath: { eq: "pages/publish.json" } }) {
edges {
node {
childPagesJson {
price {
simple {
title
info
}
advanced {
title
info
tooltips {
poolInfo
liquidityProviderFee
}
}
}
}
}
}
}
}
`
export default function Price(props: InputProps): ReactElement { export default function Price(props: InputProps): ReactElement {
const data = useStaticQuery(query)
const content = data.content.edges[0].node.childPagesJson.price
const [field, meta, helpers] = useField(props) const [field, meta, helpers] = useField(props)
const { weightOnDataToken, liquidityProviderFee } = field.value const { weightOnDataToken, liquidityProviderFee } = field.value
@ -31,11 +62,17 @@ export default function Price(props: InputProps): ReactElement {
const tabs = [ const tabs = [
{ {
title: 'Simple: Fixed', title: content.simple.title,
content: <Simple ocean={ocean} onChange={handleOceanChange} /> content: (
<Simple
ocean={ocean}
onChange={handleOceanChange}
content={content.simple}
/>
)
}, },
{ {
title: 'Advanced: Dynamic', title: content.advanced.title,
content: ( content: (
<Advanced <Advanced
ocean={ocean} ocean={ocean}
@ -43,6 +80,7 @@ export default function Price(props: InputProps): ReactElement {
weightOnDataToken={weightOnDataToken} weightOnDataToken={weightOnDataToken}
onOceanChange={handleOceanChange} onOceanChange={handleOceanChange}
liquidityProviderFee={liquidityProviderFee} liquidityProviderFee={liquidityProviderFee}
content={content.advanced}
/> />
) )
} }

View File

@ -1,8 +1,5 @@
.details { .details {
composes: box from '../../atoms/Box.module.css'; padding: calc(var(--spacer) / 4);
padding: calc(var(--spacer) / 2) !important;
min-width: 20rem;
max-width: 25rem;
} }
.balance { .balance {
@ -31,33 +28,3 @@
font-size: var(--font-size-small); font-size: var(--font-size-small);
color: var(--color-secondary); color: var(--color-secondary);
} }
.arrow,
.arrow::before {
position: absolute;
width: 1.2rem;
height: 1.2rem;
z-index: -1;
}
.arrow::before {
content: '';
transform: rotate(45deg);
background: var(--brand-grey-lighter);
}
.details[data-placement*='top'] > .arrow {
bottom: -4px;
}
.details[data-placement*='bottom'] > .arrow {
top: -4px;
}
.details[data-placement*='left'] > .arrow {
right: -4px;
}
.details[data-placement*='right'] > .arrow {
left: -4px;
}

View File

@ -8,11 +8,11 @@ import { getInjectedProviderName } from 'web3modal'
import Conversion from '../../atoms/Price/Conversion' import Conversion from '../../atoms/Price/Conversion'
import { formatCurrency } from '@coingecko/cryptoformat' import { formatCurrency } from '@coingecko/cryptoformat'
export default function Details({ attrs }: { attrs: any }): ReactElement { export default function Details(): ReactElement {
const { balance, connect, logout, chainId } = useOcean() const { balance, connect, logout, chainId } = useOcean()
return ( return (
<div className={styles.details} {...attrs}> <div className={styles.details}>
<ul> <ul>
{Object.entries(balance).map(([key, value]) => ( {Object.entries(balance).map(([key, value]) => (
<li className={styles.balance} key={key}> <li className={styles.balance} key={key}>
@ -41,7 +41,6 @@ export default function Details({ attrs }: { attrs: any }): ReactElement {
</li> </li>
</ul> </ul>
<Web3Feedback /> <Web3Feedback />
<div className={styles.arrow} data-popper-arrow />
</div> </div>
) )
} }

View File

@ -1,62 +1,15 @@
import React, { ReactElement } from 'react' import React, { ReactElement } from 'react'
import loadable from '@loadable/component'
import { useSpring, animated } from 'react-spring'
import Account from './Account' import Account from './Account'
import Details from './Details' import Details from './Details'
import Tooltip from '../../atoms/Tooltip'
import { useOcean } from '@oceanprotocol/react' import { useOcean } from '@oceanprotocol/react'
const Tippy = loadable(() => import('@tippyjs/react/headless'))
const animation = {
config: { tension: 400, friction: 20 },
from: { transform: 'scale(0.5) translateY(-3rem)' },
to: { transform: 'scale(1) translateY(0)' }
}
export default function Wallet(): ReactElement { export default function Wallet(): ReactElement {
const { accountId, refreshBalance } = useOcean() const { accountId } = useOcean()
const [props, setSpring] = useSpring(() => animation.from)
// always refetch balance when popover is opened
function onMount() {
accountId && refreshBalance()
setSpring({
transform: 'scale(1) translateY(0)',
onRest: (): void => null,
config: animation.config
})
}
function onHide({ unmount }: { unmount: any }) {
setSpring({
...animation.from,
onRest: unmount,
config: { ...animation.config, clamp: true }
})
}
return ( return (
<Tippy <Tooltip content={<Details />} trigger="click focus" disabled={!accountId}>
interactive
interactiveBorder={30}
trigger="click focus"
zIndex={1}
render={(attrs: any) => (
<animated.div style={props}>
<Details attrs={attrs} />
</animated.div>
)}
appendTo={
typeof document !== 'undefined' && document.querySelector('body')
}
animation
onMount={onMount}
onHide={onHide}
disabled={!accountId}
fallback={<Account />}
>
<Account /> <Account />
</Tippy> </Tooltip>
) )
} }

3
src/images/info.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 1.53846C5.32682 1.53846 1.53846 5.32682 1.53846 10C1.53846 14.6732 5.32682 18.4615 10 18.4615C14.6732 18.4615 18.4615 14.6732 18.4615 10C18.4615 5.32682 14.6732 1.53846 10 1.53846ZM0 10C0 4.47715 4.47715 0 10 0C15.5228 0 20 4.47715 20 10C20 15.5228 15.5228 20 10 20C4.47715 20 0 15.5228 0 10ZM10 8.46154C10.4248 8.46154 10.7692 8.80593 10.7692 9.23077V15.3846C10.7692 15.8094 10.4248 16.1538 10 16.1538C9.57517 16.1538 9.23077 15.8094 9.23077 15.3846V9.23077C9.23077 8.80593 9.57517 8.46154 10 8.46154ZM10 4.61538C9.36275 4.61538 8.84615 5.13198 8.84615 5.76923C8.84615 6.40648 9.36275 6.92308 10 6.92308C10.6373 6.92308 11.1538 6.40648 11.1538 5.76923C11.1538 5.13198 10.6373 4.61538 10 4.61538Z"/>
</svg>

After

Width:  |  Height:  |  Size: 844 B

View File

@ -35,7 +35,7 @@ export const initialValues: MetadataPublishForm = {
type: 'simple', type: 'simple',
tokensToMint: 1, tokensToMint: 1,
weightOnDataToken: '9', // 90% on data token weightOnDataToken: '9', // 90% on data token
liquidityProviderFee: '0.3' // in % liquidityProviderFee: '0.1' // in %
}, },
files: undefined, files: undefined,
description: undefined, description: undefined,