Mp 2758 updated tooltip component (#238)

* update tooltip styling

* refactor Tooltip and fix build

*  add Tooltip unit tests

* remove width and height

* update tests and yarn format script

* Apply suggestions from code review

Co-authored-by: Yusuf Seyrek <yusufseyrek@users.noreply.github.com>

---------

Co-authored-by: Yusuf Seyrek <yusufseyrek@users.noreply.github.com>
This commit is contained in:
Bob van der Helm 2023-05-31 10:34:26 +02:00 committed by GitHub
parent e651e9c797
commit ac7e82b0a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 161 additions and 40 deletions

View File

@ -1,6 +1,7 @@
import { render, screen } from '@testing-library/react' import { render, screen } from '@testing-library/react'
import Card from 'components/Card'
import { shallow } from 'enzyme' import { shallow } from 'enzyme'
import Card from 'components/Card'
import Text from 'components/Text' import Text from 'components/Text'
import Button from 'components/Button' import Button from 'components/Button'

View File

@ -1,5 +1,6 @@
import { render, fireEvent } from '@testing-library/react' import { fireEvent, render } from '@testing-library/react'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import TokenInput from 'components/TokenInput' import TokenInput from 'components/TokenInput'
import { ASSETS } from 'constants/assets' import { ASSETS } from 'constants/assets'

View File

@ -0,0 +1,45 @@
import { render } from '@testing-library/react'
import { Tooltip } from 'components/Tooltip'
describe('<Tooltip />', () => {
const defaultProps = {
content: <></>,
}
it('should render', () => {
const { container } = render(<Tooltip {...defaultProps} type='info' />)
expect(container).toBeInTheDocument()
})
it('should handle `children` prop correctly', () => {
const { getByTestId } = render(
<Tooltip {...defaultProps} type='info'>
<p data-testid='test-child'>Test text</p>
</Tooltip>,
)
expect(getByTestId('test-child')).toBeInTheDocument()
})
it('should handle `className` prop correctly', () => {
const testClass = 'test-class'
const { container } = render(<Tooltip {...defaultProps} type='info' className={testClass} />)
expect(container.getElementsByClassName(testClass)).toHaveLength(1)
})
describe('should handle `underline` prop correctly', () => {
it('should have border class when children are passed', () => {
const { container } = render(
<Tooltip {...defaultProps} type='info' underline>
<></>
</Tooltip>,
)
expect(container.getElementsByClassName('border-b-1')).toHaveLength(1)
})
it('should not have border class when children are passed', () => {
const { container } = render(<Tooltip {...defaultProps} type='info' underline />)
expect(container.getElementsByClassName('border-b-1')).toHaveLength(0)
})
})
})

View File

@ -0,0 +1,44 @@
import { render } from '@testing-library/react'
import { TooltipType } from 'components/Tooltip'
import TooltipContent from 'components/Tooltip/TooltipContent'
describe('<Tooltip />', () => {
const defaultProps = {
content: <></>,
}
it('should render', () => {
const { container } = render(<TooltipContent {...defaultProps} type='info' />)
expect(container).toBeInTheDocument()
})
it('should handle `type` prop correctly', () => {
const types = { info: 'bg-white/20', warning: 'bg-warning', error: 'bg-error' }
Object.entries(types).forEach(([key, value]) => {
const { container } = render(<TooltipContent {...defaultProps} type={key as TooltipType} />)
expect(container.getElementsByClassName(value)).toHaveLength(1)
})
})
describe('should handle `content` props correctly', () => {
it('should render Text component when type is string', () => {
const testText = 'testText'
const { getByTestId } = render(
<TooltipContent {...defaultProps} type='info' content={testText} />,
)
const textComponent = getByTestId('text-component')
expect(textComponent).toHaveTextContent(testText)
})
it('should render content when type is ReactNode', () => {
const testNode = <p data-testid='test-node'>Test node</p>
const { queryByTestId } = render(
<TooltipContent {...defaultProps} type='info' content={testNode} />,
)
expect(queryByTestId('text-component')).not.toBeInTheDocument()
expect(queryByTestId('test-node')).toBeInTheDocument()
})
})
})

View File

@ -7,7 +7,7 @@
"dev": "next dev", "dev": "next dev",
"test": "jest", "test": "jest",
"lint": "eslint ./src/ && yarn prettier-check", "lint": "eslint ./src/ && yarn prettier-check",
"format": "eslint ./src/ --fix && prettier --write ./src/", "format": "eslint ./src/ ./__tests__/ --fix && prettier --write ./src/ ./__tests__/",
"prettier-check": "prettier --ignore-path .gitignore --check ./src/", "prettier-check": "prettier --ignore-path .gitignore --check ./src/",
"start": "next start", "start": "next start",
"validate-env": "node ./validate-env" "validate-env": "node ./validate-env"

View File

@ -72,7 +72,7 @@ export const BorrowCapacity = ({
</div> </div>
)} )}
</div> </div>
<Tooltip content={<Text size='sm'>Borrow Capacity Tooltip</Text>}> <Tooltip type='info' content={<Text size='sm'>Borrow Capacity Tooltip</Text>}>
<div className='relative'> <div className='relative'>
<div <div
className='overflow-hidden rounded-3xl border-r-2 border-r-loss ' className='overflow-hidden rounded-3xl border-r-2 border-r-loss '

View File

@ -28,7 +28,7 @@ export const Gauge = ({
const circlePercent = 100 - percentageValue const circlePercent = 100 - percentageValue
return ( return (
<Tooltip content={tooltip}> <Tooltip type='info' content={tooltip}>
<div className={classNames('relative', `w-${diameter / 4} h-${diameter / 4}`)}> <div className={classNames('relative', `w-${diameter / 4} h-${diameter / 4}`)}>
<svg <svg
viewBox='2 -2 28 36' viewBox='2 -2 28 36'

View File

@ -1,3 +1,3 @@
<svg viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.99935 5.66797V9.0013M8.99935 12.3346H9.00768M17.3327 9.0013C17.3327 13.6037 13.6017 17.3346 8.99935 17.3346C4.39698 17.3346 0.666016 13.6037 0.666016 9.0013C0.666016 4.39893 4.39698 0.667969 8.99935 0.667969C13.6017 0.667969 17.3327 4.39893 17.3327 9.0013Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/> <path d="M9.74105 6.66602V9.99935M9.74105 13.3327H9.74938M18.0744 9.99935C18.0744 14.6017 14.3434 18.3327 9.74105 18.3327C5.13868 18.3327 1.40771 14.6017 1.40771 9.99935C1.40771 5.39698 5.13868 1.66602 9.74105 1.66602C14.3434 1.66602 18.0744 5.39698 18.0744 9.99935Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 421 B

After

Width:  |  Height:  |  Size: 436 B

View File

@ -67,6 +67,8 @@ export default function Settings() {
Reduce Motion Reduce Motion
</Text> </Text>
<Tooltip <Tooltip
type='info'
interactive
content={ content={
<Text size='sm'> <Text size='sm'>
Turns off all animations inside the dApp. Turning animations off can increase Turns off all animations inside the dApp. Turning animations off can increase
@ -83,6 +85,7 @@ export default function Settings() {
Display Currency Display Currency
</Text> </Text>
<Tooltip <Tooltip
type='info'
content={ content={
<Text size='sm'> <Text size='sm'>
Sets the denomination of values to a different currency. While OSMO is the Sets the denomination of values to a different currency. While OSMO is the

View File

@ -21,7 +21,7 @@ export default function SwitchWithLabel(props: Props) {
<Text className='mr-2 text-white/70' size='sm'> <Text className='mr-2 text-white/70' size='sm'>
{props.label} {props.label}
</Text> </Text>
{props.tooltip && <Tooltip content={<Text size='sm'>{props.tooltip}</Text>} />} {props.tooltip && <Tooltip type='info' content={<Text size='sm'>{props.tooltip}</Text>} />}
</div> </div>
<Switch <Switch
name={props.name} name={props.name}

View File

@ -23,6 +23,7 @@ export default function Text(props: Props) {
return ( return (
<HtmlElement <HtmlElement
data-testid='text-component'
className={classNames( className={classNames(
props.className, props.className,
props.uppercase ? `text-${sizeClass}-caps` : `text-${sizeClass}`, props.uppercase ? `text-${sizeClass}-caps` : `text-${sizeClass}`,

View File

@ -12,6 +12,7 @@ import { BN } from 'utils/helpers'
import { FormattedNumber } from 'components/FormattedNumber' import { FormattedNumber } from 'components/FormattedNumber'
import Button from 'components/Button' import Button from 'components/Button'
import { ExclamationMarkTriangle } from 'components/Icons' import { ExclamationMarkTriangle } from 'components/Icons'
import { Tooltip } from 'components/Tooltip'
interface Props { interface Props {
amount: BigNumber amount: BigNumber
@ -82,7 +83,13 @@ export default function TokenInput(props: Props) {
/> />
{props.warning && ( {props.warning && (
<div className='grid items-center px-2'> <div className='grid items-center px-2'>
<ExclamationMarkTriangle className='text-warning' /> <Tooltip
content={`You don't have any ${props.asset.symbol}. Please first deposit ${props.asset.symbol} into your credit account before.`}
type='info'
interactive
>
<ExclamationMarkTriangle className='text-warning' />
</Tooltip>
</div> </div>
)} )}
</div> </div>

View File

@ -0,0 +1,31 @@
import classNames from 'classnames'
import { ReactNode } from 'react'
import { ExclamationMarkCircled } from 'components/Icons'
import Text from 'components/Text'
import { TooltipType } from '.'
interface Props {
content: ReactNode | string
type: TooltipType
}
export default function TooltipContent(props: Props) {
return (
<div
className={classNames(
'flex max-w-[320px] flex-1 gap-2 rounded-sm p-3 text-xs shadow-tooltip backdrop-blur-lg',
'before:content-[" "] before:absolute before:inset-0 before:-z-1 before:rounded-base before:p-[1px] before:border-glas',
props.type === 'info' && 'bg-white/20',
props.type === 'warning' && 'bg-warning',
props.type === 'error' && 'bg-error',
)}
>
<div>
<ExclamationMarkCircled className='w-5 gap-3 text-white' />
</div>
{typeof props.content === 'string' ? <Text size='sm'>{props.content}</Text> : props.content}
</div>
)
}

View File

@ -2,62 +2,50 @@ import Tippy from '@tippyjs/react'
import classNames from 'classnames' import classNames from 'classnames'
import { ReactNode } from 'react' import { ReactNode } from 'react'
import { ExclamationMarkCircled, Questionmark } from 'components/Icons' import { Questionmark } from 'components/Icons'
import useStore from 'store' import useStore from 'store'
import TooltipContent from './TooltipContent'
interface Props { interface Props {
children?: ReactNode | string
content: ReactNode | string content: ReactNode | string
type: TooltipType
children?: ReactNode | string
className?: string className?: string
delay?: number delay?: number
inderactive?: boolean interactive?: boolean
underline?: boolean underline?: boolean
} }
export const Tooltip = ({ export type TooltipType = 'info' | 'warning' | 'error'
children,
content, export const Tooltip = (props: Props) => {
className,
delay = 0,
inderactive = false,
underline = false,
}: Props) => {
const enableAnimations = useStore((s) => s.enableAnimations) const enableAnimations = useStore((s) => s.enableAnimations)
return ( return (
<Tippy <Tippy
appendTo={() => document.body} appendTo={() => document.querySelector('dialog[open]') ?? document.body}
interactive={inderactive} interactive={props.interactive}
animation={false} animation={false}
delay={[delay, 0]} delay={[props.delay ?? 0, 0]}
render={(attrs) => { render={() => <TooltipContent type={props.type} content={props.content} />}
return (
<div
className='max-w-[320px] rounded-lg bg-black/80 p-4 pl-12 text-xs shadow-tooltip backdrop-blur-lg'
{...attrs}
>
<ExclamationMarkCircled className='absolute left-4 top-3.5 h-5 w-5 text-white' />
{content}
</div>
)
}}
> >
{children ? ( {props.children ? (
<span <span
className={classNames( className={classNames(
underline && props.underline &&
'border-b-1 cursor-pointer border border-x-0 border-t-0 border-dashed border-white/50 hover:border-transparent', 'border-b-1 cursor-pointer border border-x-0 border-t-0 border-dashed border-white/50 hover:border-transparent',
enableAnimations && 'transition-all', enableAnimations && 'transition-all',
className, props.className,
)} )}
> >
{children} {props.children}
</span> </span>
) : ( ) : (
<span <span
className={classNames( className={classNames(
'inline-block w-[18px] cursor-pointer opacity-40 hover:opacity-80', 'inline-block w-[18px] cursor-pointer opacity-40 hover:opacity-80',
className, props.className,
)} )}
> >
<Questionmark /> <Questionmark />

View File

@ -78,7 +78,7 @@ module.exports = {
axlusdc: '#478edc', axlusdc: '#478edc',
body: '#0D0012', body: '#0D0012',
'body-dark': '#141621', 'body-dark': '#141621',
error: '#F97066', error: '#F04438',
'error-bg': '#FDA29B', 'error-bg': '#FDA29B',
grey: '#3a3c49', grey: '#3a3c49',
'grey-dark': '#1a1c25', 'grey-dark': '#1a1c25',