feat: add tooltip support to SidebarMenuSubButton and update NavMain and NavSecondary components for improved accessibility

This commit is contained in:
mlogclub
2026-05-30 20:50:14 +08:00
parent 7e6fcc45ac
commit 57995e352b
4 changed files with 39 additions and 21 deletions
+11
View File
@@ -0,0 +1,11 @@
import assert from "node:assert/strict"
import { readFile } from "node:fs/promises"
import test from "node:test"
const source = await readFile(new URL("./nav-main.tsx", import.meta.url), "utf8")
test("dashboard nav items expose full labels when visual text is truncated", () => {
assert.match(source, /<span\s+title=\{title\}>\{title\}<\/span>/)
assert.match(source, /tooltip=\{item\.title\}/)
assert.match(source, /<span\s+title=\{item\.title\}>\{item\.title\}<\/span>/)
})
+4 -18
View File
@@ -3,10 +3,9 @@
import { ChevronRightIcon } from "lucide-react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { useEffect, useState } from "react"
import { useState } from "react"
import {
dashboardNavSectionHasActiveItem,
getDashboardNavSectionStorageKey,
isDashboardNavItemActive,
parseDashboardNavSectionOpenState,
@@ -42,7 +41,6 @@ export function NavMain({
}>
}) {
const pathname = usePathname()
const hasActiveItem = dashboardNavSectionHasActiveItem(items, pathname)
const storageKey = getDashboardNavSectionStorageKey(sectionKey)
const [open, setOpen] = useState(() => {
if (typeof window === "undefined") {
@@ -51,19 +49,6 @@ export function NavMain({
return parseDashboardNavSectionOpenState(window.localStorage.getItem(storageKey)) ?? true
})
useEffect(() => {
const storedOpen = parseDashboardNavSectionOpenState(
window.localStorage.getItem(storageKey)
)
if (storedOpen !== undefined) {
setOpen(storedOpen)
return
}
if (hasActiveItem) {
setOpen(true)
}
}, [hasActiveItem, storageKey])
const handleOpenChange = (nextOpen: boolean) => {
setOpen(nextOpen)
window.localStorage.setItem(storageKey, String(nextOpen))
@@ -80,7 +65,7 @@ export function NavMain({
>
<CollapsibleTrigger render={<SidebarMenuButton tooltip={title} />}>
{icon}
<span>{title}</span>
<span title={title}>{title}</span>
<ChevronRightIcon className="ml-auto transition-transform duration-200 group-data-open/collapsible:rotate-90" />
</CollapsibleTrigger>
<CollapsibleContent>
@@ -90,8 +75,9 @@ export function NavMain({
<SidebarMenuSubButton
render={<Link href={item.url} />}
isActive={isDashboardNavItemActive(pathname, item.url)}
tooltip={item.title}
>
<span>{item.title}</span>
<span title={item.title}>{item.title}</span>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
))}
+2 -1
View File
@@ -37,9 +37,10 @@ export function NavSecondary({
<SidebarMenuButton
render={<Link href={item.url} />}
isActive={isActive(item.url)}
tooltip={item.title}
>
{item.icon}
<span>{item.title}</span>
<span title={item.title}>{item.title}</span>
</SidebarMenuButton>
</SidebarMenuItem>
))}
+22 -2
View File
@@ -668,14 +668,17 @@ function SidebarMenuSubButton({
render,
size = "md",
isActive = false,
tooltip,
className,
...props
}: useRender.ComponentProps<"a"> &
React.ComponentProps<"a"> & {
size?: "sm" | "md"
isActive?: boolean
tooltip?: string | React.ComponentProps<typeof TooltipContent>
}) {
return useRender({
const { isMobile } = useSidebar()
const comp = useRender({
defaultTagName: "a",
props: mergeProps<"a">(
{
@@ -686,7 +689,7 @@ function SidebarMenuSubButton({
},
props
),
render,
render: !tooltip ? render : <TooltipTrigger render={render} />,
state: {
slot: "sidebar-menu-sub-button",
sidebar: "menu-sub-button",
@@ -694,6 +697,23 @@ function SidebarMenuSubButton({
active: isActive,
},
})
if (!tooltip) {
return comp
}
if (typeof tooltip === "string") {
tooltip = {
children: tooltip,
}
}
return (
<Tooltip>
{comp}
<TooltipContent side="right" align="center" hidden={isMobile} {...tooltip} />
</Tooltip>
)
}
export {