Match workspace filter menu styling (#2032)
* Match sidebar filter menu styling * Polish workspace menu layering
This commit is contained in:
parent
e35baaee5a
commit
589af3d615
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import { Activity, Check, GitBranch, ListFilter, FolderPlus, Server, X } from 'lucide-react'
|
||||
import { Check, FolderPlus, ListFilter, Server } from 'lucide-react'
|
||||
import { useAppStore } from '@/store'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
|
|
@ -16,7 +16,7 @@ import { searchRepos } from '@/lib/repo-search'
|
|||
import { cn } from '@/lib/utils'
|
||||
|
||||
const menuItemClassName =
|
||||
'rounded-[7px] px-2 py-1 text-[12px] leading-5 font-medium transition-colors hover:bg-black/8 hover:text-accent-foreground focus-visible:bg-black/8 focus-visible:text-accent-foreground focus-visible:outline-none dark:hover:bg-white/14 dark:focus-visible:bg-white/14'
|
||||
'relative flex cursor-default items-center gap-2 rounded-[7px] px-2 py-1 text-[12px] leading-5 font-medium outline-hidden select-none hover:bg-black/8 hover:text-accent-foreground focus-visible:bg-black/8 focus-visible:text-accent-foreground dark:hover:bg-white/14 dark:focus-visible:bg-white/14'
|
||||
|
||||
const SidebarFilter = React.memo(function SidebarFilter() {
|
||||
const showActiveOnly = useAppStore((s) => s.showActiveOnly)
|
||||
|
|
@ -84,7 +84,6 @@ const SidebarFilter = React.memo(function SidebarFilter() {
|
|||
}, [repos, setFilterRepoIds])
|
||||
|
||||
const clearRepos = useCallback(() => setFilterRepoIds([]), [setFilterRepoIds])
|
||||
const closeFilters = useCallback(() => handleOpenChange(false), [handleOpenChange])
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={handleOpenChange}>
|
||||
|
|
@ -119,52 +118,18 @@ const SidebarFilter = React.memo(function SidebarFilter() {
|
|||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<PopoverContent
|
||||
align="end"
|
||||
side="right"
|
||||
align="start"
|
||||
sideOffset={8}
|
||||
className="w-72 rounded-[11px] border-black/14 bg-[rgba(255,255,255,0.82)] p-1 text-black shadow-[0_16px_36px_rgba(0,0,0,0.24),inset_0_1px_0_rgba(255,255,255,0.14)] backdrop-blur-2xl dark:border-white/14 dark:bg-[rgba(0,0,0,0.72)] dark:text-white dark:shadow-[0_20px_44px_rgba(0,0,0,0.42),inset_0_1px_0_rgba(255,255,255,0.04)]"
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2 px-2 py-1">
|
||||
<span className="text-[11px] font-semibold text-muted-foreground">Filters</span>
|
||||
<div className="flex items-center gap-2">
|
||||
{hasAnyFilter ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={clearAll}
|
||||
className="inline-flex h-6 items-center rounded-[7px] px-2 text-[11px] font-medium text-muted-foreground transition-colors hover:bg-black/8 hover:text-accent-foreground focus-visible:bg-black/8 focus-visible:text-accent-foreground focus-visible:outline-none dark:hover:bg-white/14 dark:focus-visible:bg-white/14"
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
) : null}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
aria-label="Close filters"
|
||||
className="text-muted-foreground"
|
||||
onClick={closeFilters}
|
||||
>
|
||||
<X className="size-3.5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
Close filters
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="my-1 h-px bg-border/70" />
|
||||
|
||||
<div>
|
||||
<div className="space-y-0">
|
||||
<ToggleRow
|
||||
icon={<Activity className="size-3.5" />}
|
||||
label="Active only"
|
||||
checked={showActiveOnly}
|
||||
onClick={() => setShowActiveOnly(!showActiveOnly)}
|
||||
/>
|
||||
<ToggleRow
|
||||
icon={<GitBranch className="size-3.5" />}
|
||||
label="Hide default branch"
|
||||
checked={hideDefaultBranchWorkspace}
|
||||
onClick={() => setHideDefaultBranchWorkspace(!hideDefaultBranchWorkspace)}
|
||||
|
|
@ -175,10 +140,10 @@ const SidebarFilter = React.memo(function SidebarFilter() {
|
|||
<div>
|
||||
<div className="my-1 h-px bg-border/70" />
|
||||
<div className="flex items-center justify-between px-2 py-1">
|
||||
<span className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
|
||||
<span className="text-[11px] font-semibold text-muted-foreground">
|
||||
Repositories
|
||||
{hasRepoFilter && (
|
||||
<span className="ml-1.5 text-foreground normal-case tracking-normal">
|
||||
<span className="ml-1.5 font-medium text-foreground">
|
||||
{selectedCount} selected
|
||||
</span>
|
||||
)}
|
||||
|
|
@ -257,6 +222,19 @@ const SidebarFilter = React.memo(function SidebarFilter() {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{hasAnyFilter && (
|
||||
<>
|
||||
<div className="my-1 h-px bg-border/70" />
|
||||
<button
|
||||
type="button"
|
||||
onClick={clearAll}
|
||||
className={cn('w-full text-left text-muted-foreground', menuItemClassName)}
|
||||
>
|
||||
Reset filters
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Why: per design, "Add project" stays visible regardless of repo
|
||||
count so users can recover from the 0/1-repo state where the
|
||||
repo section is hidden. */}
|
||||
|
|
@ -280,13 +258,12 @@ const SidebarFilter = React.memo(function SidebarFilter() {
|
|||
})
|
||||
|
||||
type ToggleRowProps = {
|
||||
icon: React.ReactNode
|
||||
label: string
|
||||
checked: boolean
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
function ToggleRow({ icon, label, checked, onClick }: ToggleRowProps) {
|
||||
function ToggleRow({ label, checked, onClick }: ToggleRowProps) {
|
||||
// Why: the popover is not a true menu, so we use a plain button with
|
||||
// aria-pressed rather than role="menuitemcheckbox". The visible checkmark
|
||||
// carries the state for sighted users.
|
||||
|
|
@ -296,19 +273,15 @@ function ToggleRow({ icon, label, checked, onClick }: ToggleRowProps) {
|
|||
onClick={onClick}
|
||||
aria-pressed={checked}
|
||||
className={cn(
|
||||
'flex w-full items-center gap-2 text-left',
|
||||
menuItemClassName,
|
||||
'w-full pl-7 text-left',
|
||||
checked ? 'text-foreground' : 'text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
'size-3 shrink-0 text-muted-foreground',
|
||||
checked ? 'opacity-100' : 'opacity-0'
|
||||
)}
|
||||
/>
|
||||
<span className="text-muted-foreground">{icon}</span>
|
||||
<span className={cn(checked && 'text-foreground')}>{label}</span>
|
||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<Check className={cn('size-4', checked ? 'opacity-100' : 'opacity-0')} />
|
||||
</span>
|
||||
{label}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -224,6 +224,33 @@ const SidebarHeader = React.memo(function SidebarHeader() {
|
|||
setWorkspaceBoardMode('persistent')
|
||||
}, [clearWorkspaceBoardHoverClose, clearWorkspaceBoardHoverOpen, setWorkspaceBoardMode])
|
||||
|
||||
useEffect(() => {
|
||||
if (!workspaceBoardOpen) {
|
||||
return
|
||||
}
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent): void => {
|
||||
if (event.key !== 'Escape') {
|
||||
return
|
||||
}
|
||||
clearWorkspaceBoardHoverOpen()
|
||||
clearWorkspaceBoardHoverClose()
|
||||
workspaceBoardHoverSuppressedRef.current = workspaceHeaderHoveredRef.current
|
||||
event.preventDefault()
|
||||
setWorkspaceBoardMode('closed')
|
||||
}
|
||||
|
||||
// Why: the workspace board is a non-modal companion panel, so focus may
|
||||
// be outside the sheet when Escape should still dismiss it.
|
||||
document.addEventListener('keydown', handleKeyDown, true)
|
||||
return () => document.removeEventListener('keydown', handleKeyDown, true)
|
||||
}, [
|
||||
clearWorkspaceBoardHoverClose,
|
||||
clearWorkspaceBoardHoverOpen,
|
||||
setWorkspaceBoardMode,
|
||||
workspaceBoardOpen
|
||||
])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ function ContextMenuSubContent({
|
|||
<ContextMenuPrimitive.SubContent
|
||||
data-slot="context-menu-sub-content"
|
||||
className={cn(
|
||||
'z-50 min-w-[11rem] origin-(--radix-context-menu-content-transform-origin) overflow-hidden rounded-[11px] border border-black/14 bg-[rgba(255,255,255,0.10)] p-1 text-popover-foreground shadow-[0_16px_36px_rgba(0,0,0,0.24),inset_0_1px_0_rgba(255,255,255,0.14)] backdrop-blur-2xl dark:border-white/14 dark:bg-[rgba(0,0,0,0.12)] dark:shadow-[0_20px_44px_rgba(0,0,0,0.42),inset_0_1px_0_rgba(255,255,255,0.04)] data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
||||
'z-[70] min-w-[11rem] origin-(--radix-context-menu-content-transform-origin) overflow-hidden rounded-[11px] border border-black/14 bg-[rgba(255,255,255,0.10)] p-1 text-popover-foreground shadow-[0_16px_36px_rgba(0,0,0,0.24),inset_0_1px_0_rgba(255,255,255,0.14)] backdrop-blur-2xl dark:border-white/14 dark:bg-[rgba(0,0,0,0.12)] dark:shadow-[0_20px_44px_rgba(0,0,0,0.42),inset_0_1px_0_rgba(255,255,255,0.04)] data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -81,7 +81,7 @@ function ContextMenuContent({
|
|||
<ContextMenuPrimitive.Content
|
||||
data-slot="context-menu-content"
|
||||
className={cn(
|
||||
'z-50 max-h-(--radix-context-menu-content-available-height) min-w-[11rem] origin-(--radix-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-[11px] border border-black/14 bg-[rgba(255,255,255,0.10)] p-1 text-popover-foreground shadow-[0_16px_36px_rgba(0,0,0,0.24),inset_0_1px_0_rgba(255,255,255,0.14)] backdrop-blur-2xl dark:border-white/14 dark:bg-[rgba(0,0,0,0.12)] dark:shadow-[0_20px_44px_rgba(0,0,0,0.42),inset_0_1px_0_rgba(255,255,255,0.04)] data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
||||
'z-[70] max-h-(--radix-context-menu-content-available-height) min-w-[11rem] origin-(--radix-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-[11px] border border-black/14 bg-[rgba(255,255,255,0.10)] p-1 text-popover-foreground shadow-[0_16px_36px_rgba(0,0,0,0.24),inset_0_1px_0_rgba(255,255,255,0.14)] backdrop-blur-2xl dark:border-white/14 dark:bg-[rgba(0,0,0,0.12)] dark:shadow-[0_20px_44px_rgba(0,0,0,0.42),inset_0_1px_0_rgba(255,255,255,0.04)] data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ function DropdownMenuContent({
|
|||
data-slot="dropdown-menu-content"
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
'z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[11rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-[11px] border border-black/14 bg-[rgba(255,255,255,0.82)] p-1 text-black dark:text-white shadow-[0_16px_36px_rgba(0,0,0,0.24),inset_0_1px_0_rgba(255,255,255,0.14)] backdrop-blur-2xl dark:border-white/14 dark:bg-[rgba(0,0,0,0.72)] dark:shadow-[0_20px_44px_rgba(0,0,0,0.42),inset_0_1px_0_rgba(255,255,255,0.04)] data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
||||
'z-[70] max-h-(--radix-dropdown-menu-content-available-height) min-w-[11rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-[11px] border border-black/14 bg-[rgba(255,255,255,0.82)] p-1 text-black dark:text-white shadow-[0_16px_36px_rgba(0,0,0,0.24),inset_0_1px_0_rgba(255,255,255,0.14)] backdrop-blur-2xl dark:border-white/14 dark:bg-[rgba(0,0,0,0.72)] dark:shadow-[0_20px_44px_rgba(0,0,0,0.42),inset_0_1px_0_rgba(255,255,255,0.04)] data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
||||
className
|
||||
)}
|
||||
// Why: Electron's -webkit-app-region: drag on the titlebar captures
|
||||
|
|
@ -209,7 +209,7 @@ function DropdownMenuSubContent({
|
|||
<DropdownMenuPrimitive.SubContent
|
||||
data-slot="dropdown-menu-sub-content"
|
||||
className={cn(
|
||||
'z-50 min-w-[11rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-[11px] border border-black/14 bg-[rgba(255,255,255,0.82)] p-1 text-black dark:text-white shadow-[0_16px_36px_rgba(0,0,0,0.24),inset_0_1px_0_rgba(255,255,255,0.14)] backdrop-blur-2xl dark:border-white/14 dark:bg-[rgba(0,0,0,0.72)] dark:shadow-[0_20px_44px_rgba(0,0,0,0.42),inset_0_1px_0_rgba(255,255,255,0.04)] data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
||||
'z-[70] min-w-[11rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-[11px] border border-black/14 bg-[rgba(255,255,255,0.82)] p-1 text-black dark:text-white shadow-[0_16px_36px_rgba(0,0,0,0.24),inset_0_1px_0_rgba(255,255,255,0.14)] backdrop-blur-2xl dark:border-white/14 dark:bg-[rgba(0,0,0,0.72)] dark:shadow-[0_20px_44px_rgba(0,0,0,0.42),inset_0_1px_0_rgba(255,255,255,0.04)] data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
||||
className
|
||||
)}
|
||||
// Why: same no-drag fix as DropdownMenuContent — titlebar drag region
|
||||
|
|
|
|||
Loading…
Reference in New Issue