From c1ea7168bfeca90e54cbd5fa4fadd66b60a63b9d Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Wed, 10 Jun 2026 13:33:37 +0700 Subject: [PATCH 1/2] [docs] Add rich Tooltip experiment Showcase header + keyboard shortcut + description using the existing Tooltip via composed title node and slot styling. No new component. --- docs/pages/experiments/rich-tooltip.tsx | 183 ++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 docs/pages/experiments/rich-tooltip.tsx diff --git a/docs/pages/experiments/rich-tooltip.tsx b/docs/pages/experiments/rich-tooltip.tsx new file mode 100644 index 00000000000000..6c7b877b24aead --- /dev/null +++ b/docs/pages/experiments/rich-tooltip.tsx @@ -0,0 +1,183 @@ +'use client'; +import * as React from 'react'; +import Box from '@mui/material/Box'; +import Stack from '@mui/material/Stack'; +import Tooltip from '@mui/material/Tooltip'; +import type { TooltipProps } from '@mui/material/Tooltip'; +import Typography from '@mui/material/Typography'; +import IconButton from '@mui/material/IconButton'; +import FileUploadOutlinedIcon from '@mui/icons-material/FileUploadOutlined'; +import ContentCopyIcon from '@mui/icons-material/ContentCopy'; +import SettingsIcon from '@mui/icons-material/Settings'; +import { createTheme, ThemeProvider, styled } from '@mui/material/styles'; + +const theme = createTheme({}); + +const Shortcut = styled('kbd')(({ theme: t }) => ({ + display: 'inline-flex', + alignItems: 'center', + gap: 2, + font: 'inherit', + fontSize: t.typography.pxToRem(12), + lineHeight: 1, + padding: '4px 8px', + borderRadius: 6, + color: (t.vars || t).palette.common.white, + backgroundColor: t.alpha((t.vars || t).palette.common.white, 0.16), +})); + +function RichTitle({ + title, + shortcut, + description, +}: { + title: string; + shortcut?: React.ReactNode; + description: string; +}) { + return ( + // Inner wrapper owns rounding + clipping + elevation so the Tooltip slot + // stays unclipped — otherwise `overflow: hidden` would clip the arrow, + // which renders as a child of the tooltip positioned outside its box. + + + + {title} + + {shortcut ? {shortcut} : null} + + + {description} + + + ); +} + +const richSlotProps: TooltipProps['slotProps'] = { + tooltip: { + sx: { + p: 0, + maxWidth: 320, + bgcolor: 'transparent', + color: 'text.primary', + }, + }, + arrow: { + // Match the dark header, not the body surface. + sx: { color: 'grey.900' }, + }, +}; + +const triggerSx = { + border: '1px solid', + borderColor: 'divider', + borderRadius: 1.5, +} as const; + +export default function App() { + return ( + + + + Rich Tooltip — header + shortcut + description (existing Tooltip, no new component) + + + + + + P + + } + description="Upload the latest reviewed model version to Autodesk Construction Cloud." + /> + } + > + + + + + + + Hover the icons below (default open state off) + + + + + + C + + } + description="Copy a shareable link to this item to your clipboard." + /> + } + > + + + + + + + + , + + } + description="Open project settings to manage members, permissions, and integrations." + /> + } + > + + + + + + + + ); +} From 2fcaba3b21ef8dc7711d1b49ed7c4201f21c358e Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Wed, 10 Jun 2026 15:13:57 +0700 Subject: [PATCH 2/2] [docs] Add RichTooltip custom slot variant Refactor the Settings example to a custom tooltip slot component (slots={tooltip}), forwarding className/style/ref, re-applying the slot's placement margins, and coloring the arrow from the slot. --- docs/pages/experiments/rich-tooltip.tsx | 101 ++++++++++++++++++------ 1 file changed, 75 insertions(+), 26 deletions(-) diff --git a/docs/pages/experiments/rich-tooltip.tsx b/docs/pages/experiments/rich-tooltip.tsx index 6c7b877b24aead..53095b34f3f55d 100644 --- a/docs/pages/experiments/rich-tooltip.tsx +++ b/docs/pages/experiments/rich-tooltip.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import Box from '@mui/material/Box'; import Stack from '@mui/material/Stack'; -import Tooltip from '@mui/material/Tooltip'; +import Tooltip, { tooltipClasses } from '@mui/material/Tooltip'; import type { TooltipProps } from '@mui/material/Tooltip'; import Typography from '@mui/material/Typography'; import IconButton from '@mui/material/IconButton'; @@ -26,19 +26,19 @@ const Shortcut = styled('kbd')(({ theme: t }) => ({ backgroundColor: t.alpha((t.vars || t).palette.common.white, 0.16), })); -function RichTitle({ +function RichCard({ title, shortcut, description, }: { - title: string; + title: React.ReactNode; shortcut?: React.ReactNode; - description: string; + description?: React.ReactNode; }) { return ( - // Inner wrapper owns rounding + clipping + elevation so the Tooltip slot - // stays unclipped — otherwise `overflow: hidden` would clip the arrow, - // which renders as a child of the tooltip positioned outside its box. + // Rounding/clipping/elevation live here, not on the tooltip slot — the arrow + // is a sibling of this card at the slot level, so slot-level `overflow: hidden` + // would clip it. {shortcut ? {shortcut} : null} - - {description} - + {description ? ( + + {description} + + ) : null} ); } @@ -79,6 +81,58 @@ const richSlotProps: TooltipProps['slotProps'] = { }, }; +// Replaces the default tooltip slot. Mirrors the popper-placement margins + +// transform-origins from Tooltip's own slot (lost when swapping the component) +// and colors the arrow to match the dark header. +const RichTooltipRoot = styled('div')(({ theme: t }) => ({ + position: 'relative', + maxWidth: 320, + margin: 2, + color: (t.vars || t).palette.text.primary, + [`& .${tooltipClasses.arrow}`]: { + color: (t.vars || t).palette.grey[900], + }, + [`.${tooltipClasses.popper}[data-popper-placement*="left"] &`]: { + transformOrigin: 'right center', + marginInlineEnd: '14px', + }, + [`.${tooltipClasses.popper}[data-popper-placement*="right"] &`]: { + transformOrigin: 'left center', + marginInlineStart: '14px', + }, + [`.${tooltipClasses.popper}[data-popper-placement*="top"] &`]: { + transformOrigin: 'center bottom', + marginBottom: '14px', + }, + [`.${tooltipClasses.popper}[data-popper-placement*="bottom"] &`]: { + transformOrigin: 'center top', + marginTop: '14px', + }, +})); + +interface RichTooltipProps extends React.HTMLAttributes { + shortcut?: React.ReactNode; + description?: React.ReactNode; +} + +const RichTooltip = React.forwardRef(function RichTooltip( + { children, shortcut, description, ...other }, + ref, +) { + // Tooltip appends the arrow as a slot child after `title`. Split it back out so + // the title fills the card while the arrow stays a sibling (kept unclipped). + const items = React.Children.toArray(children); + const arrow = items.find((child) => React.isValidElement(child)); + const title = items.filter((child) => !React.isValidElement(child)); + + return ( + + + {arrow} + + ); +}); + const triggerSx = { border: '1px solid', borderColor: 'divider', @@ -110,7 +164,7 @@ export default function App() { placement="top" slotProps={{ ...richSlotProps, arrow: { sx: { color: 'white' } } }} title={ - @@ -138,7 +192,7 @@ export default function App() { placement="bottom" slotProps={richSlotProps} title={ - @@ -157,20 +211,15 @@ export default function App() { - - , - - } - description="Open project settings to manage members, permissions, and integrations." - /> - } + title="Settings" // only "Settings" is announced by screen readers. + slots={{ tooltip: RichTooltip }} + slotProps={{ + tooltip: { + shortcut: '⌘,', + description: + 'Open project settings to manage members, permissions, and integrations.', + } as RichTooltipProps, + }} >