fix(sw): add default tooltip for pill tool buttons

- SWToolButton appearance="pill": a non-blank string children is also
  the native title; an explicit title wins and title="" turns it off;
  numbers, JSX and arrays are never converted. text / document keep
  passing only the given title. Accessible name, DOM and classes unchanged
- DESIGN_RULES §15: Header UX principles (ellipsis full text, icon-only
  tool buttons, print class shared by pill, font-dependent widths, scope)
- Reference them from DESIGN_RULES §3·§9, COMPONENT_CATALOG §4·§11,
  AI_USAGE §5.4, SW Header spec §17 and Step Header spec §12
This commit is contained in:
shkim4 committed 2026-11-05 15:55:23 +09:00
1 parent e70082550e
commit 22841affaf
6 files changed
+73 -8

No files matched your search

@@ -27,7 +27,7 @@ interface GroupContext { selection: "none" | "single" }
const SWToolGroupContext = createContext<GroupContext | null>(null)
export const SWToolButton = forwardRef<HTMLButtonElement, SWToolButtonProps>(function SWToolButton(
{ appearance = "text", pressed, children, icon, hoverIcon, className, type = "button", ...rest }, ref,
{ appearance = "text", pressed, children, icon, hoverIcon, className, type = "button", title, ...rest }, ref,
) {
const group = useContext(SWToolGroupContext)
const radio = group?.selection === "single"
@@ -39,15 +39,19 @@ export const SWToolButton = forwardRef<HTMLButtonElement, SWToolButtonProps>(fun
// The wrapper is the Header flex item; [data-tool] keeps it at its content width (not the file-name clamp).
return (
<div className="hm-sw-header__document" data-tool="">
<button ref={ref} type={type} className={classes("hm-sw-header__document-button")} data-selected={pressed ? "true" : undefined} {...state} {...rest}>
<button ref={ref} type={type} className={classes("hm-sw-header__document-button")} data-selected={pressed ? "true" : undefined} title={title} {...state} {...rest}>
<span className="hm-sw-header__document-name">{children}</span>
</button>
</div>
)
}
if (appearance === "pill") {
// Default tooltip (2026-11-05): the pill shows only its icon, so a non-blank string `children` (its accessible name)
// is also the native `title`, as written. An explicit `title` wins and `title=""` turns it off; numbers and JSX
// children are never converted. The accessible name is unchanged (children / aria-label / aria-labelledby).
const tooltip = title ?? (typeof children === "string" && children.trim() !== "" ? children : undefined)
return (
<button ref={ref} type={type} className={classes("hm-sw-header__action hm-sw-header__action--print hm-sw-tool__pill-button")} data-selected={pressed ? "true" : undefined} {...state} {...rest}>
<button ref={ref} type={type} className={classes("hm-sw-header__action hm-sw-header__action--print hm-sw-tool__pill-button")} data-selected={pressed ? "true" : undefined} title={tooltip} {...state} {...rest}>
{/* hm-sw-header__icon is the "hide on hover" half of the sw.css icon swap: only with a hover picture to show. */}
{icon !== undefined && <span className={hoverIcon !== undefined ? "hm-sw-header__icon hm-sw-tool__icon" : "hm-sw-tool__icon"} aria-hidden="true">{icon}</span>}
{hoverIcon !== undefined && <span className="hm-sw-header__icon-hover hm-sw-tool__icon" aria-hidden="true">{hoverIcon}</span>}
@@ -56,7 +60,7 @@ export const SWToolButton = forwardRef<HTMLButtonElement, SWToolButtonProps>(fun
)
}
return (
<button ref={ref} type={type} className={classes("hm-sw-header__toggle hm-sw-tool__text-button")} data-selected={pressed ? "true" : undefined} {...state} {...rest}>
<button ref={ref} type={type} className={classes("hm-sw-header__toggle hm-sw-tool__text-button")} data-selected={pressed ? "true" : undefined} title={title} {...state} {...rest}>
{children}
</button>
)