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,
infuraProjectId: process.env.GATSBY_INFURA_PROJECT_ID || 'xxx',
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 = {

View File

@ -89,5 +89,19 @@
}
],
"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'
const Label = ({
@ -7,9 +7,9 @@ const Label = ({
...props
}: {
required?: boolean
children: string
children: ReactNode
htmlFor: string
}) => (
}): ReactElement => (
<label
className={`${styles.label} ${required && styles.required}`}
title={required ? 'Required' : ''}

View File

@ -1,4 +1,4 @@
import React from 'react'
import React, { ReactElement } from 'react'
import ReactMarkdown from 'react-markdown'
const Markdown = ({
@ -7,7 +7,7 @@ const Markdown = ({
}: {
text: string
className?: string
}) => {
}): ReactElement => {
// fix react-markdown \n transformation
// https://github.com/rexxars/react-markdown/issues/105#issuecomment-351585313
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 Tippy from '@tippyjs/react'
import React, { ReactElement, ReactNode } from '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({
content,
children
}: {
content: string
children: ReactElement
}) {
return (
<Tippy content={content}>
<CustomWrapper>{children}</CustomWrapper>
</Tippy>
)
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)' }
}
// Forward ref for Tippy.js
// eslint-disable-next-line
const CustomWrapper = forwardRef(
({ children }: { children: ReactElement }, ref: any) => {
return <div ref={ref}>{children}</div>
const DefaultTrigger = React.forwardRef((props, ref: any) => {
return <Info className={styles.icon} ref={ref} />
})
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 { graphql, useStaticQuery } from 'gatsby'
import stylesIndex from './index.module.css'
import styles from './Advanced.module.css'
import FormHelp from '../../../atoms/Input/Help'
@ -10,19 +11,22 @@ import { isCorrectNetwork } from '../../../../utils/wallet'
import { useSiteMetadata } from '../../../../hooks/useSiteMetadata'
import InputElement from '../../../atoms/Input/InputElement'
import Label from '../../../atoms/Input/Label'
import Tooltip from '../../../atoms/Tooltip'
export default function Advanced({
ocean,
tokensToMint,
weightOnDataToken,
liquidityProviderFee,
onOceanChange
onOceanChange,
content
}: {
ocean: string
tokensToMint: number
weightOnDataToken: string
liquidityProviderFee: string
onOceanChange: (event: ChangeEvent<HTMLInputElement>) => void
content: any
}): ReactElement {
const { appConfig } = useSiteMetadata()
const { account, balance, chainId, refreshBalance } = useOcean()
@ -61,10 +65,7 @@ export default function Advanced({
return (
<div className={stylesIndex.content}>
<div className={styles.advanced}>
<FormHelp className={stylesIndex.help}>
{`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>
<FormHelp className={stylesIndex.help}>{content.info}</FormHelp>
<aside className={styles.wallet}>
{balance && balance.ocean && (
@ -75,7 +76,10 @@ export default function Advanced({
<Wallet />
</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}>
<Coin
@ -95,7 +99,10 @@ export default function Advanced({
</div>
<footer className={styles.summary}>
<Label htmlFor="liquidityProviderFee">Liquidity Provider Fee</Label>
<Label htmlFor="liquidityProviderFee">
Liquidity Provider Fee{' '}
<Tooltip content={content.tooltips.liquidityProviderFee} />
</Label>
<InputElement
value={liquidityProviderFee}
name="liquidityProviderFee"

View File

@ -8,18 +8,17 @@ import Conversion from '../../../atoms/Price/Conversion'
export default function Simple({
ocean,
onChange
onChange,
content
}: {
ocean: string
onChange: (event: ChangeEvent<HTMLInputElement>) => void
content: any
}): ReactElement {
return (
<div className={stylesIndex.content}>
<div className={styles.simple}>
<FormHelp className={stylesIndex.help}>
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>
<FormHelp className={stylesIndex.help}>{content.info}</FormHelp>
<div className={styles.form}>
<Label htmlFor="ocean">Ocean Tokens</Label>

View File

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

View File

@ -1,8 +1,5 @@
.details {
composes: box from '../../atoms/Box.module.css';
padding: calc(var(--spacer) / 2) !important;
min-width: 20rem;
max-width: 25rem;
padding: calc(var(--spacer) / 4);
}
.balance {
@ -31,33 +28,3 @@
font-size: var(--font-size-small);
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 { formatCurrency } from '@coingecko/cryptoformat'
export default function Details({ attrs }: { attrs: any }): ReactElement {
export default function Details(): ReactElement {
const { balance, connect, logout, chainId } = useOcean()
return (
<div className={styles.details} {...attrs}>
<div className={styles.details}>
<ul>
{Object.entries(balance).map(([key, value]) => (
<li className={styles.balance} key={key}>
@ -41,7 +41,6 @@ export default function Details({ attrs }: { attrs: any }): ReactElement {
</li>
</ul>
<Web3Feedback />
<div className={styles.arrow} data-popper-arrow />
</div>
)
}

View File

@ -1,62 +1,15 @@
import React, { ReactElement } from 'react'
import loadable from '@loadable/component'
import { useSpring, animated } from 'react-spring'
import Account from './Account'
import Details from './Details'
import Tooltip from '../../atoms/Tooltip'
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 {
const { accountId, refreshBalance } = 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 }
})
}
const { accountId } = useOcean()
return (
<Tippy
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 />}
>
<Tooltip content={<Details />} trigger="click focus" disabled={!accountId}>
<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',
tokensToMint: 1,
weightOnDataToken: '9', // 90% on data token
liquidityProviderFee: '0.3' // in %
liquidityProviderFee: '0.1' // in %
},
files: undefined,
description: undefined,