Files
supabase/apps/docs/features/ui/InfoTooltip.tsx
T
Gildas GarciaandIvan Vasilov 96d43099bb chore: refactor Button API so that it can be used a standard button (#46880)
## Problem

Our `<Button>` component breaks the default `button` contract by
redefining the `type` prop to set its variant (`primary`, `default`,
etc) instead of the button type (`submit`, `button`, etc).
This is confusing and forces to write more code when using it with
shadcn components that expect/inject the standard button props.

## Solution

- rename the `type` prop to `variant`
- rename the `htmlType` prop to `type`
- propagate the changes where necessary
- format code

## How to test

As this is just prop renaming, if it builds it's ok

---------

Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
2026-06-16 23:59:58 +02:00

128 lines
3.7 KiB
TypeScript

'use client'
import { useBreakpoint } from 'common'
import { InfoIcon, XIcon } from 'lucide-react'
import React, {
useCallback,
useEffect,
useId,
useRef,
useState,
type PropsWithChildren,
} from 'react'
import { ErrorBoundary } from 'react-error-boundary'
import {
Button,
cn,
CommandEmpty,
Sheet,
SheetContent,
SheetHeader,
Tooltip,
TooltipContent,
TooltipTrigger,
} from 'ui'
interface PopUpProps extends PropsWithChildren {
tooltipContent: React.ReactNode
className?: string
contentContainerClassName?: string
}
const buttonClassName = cn(
'relative px-1 py-0 -my-px',
'rounded-sm bg-surface-200 border border-dashed',
'transition-colors hover:border-strong group/inline-popup'
)
const InfoTooltip = ({
children,
className,
tooltipContent,
contentContainerClassName,
}: PopUpProps) => {
const id = useId().replaceAll(':', '')
const timeout = useRef<ReturnType<typeof setTimeout> | null>(null)
const [mobileSheetOpen, setMobileSheetOpen] = useState(false)
const [tooltipOpen, _setTooltipOpen] = useState(false)
const isMobile = useBreakpoint('md')
const setTooltipOpen = useCallback(
(open: boolean) => {
_setTooltipOpen(open)
setMobileSheetOpen(true)
timeout.current = setTimeout(() => {
if (isMobile) return
const targetElem: HTMLElement | null = document.querySelector(`#tooltip-content-${id}`)
targetElem?.focus()
})
},
[_setTooltipOpen, id, isMobile]
)
useEffect(() => {
return () => {
if (timeout.current) {
clearTimeout(timeout.current)
}
}
}, [])
return (
<>
<Tooltip open={tooltipOpen} onOpenChange={(open) => !isMobile && setTooltipOpen(open)}>
<TooltipTrigger asChild>
<span
role="button"
tabIndex={0}
onClick={() => setMobileSheetOpen(true)}
className={cn(buttonClassName, className)}
>
{children}
<InfoIcon
aria-hidden={true}
className="absolute p-px bg-background rounded-full -left-1.5 -top-1.5 w-3 h-3 text-foreground-lighter group-hover/inline-popup:text-foreground-light transition-colors"
/>
</span>
</TooltipTrigger>
<TooltipContent id={`tooltip-content-${id}`} className={contentContainerClassName}>
{tooltipContent}
</TooltipContent>
</Tooltip>
{isMobile && (
<Sheet open={mobileSheetOpen} onOpenChange={setMobileSheetOpen}>
<SheetContent
id={`mobile-sheet-content-${id}`}
showClose={false}
size="full"
side="bottom"
className={cn(
'md:hidden',
'text-foreground-lighter',
'rounded-t-lg overflow-hidden overflow-y-scroll',
'w-full h-fit min-h-[200px] py-2 px-4'
)}
>
<ErrorBoundary FallbackComponent={() => <CommandEmpty />}>
<SheetHeader className="flex items-center justify-between gap-2 px-0 py-2 mb-2 max-w-full">
<div className="flex items-center gap-2 max-w-[90%]">
<InfoIcon className="p-px min-w-4 min-h-4 text-foreground-lighter" />
<p className="italic text-foreground-light truncate">{children}</p>
</div>
<Button variant="text" onClick={() => setMobileSheetOpen(false)} className="px-1">
<XIcon className="w-4 h-4 text-foreground-lighter" />
</Button>
</SheetHeader>
{tooltipContent}
</ErrorBoundary>
</SheetContent>
</Sheet>
)}
</>
)
}
export default InfoTooltip