Moved all 17 components from ui/ into atomic design tiers: atoms (Button, IconButton, Input, Textarea, Select, Checkbox, Radio, Switch, Badge, Tag, Chip, Tooltip) and molecules (Alert, Accordion, Card, Dialog, Popover). Updated all Storybook titles and cross-component imports. Changed Input icons to primary-dark and replaced palette token references with semantic tokens. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
103 lines
2.2 KiB
TypeScript
103 lines
2.2 KiB
TypeScript
import {
|
|
useState,
|
|
useRef,
|
|
cloneElement,
|
|
isValidElement,
|
|
type ReactElement,
|
|
type ReactNode,
|
|
} from 'react'
|
|
import {
|
|
useFloating,
|
|
useHover,
|
|
useFocus,
|
|
useDismiss,
|
|
useRole,
|
|
useInteractions,
|
|
offset,
|
|
flip,
|
|
shift,
|
|
arrow,
|
|
FloatingArrow,
|
|
FloatingPortal,
|
|
autoUpdate,
|
|
type Placement,
|
|
} from '@floating-ui/react'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
export interface TooltipProps {
|
|
content: ReactNode
|
|
placement?: Placement
|
|
delay?: number | { open?: number; close?: number }
|
|
children: ReactElement
|
|
className?: string
|
|
}
|
|
|
|
export function Tooltip({
|
|
content,
|
|
placement = 'top',
|
|
delay = { open: 400, close: 0 },
|
|
children,
|
|
className,
|
|
}: TooltipProps) {
|
|
const [open, setOpen] = useState(false)
|
|
const arrowRef = useRef(null)
|
|
|
|
const { refs, floatingStyles, context } = useFloating({
|
|
open,
|
|
onOpenChange: setOpen,
|
|
placement,
|
|
whileElementsMounted: autoUpdate,
|
|
middleware: [
|
|
offset(8),
|
|
flip({ fallbackAxisSideDirection: 'start' }),
|
|
shift({ padding: 8 }),
|
|
arrow({ element: arrowRef }),
|
|
],
|
|
})
|
|
|
|
const hover = useHover(context, { delay })
|
|
const focus = useFocus(context)
|
|
const dismiss = useDismiss(context)
|
|
const role = useRole(context, { role: 'tooltip' })
|
|
|
|
const { getReferenceProps, getFloatingProps } = useInteractions([
|
|
hover,
|
|
focus,
|
|
dismiss,
|
|
role,
|
|
])
|
|
|
|
if (!isValidElement(children)) return children
|
|
|
|
return (
|
|
<>
|
|
{cloneElement(children, {
|
|
ref: refs.setReference,
|
|
...getReferenceProps(),
|
|
} as Record<string, unknown>)}
|
|
{open && (
|
|
<FloatingPortal>
|
|
<div
|
|
ref={refs.setFloating}
|
|
style={floatingStyles}
|
|
className={cn(
|
|
'z-50 max-w-xs rounded-default bg-surface px-3 py-1.5 font-sans text-small text-text shadow-md',
|
|
className,
|
|
)}
|
|
{...getFloatingProps()}
|
|
>
|
|
{content}
|
|
<FloatingArrow
|
|
ref={arrowRef}
|
|
context={context}
|
|
className="fill-surface drop-shadow-sm"
|
|
width={12}
|
|
height={6}
|
|
/>
|
|
</div>
|
|
</FloatingPortal>
|
|
)}
|
|
</>
|
|
)
|
|
}
|