Skip to content

Commit b8922bd

Browse files
authored
Merge pull request #13630 from ethereum/shadcn-tooltip
Shadcn migration - tooltip
2 parents 91abfc0 + 268b993 commit b8922bd

File tree

9 files changed

+145
-79
lines changed

9 files changed

+145
-79
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"@radix-ui/react-radio-group": "^1.2.0",
4747
"@radix-ui/react-slot": "^1.1.0",
4848
"@radix-ui/react-switch": "^1.1.0",
49+
"@radix-ui/react-tooltip": "^1.1.2",
4950
"@radix-ui/react-visually-hidden": "^1.1.0",
5051
"@sentry/nextjs": "^8.19.0",
5152
"@socialgouv/matomo-next": "^1.8.0",

src/components/Glossary/GlossaryTooltip/GlossaryTooltip.stories.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { Center } from "@chakra-ui/react"
22
import { Meta, StoryObj } from "@storybook/react"
33

4+
import { TooltipProvider } from "@/components/ui/tooltip"
5+
46
import GlossaryTooltipComponent from "."
57

68
const meta = {
@@ -13,7 +15,9 @@ const meta = {
1315
decorators: [
1416
(Story) => (
1517
<Center boxSize="md">
16-
<Story />
18+
<TooltipProvider>
19+
<Story />
20+
</TooltipProvider>
1721
</Center>
1822
),
1923
],
@@ -28,6 +32,6 @@ export const Basic: Story = {}
2832
// for chromatic story snapshot showing the rendered popover
2933
export const OnOpen: Story = {
3034
args: {
31-
isOpen: true,
35+
open: true,
3236
},
3337
}

src/components/Glossary/GlossaryTooltip/index.tsx

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import React, { ReactNode } from "react"
22
import { useRouter } from "next/router"
3-
import { Box, Text, VStack } from "@chakra-ui/react"
43

5-
import Heading from "@/components/Heading"
64
import InlineLink from "@/components/Link"
75
import Tooltip, { type TooltipProps } from "@/components/Tooltip"
86
import Translation from "@/components/Translation"
@@ -23,36 +21,36 @@ const GlossaryTooltip = ({
2321
const { asPath } = useRouter()
2422

2523
return (
26-
<Box as="span" display="inline-block">
24+
<span className="inline-block">
2725
<Tooltip
2826
{...props}
2927
content={
30-
<VStack spacing={2} align="stretch" textAlign="start">
31-
<Heading as="h6">
28+
<div className="flex flex-col items-stretch gap-2 text-start">
29+
<h6>
3230
<Translation
3331
id={termKey + "-term"}
3432
options={{ ns: "glossary-tooltip" }}
3533
// Override the default `a` tag transformation to avoid circular
3634
// dependency issues
3735
transform={{ a: InlineLink }}
3836
/>
39-
</Heading>
37+
</h6>
4038
{/**
4139
* `as="span"` prevents hydration warnings for strings that contain
4240
* elements that cannot be nested inside `p` tags, like `ul` tags
4341
* (found in some Glossary definition).
4442
* TODO: Develop a better solution to handle this case.
4543
*/}
46-
<Text as="span">
44+
<span>
4745
<Translation
4846
id={termKey + "-definition"}
4947
options={{ ns: "glossary-tooltip" }}
5048
// Override the default `a` tag transformation to avoid circular
5149
// dependency issues
5250
transform={{ a: InlineLink }}
5351
/>
54-
</Text>
55-
</VStack>
52+
</span>
53+
</div>
5654
}
5755
onBeforeOpen={() => {
5856
trackCustomEvent({
@@ -62,20 +60,11 @@ const GlossaryTooltip = ({
6260
})
6361
}}
6462
>
65-
<Text
66-
as="u"
67-
textDecorationStyle="dotted"
68-
textUnderlineOffset="3px"
69-
_hover={{
70-
textDecorationColor: "primary.hover",
71-
color: "primary.hover",
72-
}}
73-
cursor="help"
74-
>
63+
<u className="cursor-help decoration-dotted underline-offset-3 hover:text-primary-hover hover:decoration-primary-hover">
7564
{children}
76-
</Text>
65+
</u>
7766
</Tooltip>
78-
</Box>
67+
</span>
7968
)
8069
}
8170

src/components/Tooltip/Tooltip.stories.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Meta, StoryObj } from "@storybook/react"
44

55
import InlineLink from "../Link"
66
import Translation from "../Translation"
7+
import { TooltipProvider } from "../ui/tooltip"
78

89
// TODO: remove `index` when we delete the old tooltip
910
import TooltipComponent from "./index"
@@ -46,7 +47,9 @@ const meta = {
4647
decorators: [
4748
(Story) => (
4849
<Center boxSize="md">
49-
<Story />
50+
<TooltipProvider>
51+
<Story />
52+
</TooltipProvider>
5053
</Center>
5154
),
5255
],
@@ -61,6 +64,6 @@ export const Basic: Story = {}
6164
// for chromatic story snapshot showing the rendered popover
6265
export const OnOpen: Story = {
6366
args: {
64-
isOpen: true,
67+
open: true,
6568
},
6669
}

src/components/Tooltip/index.tsx

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
import React, { ReactNode, useEffect } from "react"
2-
import {
3-
Popover,
4-
PopoverArrow,
5-
PopoverBody,
6-
PopoverContent,
7-
PopoverProps,
8-
PopoverTrigger,
9-
Portal,
10-
useDisclosure,
11-
} from "@chakra-ui/react"
1+
import React, { ComponentProps, ReactNode, useEffect } from "react"
122

133
import { isMobile } from "@/lib/utils/isMobile"
144

15-
export interface TooltipProps extends PopoverProps {
5+
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"
6+
import {
7+
Tooltip as Tooltipcomponent,
8+
TooltipContent,
9+
TooltipTrigger,
10+
} from "../ui/tooltip"
11+
12+
import { useDisclosure } from "@/hooks/useDisclosure"
13+
import { useIsClient } from "@/hooks/useIsClient"
14+
15+
export type TooltipProps = ComponentProps<typeof Popover> & {
1616
content: ReactNode
1717
children?: ReactNode
1818
onBeforeOpen?: () => void
@@ -22,9 +22,10 @@ const Tooltip = ({
2222
content,
2323
children,
2424
onBeforeOpen,
25-
...rest
25+
...props
2626
}: TooltipProps) => {
2727
const { isOpen, onOpen, onClose } = useDisclosure()
28+
const isClient = useIsClient()
2829

2930
// Close the popover when the user scrolls.
3031
// This is useful for mobile devices where the popover is open by clicking the
@@ -57,24 +58,39 @@ const Tooltip = ({
5758
onOpen()
5859
}
5960

61+
const handleOpenChange = (open: boolean) => {
62+
if (open) {
63+
handleOpen()
64+
} else {
65+
onClose()
66+
}
67+
}
68+
69+
// Avoid rendering on the server since the user can't interact with it and we
70+
// need to use different components depending on the device
71+
if (!isClient) {
72+
return null
73+
}
74+
75+
// Use Popover on mobile devices since the user can't hover
76+
const Component = isMobile() ? Popover : Tooltipcomponent
77+
const Trigger = isMobile() ? PopoverTrigger : TooltipTrigger
78+
const Content = isMobile() ? PopoverContent : TooltipContent
79+
6080
return (
61-
<Popover
62-
isOpen={isOpen}
63-
onOpen={handleOpen}
64-
onClose={onClose}
65-
placement="top"
66-
trigger={isMobile() ? "click" : "hover"}
67-
gutter={8}
68-
{...rest}
69-
>
70-
<PopoverTrigger>{children}</PopoverTrigger>
71-
<Portal>
72-
<PopoverContent data-testid="tooltip-popover">
73-
<PopoverArrow />
74-
<PopoverBody>{content}</PopoverBody>
75-
</PopoverContent>
76-
</Portal>
77-
</Popover>
81+
<Component open={isOpen} onOpenChange={handleOpenChange} {...props}>
82+
<Trigger className="focus-visible:rounded-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-hover">
83+
{children}
84+
</Trigger>
85+
<Content
86+
side="top"
87+
sideOffset={2}
88+
className="w-80 px-5 text-sm"
89+
data-testid="tooltip-popover"
90+
>
91+
{content}
92+
</Content>
93+
</Component>
7894
)
7995
}
8096

src/components/ui/popover.tsx

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,28 @@ const PopoverTrigger = PopoverPrimitive.Trigger
1010
const PopoverContent = React.forwardRef<
1111
React.ElementRef<typeof PopoverPrimitive.Content>,
1212
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
13-
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
14-
<PopoverPrimitive.Portal>
15-
<PopoverPrimitive.Content
16-
ref={ref}
17-
align={align}
18-
sideOffset={sideOffset}
19-
className={cn(
20-
"text-popover-foreground z-popover w-72 rounded-md border bg-background p-4 shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
21-
className
22-
)}
23-
{...props}
24-
/>
25-
</PopoverPrimitive.Portal>
26-
))
13+
>(
14+
(
15+
{ className, children, align = "center", sideOffset = 4, ...props },
16+
ref
17+
) => (
18+
<PopoverPrimitive.Portal>
19+
<PopoverPrimitive.Content
20+
ref={ref}
21+
align={align}
22+
sideOffset={sideOffset}
23+
className={cn(
24+
"text-popover-foreground z-popover w-72 rounded border border-background-highlight bg-background-highlight p-4 shadow-lg outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
25+
className
26+
)}
27+
{...props}
28+
>
29+
{children}
30+
<PopoverPrimitive.Arrow className="z-popover fill-background-highlight" />
31+
</PopoverPrimitive.Content>
32+
</PopoverPrimitive.Portal>
33+
)
34+
)
2735
PopoverContent.displayName = PopoverPrimitive.Content.displayName
2836

2937
const PopoverClose = PopoverPrimitive.Close

src/components/ui/tooltip.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import * as React from "react"
2+
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
3+
4+
import { cn } from "@/lib/utils/cn"
5+
6+
const TooltipProvider = TooltipPrimitive.Provider
7+
8+
const Tooltip = TooltipPrimitive.Root
9+
10+
const TooltipTrigger = TooltipPrimitive.Trigger
11+
12+
const TooltipContent = React.forwardRef<
13+
React.ElementRef<typeof TooltipPrimitive.Content>,
14+
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
15+
>(({ children, className, sideOffset = 4, ...props }, ref) => (
16+
<TooltipPrimitive.Content
17+
ref={ref}
18+
sideOffset={sideOffset}
19+
className={cn(
20+
"z-popover overflow-hidden rounded-md border border-background-highlight bg-background-highlight p-4 text-sm text-body shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
21+
className
22+
)}
23+
{...props}
24+
>
25+
{children}
26+
<TooltipPrimitive.Arrow className="z-popover fill-background-highlight" />
27+
</TooltipPrimitive.Content>
28+
))
29+
TooltipContent.displayName = TooltipPrimitive.Content.displayName
30+
31+
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }

src/pages/_app.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useEffect } from "react"
22
import { appWithTranslation } from "next-i18next"
3+
import { TooltipProvider } from "@radix-ui/react-tooltip"
34
import { init } from "@socialgouv/matomo-next"
45

56
import { AppPropsWithLayout } from "@/lib/types"
@@ -25,17 +26,17 @@ const App = ({ Component, pageProps }: AppPropsWithLayout) => {
2526
const getLayout = Component.getLayout ?? ((page) => page)
2627

2728
return (
28-
<>
29-
<ThemeProvider>
29+
<ThemeProvider>
30+
<TooltipProvider>
3031
<BaseLayout
3132
contentIsOutdated={!!pageProps.frontmatter?.isOutdated}
3233
contentNotTranslated={pageProps.contentNotTranslated}
3334
lastDeployLocaleTimestamp={pageProps.lastDeployLocaleTimestamp}
3435
>
3536
{getLayout(<Component {...pageProps} />)}
3637
</BaseLayout>
37-
</ThemeProvider>
38-
</>
38+
</TooltipProvider>
39+
</ThemeProvider>
3940
)
4041
}
4142

yarn.lock

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4501,6 +4501,24 @@
45014501
"@radix-ui/react-use-previous" "1.1.0"
45024502
"@radix-ui/react-use-size" "1.1.0"
45034503

4504+
"@radix-ui/react-tooltip@^1.1.2":
4505+
version "1.1.2"
4506+
resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-1.1.2.tgz#c42db2ffd7dcc6ff3d65407c8cb70490288f518d"
4507+
integrity sha512-9XRsLwe6Yb9B/tlnYCPVUd/TFS4J7HuOZW345DCeC6vKIxQGMZdx21RK4VoZauPD5frgkXTYVS5y90L+3YBn4w==
4508+
dependencies:
4509+
"@radix-ui/primitive" "1.1.0"
4510+
"@radix-ui/react-compose-refs" "1.1.0"
4511+
"@radix-ui/react-context" "1.1.0"
4512+
"@radix-ui/react-dismissable-layer" "1.1.0"
4513+
"@radix-ui/react-id" "1.1.0"
4514+
"@radix-ui/react-popper" "1.2.0"
4515+
"@radix-ui/react-portal" "1.1.1"
4516+
"@radix-ui/react-presence" "1.1.0"
4517+
"@radix-ui/react-primitive" "2.0.0"
4518+
"@radix-ui/react-slot" "1.1.0"
4519+
"@radix-ui/react-use-controllable-state" "1.1.0"
4520+
"@radix-ui/react-visually-hidden" "1.1.0"
4521+
45044522
"@radix-ui/react-use-callback-ref@1.0.1":
45054523
version "1.0.1"
45064524
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz#f4bb1f27f2023c984e6534317ebc411fc181107a"
@@ -13079,7 +13097,7 @@ prelude-ls@^1.2.1:
1307913097
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
1308013098
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
1308113099

13082-
"prettier-fallback@npm:prettier@^3":
13100+
"prettier-fallback@npm:prettier@^3", prettier@^3.1.1:
1308313101
version "3.3.2"
1308413102
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.2.tgz#03ff86dc7c835f2d2559ee76876a3914cec4a90a"
1308513103
integrity sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==
@@ -13094,11 +13112,6 @@ prettier@^2.0.5, prettier@^2.8.8:
1309413112
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
1309513113
integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
1309613114

13097-
prettier@^3.1.1:
13098-
version "3.3.2"
13099-
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.2.tgz#03ff86dc7c835f2d2559ee76876a3914cec4a90a"
13100-
integrity sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==
13101-
1310213115
prettier@^3.3.3:
1310313116
version "3.3.3"
1310413117
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105"

0 commit comments

Comments
 (0)