需实现的功能
目前的更多操作按钮的显示逻辑,为用户点击对应列表项后(isActive 为 true 时),才显示更多操作按钮。
需要添加,当用户的鼠标悬浮到侧边栏列表的每个列表项上时,也显示更多操作的按钮(一般是以三个点的图标表示),点击更多操作按钮显示展开操作的列表。
项目技术栈和组件库
- Next.js
- TailwindCSS
- Shadcn ui 的 DropdownMenu 相关组件 和 Button 组件
- cn 自定义函数,用来优化类名
实现思路
- 给更多操作按钮的父容器添加 类名
group/side - 给调用更多操作按钮的地方,添加 父容器
hover时的相关样式,未hover时 更多操作按钮的透明度为0,父容器hover时(group:hover/side),透明度为1 - 利用 cn 函数 加上
isActive时的判断,isActive为true时,透明度一样为1
修改前的代码:
<div className="relative mr-3 h-8">
{/* MoreOperationsDropdown component */}
<div className="absolute -right-2 top-0.5">{children}</div>
</div>
修改后的代码:
<div className="group/side relative mr-3 h-8">
{/* MoreOperationsDropdown component */}
<div
className={cn(
"absolute -right-2 top-0.5 opacity-0 group-hover/side:opacity-100",
isActive && "opacity-100",
)}
>
{children}
</div>
</div>
遇到的问题
按照实现思路中的代码进行修改后,发现当鼠标 hover 到列表的对应列表项上时,点击更多操作按钮显示操作列表后,hover 显示的更多操作按钮会 “消失”。
问题排查
尝试修改更多操作按钮的未 hover 时的透明度为 0.5,发现点击更多操作按钮后且显示操作菜单后,按钮显示的透明度样式从 hover 后的 1,变为 未 hover 时的样式,也就是透明度为 0.5。
思考后,觉得是展开菜单的问题,找到对应代码,把 DropdownConent 相关的代码进行注释,再次操作,操作菜单没有显示,发现按钮的透明度样式没有变化,还是 hover 后的样式。
将上面的注释打开后,打开网页开发者工具,查看更多操作按钮元素,观察显示操作菜单前后,更多操作按钮元素有无变化。
展开操作列表前:
<button
class="ring-offset-background focus-visible:ring-ring hover:bg-accent hover:text-accent-foreground x z-50 inline-flex h-full cursor-pointer items-center justify-center whitespace-nowrap rounded-md px-4 py-2 text-sm font-medium opacity-0 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-hover/side:opacity-100 peer-[[data-state=open]]:opacity-100"
type="button"
id="radix-:r37:"
aria-haspopup="menu"
aria-expanded="false"
data-state="closed"
>
<div class="flex items-center gap-0.5">
<span class="bg-foreground h-1 w-1 rounded-full"></span>
<span class="bg-foreground h-1 w-1 rounded-full"></span>
<span class="bg-foreground h-1 w-1 rounded-full"></span>
</div>
</button>
展开操作列表后:
<button
class="ring-offset-background focus-visible:ring-ring hover:bg-accent hover:text-accent-foreground x z-50 inline-flex h-full cursor-pointer items-center justify-center whitespace-nowrap rounded-md px-4 py-2 text-sm font-medium opacity-0 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-hover/side:opacity-100 peer-[[data-state=open]]:opacity-100"
type="button"
id="radix-:r37:"
aria-haspopup="menu"
aria-expanded="true"
data-state="open"
aria-controls="radix-:r38:"
>
<div class="flex items-center gap-0.5">
<span class="bg-foreground h-1 w-1 rounded-full"></span>
<span class="bg-foreground h-1 w-1 rounded-full"></span>
<span class="bg-foreground h-1 w-1 rounded-full"></span>
</div>
</button>
发现操作按钮上有三个属性,在展开列表后,发生了变化。
-
aria-expanded属性:- 第一个按钮的
aria-expanded属性值为false,表示菜单当前是关闭状态。 - 第二个按钮的
aria-expanded属性值为true,表示菜单当前是打开状态。
- 第一个按钮的
-
data-state属性:- 第一个按钮的
data-state属性值为closed,表示按钮处于关闭状态。 - 第二个按钮的
data-state属性值为open,表示按钮处于打开状态。
- 第一个按钮的
-
aria-controls属性:- 第一个按钮没有
aria-controls属性。 - 第二个按钮有
aria-controls="radix-:r38:"属性,指向一个ID为radix-:r38:的元素,通常是与按钮关联的菜单或弹出框。
- 第一个按钮没有
这些属性的不同主要用于辅助技术(如屏幕阅读器)和JavaScript,以便正确地识别和处理按钮的状态变化。具体来说:
aria-expanded: 用于指示按钮控制的内容(如菜单)是否可见。data-state: 通常用于JavaScript逻辑,以便根据按钮的状态执行不同的操作。aria-controls: 用于关联按钮和它控制的元素,增强可访问性。
解决办法
可以根据属性的值,设置指定属性的指定值时的样式。
用到了 TailwindCSS 中的样式写法,设置 当 aria-expanded 属性的值为 true 时,透明度也为 1,这样操作菜单展开后,更多操作按钮依旧会显示。
这边需要把对更多按钮显示的样式逻辑,直接写在了更多操作按钮上,而不是在更多操作菜单组件。
因为发现,写在引用处时,属性值设置样式不生效,因为这个属性只在更多操作按钮上,所以直接把显示逻辑写在了更多操作按钮上。对应的把 isActive 的值的获取 也放到了更多操作按钮组件的代码中。
<DropdownMenu>
<DropdownMenuTrigger asChild disabled={isDisabled}>
<Button
className={cn(
"z-50 h-full cursor-pointer py-2 opacity-0
group-hover/side:opacity-100 aria-expanded:opacity-100",
isActive && "opacity-100",
)}
>
<div className="flex items-center gap-0.5">
<span className="h-1 w-1 rounded-full bg-foreground" />
<span className="h-1 w-1 rounded-full bg-foreground" />
<span className="h-1 w-1 rounded-full bg-foreground" />
</div>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>
操作1
</DropdownMenuItem>
<DropdownMenuItem>
操作2
</DropdownMenuItem>
<DropdownMenuItem>
操作3
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
功能测试
作为用户进行使用,发现进行完一些操作后,页面刷新时,列表中的每个列表项会在一瞬间都显示更多操作按钮图标,但看起来样式不是 hover 前也不是 hover 后的。
猜测是因为页面刷新时,更多操作按钮被禁用,显示的是禁用状态时的样式。
再给更多操作按钮添加禁用状态时的样式 disabled:opacity-0,设置透明度为 0,最终解决了这个问题。
<Button
className={cn(
"z-50 h-full cursor-pointer py-2 opacity-0
group-hover/side:opacity-100 aria-expanded:opacity-100 disabled:opacity-0",
isActive && "opacity-100",
)}
>
<div className="flex items-center gap-0.5">
<span className="h-1 w-1 rounded-full bg-foreground" />
<span className="h-1 w-1 rounded-full bg-foreground" />
<span className="h-1 w-1 rounded-full bg-foreground" />
</div>
</Button>
鼠标点击更多操作按钮后,再点击其他地方关闭操作菜单,按钮的周围会显示一圈边框,觉得应该是 focus 的问题,查询了 Shadcn ui 的 DropdownMenu 组件的文档,发现 DropdownConent 有 几个 focus 相关的属性,感觉应该是 onCloseAutoFocus 属性,经过尝试,确实是 onCloseAutoFocus 属性,把该属性的方法的默认行为进行了禁用,这样点击更多操作按钮后,再点击其他地方,就不会有一圈边框在按钮周围了。
<DropdownMenuContent
onCloseAutoFocus={(e) => {
e.preventDefault();
}}
>
<DropdownMenuItem>
操作1
</DropdownMenuItem>
<DropdownMenuItem>
操作2
</DropdownMenuItem>
<DropdownMenuItem>
操作3
</DropdownMenuItem>
</DropdownMenuContent>
总结
通过上述的思路和实践的过程,最终实现需要完成的功能。如有更好的方法或建议,欢迎提供和指出。