Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Изменение Tooltip и Popover #482

Merged
merged 14 commits into from
Aug 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 121 additions & 19 deletions .pnp.cjs

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions ui-parts/condition/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"devDependencies": {
"@emotion/react": "11.9.3",
"@emotion/styled": "11.9.3",
"@types/react": "18.2.20",
"react": "18.2.0"
},
"peerDependencies": {
Expand Down
60 changes: 28 additions & 32 deletions ui-parts/condition/src/condition.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,13 @@ export const Condition: FC<ConditionProps> = ({
smoothDuration = 0.3,
smoothPattern = 'in-and-out',
children,
/* eslint-disable consistent-return */
}) => {
if (!match) return null

if (smooth) {
if (smoothPattern === 'in-and-out') {
return (
<AnimatePresence>
{match && (
<motion.div
style={{ display: 'flex', width: '100%', height: '100%' }}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: smoothDuration }}
>
{children}
</motion.div>
)}
</AnimatePresence>
)
}

if (smoothPattern === 'in') {
return (
match && (
<motion.div
style={{ display: 'flex', width: '100%', height: '100%' }}
initial={{ opacity: 0 }}
Expand All @@ -44,28 +27,41 @@ export const Condition: FC<ConditionProps> = ({
>
{children}
</motion.div>
)
</AnimatePresence>
)
}

if (smoothPattern === 'in') {
return (
<motion.div
style={{ display: 'flex', width: '100%', height: '100%' }}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: smoothDuration }}
>
{children}
</motion.div>
)
}

if (smoothPattern === 'out') {
return (
<AnimatePresence>
{match && (
<motion.div
style={{ display: 'flex', width: '100%', height: '100%' }}
initial={{ opacity: 1 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: smoothDuration }}
>
{children}
</motion.div>
)}
<motion.div
style={{ display: 'flex', width: '100%', height: '100%' }}
initial={{ opacity: 1 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: smoothDuration }}
>
{children}
</motion.div>
</AnimatePresence>
)
}
}

if (match) return children
// eslint-disable-next-line react/jsx-no-useless-fragment
return <>{children}</>
}
1 change: 1 addition & 0 deletions ui-parts/tooltip/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"postpack": "rm -rf dist"
},
"devDependencies": {
"@atls-ui-parts/condition": "workspace:0.0.5",
"@atls-ui-parts/layout": "workspace:0.0.7",
"@emotion/styled": "11.9.3",
"@testing-library/react": "13.3.0",
Expand Down
27 changes: 23 additions & 4 deletions ui-parts/tooltip/src/container/container.component.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
import styled from '@emotion/styled'
import styled from '@emotion/styled'

import { ContainerElement } from './container.element'
import { baseContainerStyles } from './container.styles'
import React from 'react'
import { Arrow } from 'react-laag'
import { forwardRef } from 'react'

export const Container = styled(ContainerElement)(baseContainerStyles)
import { Condition } from '@atls-ui-parts/condition'

import { ContainerElement } from './container.element'
import { ContainerComponentProps } from './container.interfaces'
import { baseContainerStyles } from './container.styles'

const StyledContainer = styled(ContainerElement)(baseContainerStyles)

export const Container = forwardRef<HTMLDivElement, ContainerComponentProps>((
{ text, showArrow, arrowOptions, arrowProps, layerSide, ...props },
ref
) => (
<StyledContainer ref={ref} {...props}>
{text}
<Condition match={Boolean(showArrow)}>
<Arrow {...layerSide} {...arrowOptions} {...arrowProps} />
</Condition>
</StyledContainer>
))
3 changes: 1 addition & 2 deletions ui-parts/tooltip/src/container/container.element.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import React from 'react'
import { FunctionComponent } from 'react'
import { motion } from 'framer-motion'
import { forwardRef } from 'react'

import { ContainerElementProps } from './container.interfaces'

export const ContainerElement: FunctionComponent<ContainerElementProps> = forwardRef((
export const ContainerElement = forwardRef<HTMLDivElement, ContainerElementProps>((
{ animate, ...props },
ref
) => (
Expand Down
21 changes: 20 additions & 1 deletion ui-parts/tooltip/src/container/container.interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
export type ContainerElementProps = {
import { LayerSide } from 'react-laag'
import { UseLayerArrowProps } from 'react-laag'

export interface ArrowOptions {
angle?: number | undefined
size?: number | undefined
roundness?: number | undefined
borderWidth?: number | undefined
borderColor?: string | undefined
backgroundColor?: string | undefined
}
export interface ContainerElementProps {
animate?: boolean
ref?: any
style?: any
}

export interface ContainerComponentProps extends ContainerElementProps {
text?: string | number
showArrow?: boolean
layerSide?: LayerSide
arrowProps?: UseLayerArrowProps
arrowOptions?: ArrowOptions
}
1 change: 1 addition & 0 deletions ui-parts/tooltip/src/container/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './container.component'
export * from './container.element'
export * from './container.interfaces'
2 changes: 2 additions & 0 deletions ui-parts/tooltip/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from './tooltip.component'
export * from './tooltip.interfaces'
export * from './use-tooltip.hook'
export { ArrowOptions } from './container'
export { Arrow } from 'react-laag'
126 changes: 8 additions & 118 deletions ui-parts/tooltip/src/tooltip.component.tsx
Original file line number Diff line number Diff line change
@@ -1,140 +1,30 @@
import React from 'react'
import { AnimatePresence } from 'framer-motion'
import { Children } from 'react'
import { PropsWithChildren } from 'react'
import { FC } from 'react'
import { Arrow } from 'react-laag'
import { PropsWithChildren } from 'react'
import { cloneElement } from 'react'
import { forwardRef } from 'react'
import { useLayer } from 'react-laag'

import { Container } from './container'
import { TooltipProps } from './tooltip.interfaces'
import { useClick } from './hooks'
import { useContextMenu } from './hooks'
import { useHover } from './hooks'

const doNothing = () => {
// do nothing
}

const DefaultContainer = forwardRef(({ text, arrow, ...props }: any, ref) => (
<Container ref={ref} {...props}>
{text}
{arrow}
</Container>
))
import { useTooltip } from './use-tooltip.hook'

export const Tooltip: FC<PropsWithChildren<TooltipProps>> = ({
text,
trigger = 'hover',
showArrow,
mouseEnterDelay,
mouseLeaveDelay,
anchor,
closeOnOutsideClick,
isOpen,
text = 'Text',
children,
container,
animate,
arrowOptions,
...props
}) => {
const [isOver, hoverProps] = useHover({
delayEnter: mouseEnterDelay,
delayLeave: mouseLeaveDelay,
})
const [isContextMenu, closeContextMenu, contextMenuProps] = useContextMenu()
const [isClicked, closeClicked, clickedProps] = useClick()

const getClose = (): (() => void) => {
if (trigger === 'click') return closeClicked
if (trigger === 'menu') return closeContextMenu

return doNothing
}

const getTrigger = (): boolean => {
if (typeof isOpen === 'boolean') {
return isOpen
}
if (trigger === 'hover') return isOver
if (trigger === 'click') return isClicked
if (trigger === 'menu') return isContextMenu

return false
}

const { triggerProps, layerProps, layerSide, arrowProps, renderLayer } = useLayer({
isOpen: getTrigger(),
onOutsideClick: closeOnOutsideClick ? getClose() : doNothing,
placement: anchor,
...props,
})

const getTriggerProps = () => {
if (trigger === 'hover') return { ...triggerProps, ...hoverProps }
if (trigger === 'click') return { ...triggerProps, ...clickedProps }
if (trigger === 'menu') return { ...triggerProps, ...contextMenuProps }

return triggerProps
}

const getChildrenControls = (): [boolean, () => void] => {
if (trigger === 'click' || trigger === 'menu') return [getTrigger(), getClose()]

return [getTrigger(), doNothing]
}

const getContainerControls = (): [() => void] => {
if (trigger === 'click' || trigger === 'menu') return [getClose()]

return [doNothing]
}
const { isOpen, close, triggerProps, render } = useTooltip({ ...props })

const renderChildren = () => {
if (typeof children === 'function') return children(...getChildrenControls())

return Children.only(
cloneElement(children as any, {
...getTriggerProps(),
})
)
}
const renderContainerWithoutArrow = () => {
if (typeof container === 'function') return container(...getContainerControls())

return cloneElement(container!, {
...layerProps,
text,
})
}
const renderContainerWithArrow = () => {
const renderedContainer = renderContainerWithoutArrow()

const arrow = <Arrow {...layerSide} {...arrowProps} {...arrowOptions} />

return cloneElement(renderedContainer, { arrow })
}
const renderContainer = () => {
if (showArrow) return renderContainerWithArrow()

return renderContainerWithoutArrow()
}
if (typeof children === 'function') return children(isOpen, close)

if (animate) {
return (
<>
{renderChildren()}
{renderLayer(<AnimatePresence>{getTrigger() && renderContainer()}</AnimatePresence>)}
</>
)
return Children.only(cloneElement(children as any, triggerProps))
}

return (
<>
{renderChildren()}
{renderLayer(getTrigger() && renderContainer())}
{render({ text })}
</>
)
}
Expand All @@ -153,6 +43,6 @@ Tooltip.defaultProps = {
triggerOffset: 8,
animate: false,
closeOnOutsideClick: true,
container: <DefaultContainer />,
container: <Container />,
text: 'Text',
}
29 changes: 9 additions & 20 deletions ui-parts/tooltip/src/tooltip.interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,17 @@
import { UseLayerOptions } from 'react-laag'
import { Placement } from 'react-laag'

export type TooltipTrigger = 'click' | 'hover' | 'menu'
import { ArrowOptions } from './container'

export type LayerDimensions = (layerSide: string) => {
width: number
height: number
}
export type TooltipTrigger = 'click' | 'hover' | 'menu'

export type ContainerFunction = (close: () => void) => React.ReactElement

export type ChildrenFunction = (trigger: boolean, close: () => void) => React.ReactElement

export interface ArrowOptions {
angle?: number | undefined
size?: number | undefined
roundness?: number | undefined
borderWidth?: number | undefined
borderColor?: string | undefined
backgroundColor?: string | undefined
}
type OmitOptions = 'placement' | 'onOutsideClick' | 'isOpen' | 'container' | 'trigger'

export interface TooltipProps
extends Omit<
UseLayerOptions,
'placement' | 'onOutsideClick' | 'isOpen' | 'container' | 'trigger'
> {
text?: string | number
export interface UseTooltipOptions extends Omit<UseLayerOptions, OmitOptions> {
trigger?: TooltipTrigger
showArrow?: boolean
mouseEnterDelay?: number
Expand All @@ -36,6 +21,10 @@ export interface TooltipProps
animate?: boolean
isOpen?: boolean
container?: React.ReactElement | ContainerFunction
children: React.ReactElement | ChildrenFunction
arrowOptions?: ArrowOptions
}

export interface TooltipProps extends UseTooltipOptions {
text?: string | number
children: React.ReactElement | ChildrenFunction
}
Loading
Loading