From f9d337b9cc1270e0a76063eae8997c3e6c76a125 Mon Sep 17 00:00:00 2001 From: Michael Gray <46670918+mgray-sonalake@users.noreply.github.com> Date: Fri, 18 Feb 2022 08:58:41 +0000 Subject: [PATCH] feat(tooltip): add radix-ui tooltip component (#39) * feat(tooltip): add radix-ui tooltip component * feat(tooltip): fix export --- jest.config.js | 4 +- package.json | 5 +- src/__mocks__/DOMRect.js | 12 ++++ src/__mocks__/ResizeObserver.js | 6 ++ src/index.tsx | 1 + src/setupTests.ts | 6 ++ src/ui/alert/alert.spec.tsx | 56 ++++++++--------- src/ui/tooltip/index.ts | 2 + src/ui/tooltip/tooltip.component.tsx | 50 +++++++++++++++ src/ui/tooltip/tooltip.context.tsx | 3 + src/ui/tooltip/tooltip.spec.tsx | 22 +++++++ src/ui/tooltip/tooltip.stories.tsx | 43 +++++++++++++ yarn.lock | 91 +++++++++++++++++++++++++++- 13 files changed, 270 insertions(+), 31 deletions(-) create mode 100644 src/__mocks__/DOMRect.js create mode 100644 src/__mocks__/ResizeObserver.js create mode 100644 src/ui/tooltip/index.ts create mode 100644 src/ui/tooltip/tooltip.component.tsx create mode 100644 src/ui/tooltip/tooltip.context.tsx create mode 100644 src/ui/tooltip/tooltip.spec.tsx create mode 100644 src/ui/tooltip/tooltip.stories.tsx diff --git a/jest.config.js b/jest.config.js index dff4787..80ad892 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,7 +1,9 @@ module.exports = { - setupFilesAfterEnv: ['./src/setupTests.ts'], + clearMocks: true, moduleNameMapper: { '\\.(css|sass|scss)$': 'identity-obj-proxy', '\\.svg$': 'jest-transform-stub', }, + setupFilesAfterEnv: ['/src/setupTests.ts'], + testEnvironment: 'jsdom', }; diff --git a/package.json b/package.json index d1be669..42f3c88 100644 --- a/package.json +++ b/package.json @@ -29,9 +29,9 @@ }, "peerDependencies": { "@radix-ui/react-alert-dialog": "^0.1.5", + "@radix-ui/react-tooltip": "^0.1.6", "clsx": "^1.1.1", - "react": ">=16", - "react-icons": "^4.3.1" + "react": ">=16" }, "husky": { "hooks": { @@ -68,6 +68,7 @@ "@commitlint/cli": "^16.1.0", "@commitlint/config-conventional": "^16.0.0", "@radix-ui/react-alert-dialog": "^0.1.5", + "@radix-ui/react-tooltip": "^0.1.6", "@size-limit/preset-small-lib": "^7.0.8", "@storybook/addon-essentials": "^6.4.18", "@storybook/addon-info": "^5.3.21", diff --git a/src/__mocks__/DOMRect.js b/src/__mocks__/DOMRect.js new file mode 100644 index 0000000..a1c9837 --- /dev/null +++ b/src/__mocks__/DOMRect.js @@ -0,0 +1,12 @@ +class DOMRect { + static fromRect() { + return { + height: 0, + width: 0, + x: 0, + y: 0, + }; + } +} + +global.DOMRect = DOMRect; diff --git a/src/__mocks__/ResizeObserver.js b/src/__mocks__/ResizeObserver.js new file mode 100644 index 0000000..6b6d67c --- /dev/null +++ b/src/__mocks__/ResizeObserver.js @@ -0,0 +1,6 @@ +class ResizeObserver { + observe() {} + unobserve() {} +} + +global.ResizeObserver = ResizeObserver; diff --git a/src/index.tsx b/src/index.tsx index 881f21a..6e0cd34 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,3 +1,4 @@ import './style.css'; +export * from './models'; export * from './ui'; diff --git a/src/setupTests.ts b/src/setupTests.ts index 9e26456..3d8e4fe 100644 --- a/src/setupTests.ts +++ b/src/setupTests.ts @@ -2,5 +2,11 @@ import { toHaveNoViolations } from 'jest-axe'; import '@testing-library/jest-dom'; import 'regenerator-runtime/runtime.js'; +import './__mocks__/DOMRect'; +import './__mocks__/ResizeObserver'; +/** + * Need to mock above as not included in jest/jsdom + * see https://github.com/radix-ui/primitives/issues/420#issuecomment-771615182 + */ expect.extend(toHaveNoViolations); diff --git a/src/ui/alert/alert.spec.tsx b/src/ui/alert/alert.spec.tsx index 9c19ec1..8ae115f 100644 --- a/src/ui/alert/alert.spec.tsx +++ b/src/ui/alert/alert.spec.tsx @@ -13,33 +13,35 @@ import { AlertTrigger, } from './components'; -test('renders an accessible alert dialog', async () => { - const { container } = render( - - - - - - Title - Description - - - - - - - - - ); +describe('Alert', () => { + test('renders an accessible alert dialog', async () => { + const { container } = render( + + + + + + Title + Description + + + + + + + + + ); - userEvent.click(screen.getByRole('button', { name: 'Trigger' })); + userEvent.click(screen.getByRole('button', { name: 'Trigger' })); - expect( - screen.getByRole('alertdialog', { name: 'Title' }) - ).toBeInTheDocument(); - expect(screen.getByRole('alertdialog')).toHaveAccessibleDescription( - 'Description' - ); - expect(screen.getByRole('button', { name: 'Cancel' })).toHaveFocus(); - expect(await axe(container)).toHaveNoViolations(); + expect( + screen.getByRole('alertdialog', { name: 'Title' }) + ).toBeInTheDocument(); + expect(screen.getByRole('alertdialog')).toHaveAccessibleDescription( + 'Description' + ); + expect(screen.getByRole('button', { name: 'Cancel' })).toHaveFocus(); + expect(await axe(container)).toHaveNoViolations(); + }); }); diff --git a/src/ui/tooltip/index.ts b/src/ui/tooltip/index.ts new file mode 100644 index 0000000..588fc25 --- /dev/null +++ b/src/ui/tooltip/index.ts @@ -0,0 +1,2 @@ +export * from './tooltip.component'; +export * from './tooltip.context'; diff --git a/src/ui/tooltip/tooltip.component.tsx b/src/ui/tooltip/tooltip.component.tsx new file mode 100644 index 0000000..39de5fb --- /dev/null +++ b/src/ui/tooltip/tooltip.component.tsx @@ -0,0 +1,50 @@ +import React, { ReactNode } from 'react'; +import { + Arrow, + Content, + Root, + TooltipContentProps, + TooltipProps, + Trigger, +} from '@radix-ui/react-tooltip'; +import clsx from 'clsx'; + +export type TooltipOwnProps = { + children: ReactNode; + content: ReactNode; + side?: TooltipContentProps['side']; + padding?: 'sm' | 'md'; + defaultOpen?: boolean; + open?: boolean; + onOpenChange?: TooltipProps['onOpenChange']; +}; + +export const Tooltip = ({ + children, + content, + side = 'top', + padding = 'sm', + open, + defaultOpen, + onOpenChange, +}: TooltipOwnProps) => ( + + {children} + +
+ {content} + +
+
+
+); diff --git a/src/ui/tooltip/tooltip.context.tsx b/src/ui/tooltip/tooltip.context.tsx new file mode 100644 index 0000000..e60cbb1 --- /dev/null +++ b/src/ui/tooltip/tooltip.context.tsx @@ -0,0 +1,3 @@ +import { Provider } from '@radix-ui/react-tooltip'; + +export const TooltipProvider = Provider; diff --git a/src/ui/tooltip/tooltip.spec.tsx b/src/ui/tooltip/tooltip.spec.tsx new file mode 100644 index 0000000..943cf83 --- /dev/null +++ b/src/ui/tooltip/tooltip.spec.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; + +import { Tooltip } from './tooltip.component'; +import { TooltipProvider } from './tooltip.context'; + +describe('Tooltip', () => { + test('has no accessibility violations', async () => { + const { container } = render( + + + + + + ); + + expect(screen.getByRole('button', { name: 'Trigger' })).toBeInTheDocument(); + + expect(await axe(container)).toHaveNoViolations(); + }); +}); diff --git a/src/ui/tooltip/tooltip.stories.tsx b/src/ui/tooltip/tooltip.stories.tsx new file mode 100644 index 0000000..816296d --- /dev/null +++ b/src/ui/tooltip/tooltip.stories.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { ComponentMeta, ComponentStory } from '@storybook/react'; + +import { Tooltip } from './tooltip.component'; +import { TooltipProvider } from './tooltip.context'; + +const meta: ComponentMeta = { + title: 'Atoms/Tooltip', + component: Tooltip, + parameters: { + backgrounds: { + default: 'light', + }, + }, + decorators: [ + (Story) => ( +
+ + + +
+ ), + ], +}; + +export default meta; + +const Template: ComponentStory = (args) => ( + +); + +export const Default = Template.bind({}); +Default.args = { + children: , + content: 'This is the content!', + side: 'bottom', +}; + +export const OpenByDefault = Template.bind({}); +OpenByDefault.args = { + ...Default.args, + defaultOpen: true, +}; diff --git a/yarn.lock b/yarn.lock index 766f03b..fd48b37 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2041,6 +2041,14 @@ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.2.tgz#830beaec4b4091a9e9398ac50f865ddea52186b9" integrity sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA== +"@radix-ui/popper@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/popper/-/popper-0.1.0.tgz#c387a38f31b7799e1ea0d2bb1ca0c91c2931b063" + integrity sha512-uzYeElL3w7SeNMuQpXiFlBhTT+JyaNMCwDfjKkrzugEcYrf5n52PHqncNdQPUtR42hJh8V9FsqyEDbDxkeNjJQ== + dependencies: + "@babel/runtime" "^7.13.10" + csstype "^3.0.4" + "@radix-ui/primitive@0.1.0": version "0.1.0" resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-0.1.0.tgz#6206b97d379994f0d1929809db035733b337e543" @@ -2061,6 +2069,14 @@ "@radix-ui/react-primitive" "0.1.3" "@radix-ui/react-slot" "0.1.2" +"@radix-ui/react-arrow@0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-0.1.3.tgz#17f86eab216c48aff17b13b811569a9bbabaa44d" + integrity sha512-9x1gRYdlUD5OUwY7L+M+4FY/YltDSsrNSj8QXGPbxZxL5ghWXB/4lhyIGccCwk/e8ggfmQYv9SRNmn3LavPo3A== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-primitive" "0.1.3" + "@radix-ui/react-compose-refs@0.1.0": version "0.1.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-0.1.0.tgz#cff6e780a0f73778b976acff2c2a5b6551caab95" @@ -2134,6 +2150,21 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-use-layout-effect" "0.1.0" +"@radix-ui/react-popper@0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-0.1.3.tgz#a93bdd72845566007e5f3868caddd62318bb781e" + integrity sha512-2OV2YaJv7iTZexJY3HJ7B6Fs1A/3JXd3fRGU4JY0guACfGMD1C/jSgds505MKQOTiHE/quI6j3/q8yfzFjJR9g== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/popper" "0.1.0" + "@radix-ui/react-arrow" "0.1.3" + "@radix-ui/react-compose-refs" "0.1.0" + "@radix-ui/react-context" "0.1.1" + "@radix-ui/react-primitive" "0.1.3" + "@radix-ui/react-use-rect" "0.1.1" + "@radix-ui/react-use-size" "0.1.0" + "@radix-ui/rect" "0.1.1" + "@radix-ui/react-portal@0.1.3": version "0.1.3" resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-0.1.3.tgz#56826e789b3d4e37983f6d23666e3f1b1b9ee358" @@ -2168,6 +2199,27 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-compose-refs" "0.1.0" +"@radix-ui/react-tooltip@^0.1.6": + version "0.1.6" + resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-0.1.6.tgz#46a3e385e004aaebd16ecaa1da7d1af70ba3bb45" + integrity sha512-0uaRpRmTCQo5yMUkDpv4LEDnaQDoeLXcNNhZonCZdbZBQ7ntvjURIWIigq1/pXZp0UX7oPpFzsXD9jUp8JT0WA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "0.1.0" + "@radix-ui/react-compose-refs" "0.1.0" + "@radix-ui/react-context" "0.1.1" + "@radix-ui/react-id" "0.1.4" + "@radix-ui/react-popper" "0.1.3" + "@radix-ui/react-portal" "0.1.3" + "@radix-ui/react-presence" "0.1.1" + "@radix-ui/react-primitive" "0.1.3" + "@radix-ui/react-slot" "0.1.2" + "@radix-ui/react-use-controllable-state" "0.1.0" + "@radix-ui/react-use-escape-keydown" "0.1.0" + "@radix-ui/react-use-previous" "0.1.0" + "@radix-ui/react-use-rect" "0.1.1" + "@radix-ui/react-visually-hidden" "0.1.3" + "@radix-ui/react-use-body-pointer-events@0.1.0": version "0.1.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-body-pointer-events/-/react-use-body-pointer-events-0.1.0.tgz#29b211464493f8ca5149ce34b96b95abbc97d741" @@ -2206,6 +2258,43 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-use-previous@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-0.1.0.tgz#fed880d41187d0fdd1e19c4588402765f342777e" + integrity sha512-0fxNc33rYnCzDMPSiSnfS8YklnxQo8WqbAQXPAgIaaA1jRu2qFB916PL4qCIW+avcAAqFD38vWhqDqcVmBharA== + dependencies: + "@babel/runtime" "^7.13.10" + +"@radix-ui/react-use-rect@0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-0.1.1.tgz#6c15384beee59c086e75b89a7e66f3d2e583a856" + integrity sha512-kHNNXAsP3/PeszEmM/nxBBS9Jbo93sO+xuMTcRfwzXsmxT5gDXQzAiKbZQ0EecCPtJIzqvr7dlaQi/aP1PKYqQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/rect" "0.1.1" + +"@radix-ui/react-use-size@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-0.1.0.tgz#dc49295d646f5d3f570943dbb88bd94fc7db7daf" + integrity sha512-TcZAsR+BYI46w/RbaSFCRACl+Jh6mDqhu6GS2r0iuJpIVrj8atff7qtTjmMmfGtEDNEjhl7DxN3pr1nTS/oruQ== + dependencies: + "@babel/runtime" "^7.13.10" + +"@radix-ui/react-visually-hidden@0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-0.1.3.tgz#406a2f1e2f2cf27e5b85a29dc3aca718e695acaf" + integrity sha512-dPU6ZR2WQ/W9qv7E1Y8/I8ymqG+8sViU6dQQ6sfr2/8yGr0I4mmI7ywTnqXaE+YS9gHLEZHdQcEqTNESg6YfdQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-primitive" "0.1.3" + +"@radix-ui/rect@0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-0.1.1.tgz#95b5ba51f469bea6b1b841e2d427e17e37d38419" + integrity sha512-g3hnE/UcOg7REdewduRPAK88EPuLZtaq7sA9ouu8S+YEtnyFRI16jgv6GZYe3VMoQLL1T171ebmEPtDjyxWLzw== + dependencies: + "@babel/runtime" "^7.13.10" + "@reach/router@^1.2.1": version "1.3.4" resolved "https://registry.yarnpkg.com/@reach/router/-/router-1.3.4.tgz#d2574b19370a70c80480ed91f3da840136d10f8c" @@ -6333,7 +6422,7 @@ csstype@^2.5.7: resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.19.tgz#feeb5aae89020bb389e1f63669a5ed490e391caa" integrity sha512-ZVxXaNy28/k3kJg0Fou5MiYpp88j7H9hLZp8PDC3jV0WFjfH5E9xHb56L0W59cPbKbcHXeP4qyT8PrHp8t6LcQ== -csstype@^3.0.2: +csstype@^3.0.2, csstype@^3.0.4: version "3.0.10" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.10.tgz#2ad3a7bed70f35b965707c092e5f30b327c290e5" integrity sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==