feat: add tooltip support to SidebarMenuSubButton and update NavMain and NavSecondary components for improved accessibility
This commit is contained in:
@@ -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>/)
|
||||||
|
})
|
||||||
@@ -3,10 +3,9 @@
|
|||||||
import { ChevronRightIcon } from "lucide-react"
|
import { ChevronRightIcon } from "lucide-react"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { usePathname } from "next/navigation"
|
import { usePathname } from "next/navigation"
|
||||||
import { useEffect, useState } from "react"
|
import { useState } from "react"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
dashboardNavSectionHasActiveItem,
|
|
||||||
getDashboardNavSectionStorageKey,
|
getDashboardNavSectionStorageKey,
|
||||||
isDashboardNavItemActive,
|
isDashboardNavItemActive,
|
||||||
parseDashboardNavSectionOpenState,
|
parseDashboardNavSectionOpenState,
|
||||||
@@ -42,7 +41,6 @@ export function NavMain({
|
|||||||
}>
|
}>
|
||||||
}) {
|
}) {
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const hasActiveItem = dashboardNavSectionHasActiveItem(items, pathname)
|
|
||||||
const storageKey = getDashboardNavSectionStorageKey(sectionKey)
|
const storageKey = getDashboardNavSectionStorageKey(sectionKey)
|
||||||
const [open, setOpen] = useState(() => {
|
const [open, setOpen] = useState(() => {
|
||||||
if (typeof window === "undefined") {
|
if (typeof window === "undefined") {
|
||||||
@@ -51,19 +49,6 @@ export function NavMain({
|
|||||||
return parseDashboardNavSectionOpenState(window.localStorage.getItem(storageKey)) ?? true
|
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) => {
|
const handleOpenChange = (nextOpen: boolean) => {
|
||||||
setOpen(nextOpen)
|
setOpen(nextOpen)
|
||||||
window.localStorage.setItem(storageKey, String(nextOpen))
|
window.localStorage.setItem(storageKey, String(nextOpen))
|
||||||
@@ -80,7 +65,7 @@ export function NavMain({
|
|||||||
>
|
>
|
||||||
<CollapsibleTrigger render={<SidebarMenuButton tooltip={title} />}>
|
<CollapsibleTrigger render={<SidebarMenuButton tooltip={title} />}>
|
||||||
{icon}
|
{icon}
|
||||||
<span>{title}</span>
|
<span title={title}>{title}</span>
|
||||||
<ChevronRightIcon className="ml-auto transition-transform duration-200 group-data-open/collapsible:rotate-90" />
|
<ChevronRightIcon className="ml-auto transition-transform duration-200 group-data-open/collapsible:rotate-90" />
|
||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
<CollapsibleContent>
|
<CollapsibleContent>
|
||||||
@@ -90,8 +75,9 @@ export function NavMain({
|
|||||||
<SidebarMenuSubButton
|
<SidebarMenuSubButton
|
||||||
render={<Link href={item.url} />}
|
render={<Link href={item.url} />}
|
||||||
isActive={isDashboardNavItemActive(pathname, item.url)}
|
isActive={isDashboardNavItemActive(pathname, item.url)}
|
||||||
|
tooltip={item.title}
|
||||||
>
|
>
|
||||||
<span>{item.title}</span>
|
<span title={item.title}>{item.title}</span>
|
||||||
</SidebarMenuSubButton>
|
</SidebarMenuSubButton>
|
||||||
</SidebarMenuSubItem>
|
</SidebarMenuSubItem>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -37,9 +37,10 @@ export function NavSecondary({
|
|||||||
<SidebarMenuButton
|
<SidebarMenuButton
|
||||||
render={<Link href={item.url} />}
|
render={<Link href={item.url} />}
|
||||||
isActive={isActive(item.url)}
|
isActive={isActive(item.url)}
|
||||||
|
tooltip={item.title}
|
||||||
>
|
>
|
||||||
{item.icon}
|
{item.icon}
|
||||||
<span>{item.title}</span>
|
<span title={item.title}>{item.title}</span>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -668,14 +668,17 @@ function SidebarMenuSubButton({
|
|||||||
render,
|
render,
|
||||||
size = "md",
|
size = "md",
|
||||||
isActive = false,
|
isActive = false,
|
||||||
|
tooltip,
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: useRender.ComponentProps<"a"> &
|
}: useRender.ComponentProps<"a"> &
|
||||||
React.ComponentProps<"a"> & {
|
React.ComponentProps<"a"> & {
|
||||||
size?: "sm" | "md"
|
size?: "sm" | "md"
|
||||||
isActive?: boolean
|
isActive?: boolean
|
||||||
|
tooltip?: string | React.ComponentProps<typeof TooltipContent>
|
||||||
}) {
|
}) {
|
||||||
return useRender({
|
const { isMobile } = useSidebar()
|
||||||
|
const comp = useRender({
|
||||||
defaultTagName: "a",
|
defaultTagName: "a",
|
||||||
props: mergeProps<"a">(
|
props: mergeProps<"a">(
|
||||||
{
|
{
|
||||||
@@ -686,7 +689,7 @@ function SidebarMenuSubButton({
|
|||||||
},
|
},
|
||||||
props
|
props
|
||||||
),
|
),
|
||||||
render,
|
render: !tooltip ? render : <TooltipTrigger render={render} />,
|
||||||
state: {
|
state: {
|
||||||
slot: "sidebar-menu-sub-button",
|
slot: "sidebar-menu-sub-button",
|
||||||
sidebar: "menu-sub-button",
|
sidebar: "menu-sub-button",
|
||||||
@@ -694,6 +697,23 @@ function SidebarMenuSubButton({
|
|||||||
active: isActive,
|
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 {
|
export {
|
||||||
|
|||||||
Reference in New Issue
Block a user