From 20a23ab2c29513c7a54585fd29955b32403db373 Mon Sep 17 00:00:00 2001 From: Francis Targanski <107006413+Ftarganski@users.noreply.github.com> Date: Tue, 2 Jan 2024 15:45:21 -0300 Subject: [PATCH] Feat visu tooltip time (#106) * feat: add time variation to tooltip component * feat: create tooltip variation - time duration to close * feat: add tooltip variation, mode time --- .../docs/src/stories/Tooltip/Time.stories.tsx | 140 ++++++++++++++++++ package-lock.json | 8 +- packages/visu/src/App.tsx | 8 +- .../visu/src/__tests__/Tooltip/Close.test.tsx | 4 +- .../visu/src/__tests__/Tooltip/Time.test.tsx | 119 +++++++++++++++ packages/visu/src/library/Tooltip/Hover.tsx | 18 +-- packages/visu/src/library/Tooltip/Time.tsx | 81 ++++++++++ packages/visu/src/library/Tooltip/index.ts | 3 + 8 files changed, 355 insertions(+), 26 deletions(-) create mode 100644 apps/docs/src/stories/Tooltip/Time.stories.tsx create mode 100644 packages/visu/src/__tests__/Tooltip/Time.test.tsx create mode 100644 packages/visu/src/library/Tooltip/Time.tsx diff --git a/apps/docs/src/stories/Tooltip/Time.stories.tsx b/apps/docs/src/stories/Tooltip/Time.stories.tsx new file mode 100644 index 00000000..b487b80a --- /dev/null +++ b/apps/docs/src/stories/Tooltip/Time.stories.tsx @@ -0,0 +1,140 @@ +import { Button, Tooltip, TooltipTimeProps } from '@droz-js/visu' +import { Meta, StoryObj } from '@storybook/react' +import { Question } from 'phosphor-react' + +const meta: Meta = { + title: 'Tooltip/Time', + component: Tooltip.Time, + argTypes: { + children: { + control: 'none', + description: 'Trigger do Tooltip.', + table: { + type: { + summary: 'React.ReactNode', + }, + }, + }, + content: { + control: 'text', + description: 'Define o conteúdo exibido no Tooltip.', + table: { + type: { summary: 'React.ReactNode' }, + }, + type: { name: 'string', required: false }, + }, + defaultOpen: { + control: 'boolean', + description: 'Define se o Tooltip deve iniciar aberto ou fechado.', + table: { + type: { summary: 'boolean' }, + defaultValue: { summary: 'false' }, + }, + type: { name: 'boolean', required: false }, + }, + closeTime: { + control: 'number', + description: 'Define o tempo em milissegundos para fechar o conteúdo do Tooltip.', + table: { + type: { summary: 'number' }, + defaultValue: { summary: '2000' }, + }, + type: { name: 'number', required: false }, + }, + open: { + control: 'boolean', + description: 'Define se o conteúdo do Tooltip está visível ou não.', + table: { + type: { summary: 'boolean' }, + defaultValue: { summary: 'false' }, + }, + type: { name: 'boolean', required: false }, + }, + side: { + control: { type: 'inline-radio' }, + description: 'Define o local em que o componente irá aparecer, relativo ao trigger.', + options: ['bottom', 'left', 'right', 'top'], + table: { + type: { + summary: ['bottom', 'left', 'right', 'top'].join('|'), + }, + defaultValue: { summary: 'bottom' }, + }, + type: { name: 'string', required: false }, + }, + onOpenChange: { + control: 'none', + description: 'Callback executado quando o estado de `open` é alterado.', + table: { + type: { summary: 'function' }, + }, + type: { name: 'function', required: false }, + }, + + /** + * @deprecated - Deprecated props will be removed in the next major version. + */ + text: { + control: 'none', + name: 'text (deprecated)', + description: 'Define o valor em texto do componente. (deprecated) - Use a propriedade `content`.', + table: { + type: { summary: 'text' }, + }, + type: { name: 'string', required: false }, + }, + }, + args: { + children: '', + content: 'Texto do tooltip', + defaultOpen: false, + closeTime: 2000, + open: false, + side: 'bottom', + }, + parameters: { + design: { + type: 'figma', + url: 'https://www.figma.com/file/cUmiZr1GhrB9HsRCuOJ0S2/%5BDroz-Nexo%5D-Library?type=design&node-id=3107-18351&t=NDiO1Vda4DCr6uFV-0', + allowFullscreen: true, + }, + }, +} + +export default meta +type TooltipTimeStory = StoryObj + +export const ComTexto: TooltipTimeStory = { + name: 'Com texto', + render: (args) => { + return ( + + + + ) + }, +} + +export const ComReactNode: TooltipTimeStory = { + name: 'Com ReactNode', + args: { + content: ( + <> +

Texto do tooltip

+ Button do tooltip + + ), + }, + argTypes: { + content: { + control: 'none', + }, + }, + render: (args) => { + return ( + + + + ) + }, +} diff --git a/package-lock.json b/package-lock.json index b71b22de..f450957b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,7 +43,7 @@ }, "apps/docs": { "name": "@droz-js/visu-docs", - "version": "2.4.0", + "version": "2.5.0", "dependencies": { "@droz-js/visu": "*", "react": "^18.2.0", @@ -75,7 +75,7 @@ }, "apps/web": { "name": "@droz-js/visu-web", - "version": "2.4.0", + "version": "2.5.0", "dependencies": { "@droz-js/visu": "*", "react": "^18.2.0", @@ -30188,11 +30188,11 @@ }, "packages/tsconfig": { "name": "@droz-js/tsconfig", - "version": "2.4.0" + "version": "2.5.0" }, "packages/visu": { "name": "@droz-js/visu", - "version": "2.4.0", + "version": "2.5.0", "dependencies": { "@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-avatar": "^1.0.2", diff --git a/packages/visu/src/App.tsx b/packages/visu/src/App.tsx index ed6bf34d..4029a119 100644 --- a/packages/visu/src/App.tsx +++ b/packages/visu/src/App.tsx @@ -1,5 +1,5 @@ import LayoutDefault from './layout/Default' -import { Skeleton } from './library' +import { Tooltip } from './library' import { zodResolver } from '@hookform/resolvers/zod' import { Eraser } from 'phosphor-react' import { useState } from 'react' @@ -32,9 +32,9 @@ function App() { , onClick: clearState }]}>
{/* ================================= TEST AREA ================================= */} -
- -
+ + Time + {/* ================================= TEST AREA ================================= */}
diff --git a/packages/visu/src/__tests__/Tooltip/Close.test.tsx b/packages/visu/src/__tests__/Tooltip/Close.test.tsx index 69a8279b..f03d17d1 100644 --- a/packages/visu/src/__tests__/Tooltip/Close.test.tsx +++ b/packages/visu/src/__tests__/Tooltip/Close.test.tsx @@ -38,9 +38,7 @@ describe('TooltipClose tests', () => { fireEvent.click(element) await waitFor(() => { - expect(screen.getByRole('dialog')).toContainHTML( - 'Text1', - ) + expect(screen.getByRole('dialog')).toContainHTML('Text1') }) }) diff --git a/packages/visu/src/__tests__/Tooltip/Time.test.tsx b/packages/visu/src/__tests__/Tooltip/Time.test.tsx new file mode 100644 index 00000000..0bfbc8fe --- /dev/null +++ b/packages/visu/src/__tests__/Tooltip/Time.test.tsx @@ -0,0 +1,119 @@ +import { Tooltip } from '@library' +import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' + +// Testes limitados por conta do Radix +describe('TooltipTime tests', () => { + it('Should render a TooltipTime element', () => { + render( + +
Hello
+
, + ) + const element = screen.getByTestId('element') + + expect(element).toBeInTheDocument() + }) + + it('Should render a text with the prop "content"', async () => { + const user = userEvent.setup() + + render( + +
Hello
+
, + ) + const element = screen.getByTestId('element') + await user.click(element) + + await waitFor(() => { + expect(screen.getByRole('tooltip')).toHaveTextContent('Text1') + }) + }) + + it('Should render a ReactNode content with the prop "content"', async () => { + const user = userEvent.setup() + + render( + Text1}> +
Hello
+
, + ) + const element = screen.getByTestId('element') + await user.click(element) + + await waitFor(() => { + expect(screen.getByRole('tooltip')).toContainHTML('Text1') + }) + }) + + it('Should render content when trigger gets a click', async () => { + render( + Text1}> +
Hello
+
, + ) + const element = screen.getByTestId('element') + fireEvent.click(element) + + await waitFor(() => { + expect(screen.getByRole('tooltip')).toContainHTML('Text1') + }) + }) + + it('Should execute "onOpenChange" when "open" value changes', async () => { + const onOpenChange = jest.fn() + console.log(onOpenChange) + render( + Text1} onOpenChange={onOpenChange}> +
Hello
+
, + ) + const element = screen.getByTestId('element') + fireEvent.click(element) + + await waitFor(() => { + expect(screen.getByRole('tooltip')).toContainHTML('Text1') + expect(onOpenChange).toBeCalledTimes(1) + expect(onOpenChange).toBeCalledWith(true) + }) + }) + + /** + * @deprecated - Teste para uma propriedade deprecated + */ + it('Should render a text with the prop "text"', async () => { + const user = userEvent.setup() + + render( + +
Hello
+
, + ) + const element = screen.getByTestId('element') + await user.click(element) + + await waitFor(() => { + expect(screen.getByRole('tooltip')).toHaveTextContent('Text1') + }) + }) + + it('Should close the tooltip after the specified closeTime', async () => { + const closeTime = 3500 // Tempo de fechamento em milissegundos + + render( + Text1} closeTime={closeTime}> +
Hello
+
, + ) + const element = screen.getByTestId('element') + fireEvent.click(element) + + await waitFor(() => { + expect(screen.getByRole('tooltip')).toContainHTML('Text1') + setTimeout(() => { + expect(screen.queryByRole('tooltip')).toBeNull() + }, closeTime + 100) + }) + }) +}) diff --git a/packages/visu/src/library/Tooltip/Hover.tsx b/packages/visu/src/library/Tooltip/Hover.tsx index 3f48cb3a..739b0445 100644 --- a/packages/visu/src/library/Tooltip/Hover.tsx +++ b/packages/visu/src/library/Tooltip/Hover.tsx @@ -1,17 +1,9 @@ import * as RadixTooltip from '@radix-ui/react-tooltip' import { Position } from '@types' import { clsx } from 'clsx' -import { - FC, - HTMLAttributes, - ReactNode, - useCallback, - useEffect, - useState, -} from 'react' +import { FC, HTMLAttributes, ReactNode, useCallback, useEffect, useState } from 'react' -export interface TooltipHoverProps - extends Omit, 'content'> { +export interface TooltipHoverProps extends Omit, 'content'> { // Optional because we can't remove `text` until the next major release content?: ReactNode defaultOpen?: boolean @@ -60,11 +52,7 @@ const TooltipHover: FC = ({ open={tooltipOpen} onOpenChange={(value) => handleOpenChange(value)} > - handleOpenChange(true)} - > + handleOpenChange(true)}> {children} diff --git a/packages/visu/src/library/Tooltip/Time.tsx b/packages/visu/src/library/Tooltip/Time.tsx new file mode 100644 index 00000000..4397f530 --- /dev/null +++ b/packages/visu/src/library/Tooltip/Time.tsx @@ -0,0 +1,81 @@ +import * as RadixTooltip from '@radix-ui/react-tooltip' +import { Position } from '@types' +import { clsx } from 'clsx' +import { FC, HTMLAttributes, ReactNode, useCallback, useEffect, useState } from 'react' + +export interface TooltipTimeProps extends Omit, 'content'> { + // Optional because we can't remove `text` until the next major release + content?: ReactNode + defaultOpen?: boolean + open?: boolean + side?: Position + closeTime?: number + onOpenChange?: (open: boolean) => void + /** + * @deprecated Use `content` instead. Will be removed in the next major release + */ + text?: string +} + +const TooltipTime: FC = ({ + children, + className, + content, + defaultOpen = false, + closeTime = 2000, + open = false, + onOpenChange, + side, + text, + ...rest +}) => { + const [tooltipOpen, setTooltipOpen] = useState(open) + + const handleOpenChange = useCallback( + (value: boolean) => { + setTooltipOpen(value) + if (onOpenChange) onOpenChange(value) + + if (value && closeTime) { + setTimeout(() => { + setTooltipOpen(false) + if (onOpenChange) onOpenChange(false) + }, closeTime) + } + }, + [onOpenChange, closeTime], + ) + + useEffect(() => { + setTooltipOpen(open) + }, [open]) + + const handleTooltipClick = () => { + handleOpenChange(!tooltipOpen) + } + + return ( + + + + {children} + + + + + +
+ + {content || text} + +
+
+
+
+
+ ) +} + +TooltipTime.displayName = 'Tooltip.Time' + +export default TooltipTime diff --git a/packages/visu/src/library/Tooltip/index.ts b/packages/visu/src/library/Tooltip/index.ts index 155a9a0e..f1cff74a 100644 --- a/packages/visu/src/library/Tooltip/index.ts +++ b/packages/visu/src/library/Tooltip/index.ts @@ -1,15 +1,18 @@ import TooltipClose from './Close' import TooltipHover from './Hover' import TooltipStep from './Step' +import TooltipTime from './Time' export type { TooltipCloseProps } from './Close' export type { TooltipHoverProps } from './Hover' export type { TooltipStepProps } from './Step' +export type { TooltipTimeProps } from './Time' const Tooltip = { Close: TooltipClose, Hover: TooltipHover, Step: TooltipStep, + Time: TooltipTime, } export default Tooltip