Skip to content

Commit 8feec78

Browse files
authored
Merge pull request #13456 from ethereum/shad-feedback-widget
Migrate FeedbackWidget to shadcn
2 parents af516e8 + ca5da4c commit 8feec78

File tree

13 files changed

+329
-209
lines changed

13 files changed

+329
-209
lines changed

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"@hookform/resolvers": "^3.8.0",
3737
"@radix-ui/react-accordion": "^1.2.0",
3838
"@radix-ui/react-navigation-menu": "^1.2.0",
39+
"@radix-ui/react-popover": "^1.1.1",
3940
"@radix-ui/react-slot": "^1.1.0",
4041
"@socialgouv/matomo-next": "^1.8.0",
4142
"chart.js": "^4.4.2",
@@ -120,7 +121,8 @@
120121
"tsconfig-paths-webpack-plugin": "4.1.0",
121122
"typescript": "^5.5.2",
122123
"unified": "^10.0.0",
123-
"unist-util-visit": "^5.0.0"
124+
"unist-util-visit": "^5.0.0",
125+
"usehooks-ts": "^3.1.0"
124126
},
125127
"resolutions": {
126128
"jackspeak": "2.1.1",
Lines changed: 51 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,60 @@
1+
import { forwardRef } from "react"
12
import { useTranslation } from "next-i18next"
2-
import { Button, ButtonProps, ScaleFade, Text } from "@chakra-ui/react"
3+
import type { ButtonHTMLAttributes } from "react"
4+
5+
import { cn } from "@/lib/utils/cn"
36

47
import { FeedbackGlyphIcon } from "../icons"
58

6-
type FixedDotProps = ButtonProps & {
7-
bottomOffset: number
9+
import { Button } from "@/../tailwind/ui/buttons/Button"
10+
11+
type FixedDotProps = ButtonHTMLAttributes<HTMLButtonElement> & {
812
isExpanded: boolean
13+
offsetBottom?: boolean
14+
suppressScale?: boolean
915
}
10-
const FixedDot = ({ bottomOffset, isExpanded, ...props }: FixedDotProps) => {
11-
const { t } = useTranslation("common")
12-
const size = "12"
13-
return (
14-
<Button
15-
data-testid="feedback-widget-button"
16-
h={size}
17-
w={{ base: size, lg: isExpanded ? "15rem" : size }}
18-
borderRadius="full"
19-
boxShadow="tableItemBox"
20-
position="sticky"
21-
bottom={{ base: `${bottomOffset + 1}rem`, lg: 4 }}
22-
color="white"
23-
ms="auto"
24-
me="4"
25-
mt={{ lg: "inherit" }}
26-
zIndex={98} /* Below the mobile menu */
27-
display="flex"
28-
alignItems="center"
29-
_hover={{
30-
transform: "scale(1.1)",
31-
transition: "transform 0.2s ease-in-out",
32-
}}
33-
transition="transform 0.2s ease-in-out, width 0.25s ease-in-out,
34-
border-radius 0.25s linear"
35-
aria-label={t("feedback-widget")}
36-
leftIcon={<FeedbackGlyphIcon color="white" />}
37-
iconSpacing={{ base: 0, lg: "3" }}
38-
sx={{
39-
".chakra-button__icon": {
40-
me: !isExpanded ? 0 : undefined,
41-
},
42-
}}
43-
{...props}
44-
>
45-
<ScaleFade in={isExpanded} delay={0.25}>
46-
<Text
47-
as="span"
48-
fontWeight="bold"
49-
noOfLines={2}
50-
height="100%"
51-
alignItems="center"
52-
display={{ base: "none", lg: isExpanded ? "flex" : "none" }}
16+
17+
const FixedDot = forwardRef<HTMLButtonElement, FixedDotProps>(
18+
({ offsetBottom, isExpanded, suppressScale, className, ...props }, ref) => {
19+
const { t } = useTranslation("common")
20+
return (
21+
<Button
22+
ref={ref}
23+
data-testid="feedback-widget-button"
24+
aria-label={t("feedback-widget")}
25+
className={cn(
26+
"lg:mt-inherit sticky bottom-4 z-20 me-4 ms-auto flex size-12 items-center gap-0 rounded-full text-white shadow-table-item-box",
27+
"transition-all duration-200 hover:shadow-none hover:transition-transform hover:duration-200",
28+
!suppressScale && "hover:scale-110",
29+
offsetBottom && "bottom-31 lg:bottom-4",
30+
isExpanded ? "lg:w-60 lg:gap-3" : "lg:w-12",
31+
className
32+
)}
33+
{...props}
34+
>
35+
<FeedbackGlyphIcon
36+
className={cn("text-white", !isExpanded && "-mx-1")}
37+
/>
38+
<div
39+
className={cn(
40+
"duration-250 transform transition-all",
41+
isExpanded ? "scale-100 opacity-100" : "scale-95 opacity-0"
42+
)}
5343
>
54-
{t("feedback-widget-prompt")}
55-
</Text>
56-
</ScaleFade>
57-
</Button>
58-
)
59-
}
44+
<span
45+
className={cn(
46+
"line-clamp-2 hidden h-full items-center font-bold text-white",
47+
isExpanded && "lg:flex"
48+
)}
49+
>
50+
{t("feedback-widget-prompt")}
51+
</span>
52+
</div>
53+
</Button>
54+
)
55+
}
56+
)
57+
58+
FixedDot.displayName = "FixedDot"
6059

6160
export default FixedDot

src/components/FeedbackWidget/index.tsx

Lines changed: 75 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,24 @@
11
import { useTranslation } from "next-i18next"
2+
import { MdClose } from "react-icons/md"
3+
24
import {
3-
AlertDialog,
4-
AlertDialogBody,
5-
AlertDialogCloseButton,
6-
AlertDialogContent,
7-
AlertDialogFooter,
8-
AlertDialogHeader,
9-
AlertDialogOverlay,
10-
Box,
11-
Button,
12-
HStack,
13-
} from "@chakra-ui/react"
5+
Popover,
6+
PopoverClose,
7+
PopoverContent,
8+
PopoverTrigger,
9+
} from "../ui/popover"
1410

1511
import FixedDot from "./FixedDot"
1612
import { useFeedbackWidget } from "./useFeedbackWidget"
1713

14+
import { Button } from "@/../tailwind/ui/buttons/Button"
15+
1816
const FeedbackWidget = () => {
1917
const { t } = useTranslation("common")
2018
const {
21-
bottomOffset,
19+
offsetBottom,
2220
cancelRef,
2321
feedbackSubmitted,
24-
getButtonProps,
2522
handleClose,
2623
handleOpen,
2724
handleSubmit,
@@ -31,99 +28,74 @@ const FeedbackWidget = () => {
3128
} = useFeedbackWidget()
3229
return (
3330
<>
34-
<FixedDot
35-
{...getButtonProps()}
36-
onClick={handleOpen}
37-
bottomOffset={bottomOffset}
38-
isExpanded={isExpanded}
39-
/>
40-
41-
<AlertDialog
42-
isOpen={isOpen}
43-
leastDestructiveRef={cancelRef}
44-
onClose={handleClose}
45-
isCentered
31+
<Popover
32+
onOpenChange={(open) => (open ? handleOpen : handleClose)()}
33+
open={isOpen}
4634
>
47-
<AlertDialogOverlay>
48-
<AlertDialogContent
49-
position="fixed"
50-
maxW={1504}
51-
m="auto"
52-
alignItems="flex-end"
53-
backgroundColor="transparent"
54-
me={24}
55-
bottom={{ base: `${bottomOffset + 5}rem`, lg: 20 }}
56-
data-testid="feedback-widget-modal"
57-
padding={0}
58-
>
59-
<Box
60-
w="min(320px, calc(100% - 1rem))"
61-
mx="2"
62-
bg="background.base"
63-
borderRadius="base"
64-
padding={{ base: "4", sm: "8" }}
65-
>
66-
<HStack>
67-
<AlertDialogHeader fontSize="xl" fontWeight="bold" me="0">
68-
{feedbackSubmitted
69-
? t("feedback-widget-thank-you-title")
70-
: t("feedback-widget-prompt")}
71-
</AlertDialogHeader>
72-
<AlertDialogCloseButton alignSelf="start" />
73-
</HStack>
35+
<PopoverTrigger asChild>
36+
<FixedDot
37+
offsetBottom={offsetBottom}
38+
isExpanded={isExpanded}
39+
suppressScale={isOpen}
40+
/>
41+
</PopoverTrigger>
42+
43+
<PopoverContent className="mx-2 w-80 max-w-[calc(100vw_-_1rem)] rounded bg-background p-4 sm:p-8">
44+
<div className="flex items-start gap-2">
45+
<header className="me-0 flex-1 p-0 text-xl font-bold">
46+
{feedbackSubmitted
47+
? t("feedback-widget-thank-you-title")
48+
: t("feedback-widget-prompt")}
49+
</header>
50+
<PopoverClose asChild>
51+
<Button
52+
variant="ghost"
53+
className="w-8 py-0 text-body"
54+
size="sm"
55+
ref={cancelRef}
56+
>
57+
<MdClose className="h-fit w-5" />
58+
</Button>
59+
</PopoverClose>
60+
</div>
7461

75-
{/* Body: */}
76-
{feedbackSubmitted && (
77-
<>
78-
<AlertDialogBody
79-
fontWeight="normal"
80-
fontSize="md"
81-
lineHeight="5"
82-
textAlign="center"
83-
>
84-
{t("feedback-widget-thank-you-subtitle")}
85-
</AlertDialogBody>
86-
<AlertDialogBody
87-
fontWeight="bold"
88-
fontSize="xs"
89-
lineHeight="4"
90-
letterSpacing="wide"
91-
color="searchBorder"
92-
textAlign="center"
93-
>
94-
{t("feedback-widget-thank-you-timing")}
95-
</AlertDialogBody>
96-
</>
97-
)}
62+
{feedbackSubmitted && (
63+
<>
64+
<div className="text-center text-md font-normal leading-5">
65+
{t("feedback-widget-thank-you-subtitle")}
66+
</div>
67+
<div className="text-center text-xs font-bold leading-4 tracking-wide text-body-medium">
68+
{t("feedback-widget-thank-you-timing")}
69+
</div>
70+
</>
71+
)}
9872

99-
<AlertDialogFooter display="flex" gap="6">
100-
{feedbackSubmitted ? (
101-
<Button onClick={handleSurveyOpen} flex={1}>
102-
{t("feedback-widget-thank-you-cta")}
103-
</Button>
104-
) : (
105-
<>
106-
<Button
107-
variant="solid"
108-
onClick={() => handleSubmit(true)}
109-
flex={1}
110-
>
111-
{t("yes")}
112-
</Button>
113-
<Button
114-
variant="solid"
115-
onClick={() => handleSubmit(false)}
116-
flex={1}
117-
>
118-
{t("no")}
119-
</Button>
120-
</>
121-
)}
122-
</AlertDialogFooter>
123-
</Box>
124-
</AlertDialogContent>
125-
</AlertDialogOverlay>
126-
</AlertDialog>
73+
<footer className="mt-8 flex gap-6">
74+
{feedbackSubmitted ? (
75+
<Button onClick={handleSurveyOpen} className="flex-1">
76+
{t("feedback-widget-thank-you-cta")}
77+
</Button>
78+
) : (
79+
<>
80+
<Button
81+
variant="solid"
82+
onClick={() => handleSubmit(true)}
83+
className="flex-1"
84+
>
85+
{t("yes")}
86+
</Button>
87+
<Button
88+
variant="solid"
89+
onClick={() => handleSubmit(false)}
90+
className="flex-1"
91+
>
92+
{t("no")}
93+
</Button>
94+
</>
95+
)}
96+
</footer>
97+
</PopoverContent>
98+
</Popover>
12799
</>
128100
)
129101
}

src/components/FeedbackWidget/useFeedbackWidget.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { useEffect, useMemo, useRef, useState } from "react"
22
import { useRouter } from "next/router"
3-
import { useDisclosure } from "@chakra-ui/react"
43

54
import { trackCustomEvent } from "@/lib/utils/matomo"
65

6+
import useDisclosure from "@/hooks/useDisclosure"
77
import { useSurvey } from "@/hooks/useSurvey"
88

99
export const useFeedbackWidget = () => {
@@ -12,7 +12,7 @@ export const useFeedbackWidget = () => {
1212
const [isExpanded, setIsExpanded] = useState(false)
1313
const [feedbackSubmitted, setFeedbackSubmitted] = useState(false)
1414

15-
const { getButtonProps, isOpen, onClose, onOpen } = useDisclosure()
15+
const { isOpen, onClose, onOpen } = useDisclosure()
1616

1717
const cancelRef = useRef<HTMLButtonElement>(null)
1818

@@ -29,16 +29,15 @@ export const useFeedbackWidget = () => {
2929

3030
const surveyUrl = useSurvey(feedbackSubmitted)
3131

32-
const bottomOffset = useMemo(() => {
32+
const offsetBottom = useMemo(() => {
3333
const pathsWithBottomNav = ["/staking", "/dao", "/defi", "/nft"]
34-
const CONDITIONAL_OFFSET = 6.75
35-
let offset = 0
34+
let shouldOffset = false
3635
pathsWithBottomNav.forEach((path) => {
3736
if (asPath.includes(path)) {
38-
offset = CONDITIONAL_OFFSET
37+
shouldOffset = true
3938
}
4039
})
41-
return offset
40+
return shouldOffset
4241
}, [asPath])
4342

4443
const handleClose = (): void => {
@@ -80,10 +79,9 @@ export const useFeedbackWidget = () => {
8079
}
8180

8281
return {
83-
bottomOffset,
82+
offsetBottom,
8483
cancelRef,
8584
feedbackSubmitted,
86-
getButtonProps,
8785
handleClose,
8886
handleOpen,
8987
handleSubmit,

0 commit comments

Comments
 (0)