Refactor dropdown menu components in various files to prevent event propagation, enhancing user interaction. Update CountedLineTabs component documentation with preview links for better clarity. Improve styling and layout in resource page and user columns for consistency and visual appeal.
Build and Push Telemt Panel Docker Image / build-and-push (push) Successful in 2m8s
Build and Push Telemt Panel Docker Image / create-release (push) Skipped

This commit is contained in:
Denozordec
2026-08-04 22:06:15 +07:00
parent 3c065e8de1
commit ee3e96647e
5 changed files with 115 additions and 75 deletions
+18 -3
View File
@@ -17,7 +17,11 @@ interface CountedLineTabsProps {
children?: ReactNode
}
/** Line tabs with count pills (c-tabs-2 / data-grid-filtering-2). */
/**
* Line tabs with count pills — ReUI PRO DNA.
* Preview: https://reui.io/preview/base/components/c-tabs-2
* Lists: https://reui.io/preview/base/data-grid-filtering-2
*/
export function CountedLineTabs({
tabs,
value,
@@ -28,12 +32,23 @@ export function CountedLineTabs({
}: CountedLineTabsProps) {
return (
<Tabs value={value} onValueChange={onValueChange} className={className}>
<TabsList variant="line" className={cn('gap-5', listClassName)}>
<TabsList
variant="line"
className={cn(
'h-auto w-full justify-start gap-5 rounded-none bg-transparent p-0',
listClassName,
)}
>
{tabs.map((tab) => (
<TabsTrigger
key={tab.id}
value={tab.id}
className="text-muted-foreground hover:text-foreground h-auto gap-2 px-0 pb-3 after:bottom-0"
className={cn(
'text-muted-foreground hover:text-foreground h-auto flex-none gap-2 rounded-none px-0 pb-3 shadow-none',
'data-active:bg-transparent data-active:text-foreground data-active:shadow-none',
'dark:data-active:border-transparent dark:data-active:bg-transparent',
'after:bottom-0 after:bg-foreground',
)}
>
<span>{tab.label}</span>
{tab.count !== undefined ? (
@@ -251,26 +251,32 @@ function RowActions({
onOpen: (row: TelemtErrorRow) => void
}) {
return (
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button
size="icon"
variant="ghost"
className="size-7"
aria-label="Действия"
/>
}
>
<MoreHorizontalIcon aria-hidden />
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-40">
<DropdownMenuItem onClick={() => onOpen(row.original)}>
<EyeIcon className="size-4" aria-hidden />
Открыть
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<div
className="flex items-center justify-center"
onClick={(event) => event.stopPropagation()}
onPointerDown={(event) => event.stopPropagation()}
>
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button
size="icon"
variant="ghost"
className="size-7"
aria-label="Действия"
/>
}
>
<MoreHorizontalIcon aria-hidden />
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-40">
<DropdownMenuItem onClick={() => onOpen(row.original)}>
<EyeIcon className="size-4" aria-hidden />
Открыть
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
)
}
@@ -570,14 +570,13 @@ function ResourcePageFiltered<T extends object>({
<FramePanel className="p-0 shadow-none!">
{countedTabs.length > 0 ? (
<>
<div className="px-(--frame-panel-header-px) pt-(--frame-panel-header-py)">
<div className="border-border flex flex-col gap-0 border-b px-(--frame-panel-header-px) pt-(--frame-panel-header-py)">
<CountedLineTabs
tabs={countedTabs}
value={activeTab}
onValueChange={handleTabChange}
/>
</div>
<Separator />
</>
) : null}
@@ -1269,7 +1269,21 @@ function DataGridTableBodyRow<TData>({
data-depth={row.depth || undefined}
data-row-pinned={isRowPinned || undefined}
data-row-pinned-boundary={pinnedBoundary}
onClick={() => props.onRowClick && props.onRowClick(row.original)}
onClick={(event) => {
if (!props.onRowClick) return
// Interactive controls (actions menu, pin, checkbox, links) must not
// open the row detail — same contract as DataGridTableRowPin/Select.
const target = event.target
if (
target instanceof Element &&
target.closest(
'button, a, input, textarea, select, [role="button"], [role="menuitem"], [role="checkbox"], [data-slot="dropdown-menu-trigger"], [data-slot="checkbox"]'
)
) {
return
}
props.onRowClick(row.original)
}}
className={cn(
"hover:bg-muted/40 data-[state=selected]:bg-muted/50",
props.onRowClick && "cursor-pointer",
+55 -49
View File
@@ -199,55 +199,61 @@ export function ActionsCell({
return (
<>
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button
size="icon"
variant="ghost"
className="size-7"
aria-label="Действия строки"
/>
}
>
<MoreHorizontalIcon aria-hidden="true" />
</DropdownMenuTrigger>
<DropdownMenuContent side="bottom" align="start" className="w-44">
<DropdownMenuGroup>
<DropdownMenuItem onClick={() => row.pin(isPinned ? false : 'top')}>
{isPinned ? (
<PinOffIcon className="size-4" aria-hidden="true" />
) : (
<Pin className="size-4" aria-hidden="true" />
)}
{isPinned ? 'Открепить' : 'Закрепить'}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => onOpen(row.original)}>
<EyeIcon className="size-4" aria-hidden="true" />
Открыть
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
copyToClipboard(row.original.username)
toast.success('Имя скопировано', {
description: row.original.username,
})
}}
>
<CopyIcon className="size-4" aria-hidden="true" />
Копировать имя
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
variant="destructive"
onClick={() => setDeleteOpen(true)}
>
<Trash2Icon className="size-4" aria-hidden="true" />
Удалить
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
<div
className="flex items-center justify-center"
onClick={(event) => event.stopPropagation()}
onPointerDown={(event) => event.stopPropagation()}
>
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button
size="icon"
variant="ghost"
className="size-7"
aria-label="Действия строки"
/>
}
>
<MoreHorizontalIcon aria-hidden="true" />
</DropdownMenuTrigger>
<DropdownMenuContent side="bottom" align="start" className="w-44">
<DropdownMenuGroup>
<DropdownMenuItem onClick={() => row.pin(isPinned ? false : 'top')}>
{isPinned ? (
<PinOffIcon className="size-4" aria-hidden="true" />
) : (
<Pin className="size-4" aria-hidden="true" />
)}
{isPinned ? 'Открепить' : 'Закрепить'}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => onOpen(row.original)}>
<EyeIcon className="size-4" aria-hidden="true" />
Открыть
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
copyToClipboard(row.original.username)
toast.success('Имя скопировано', {
description: row.original.username,
})
}}
>
<CopyIcon className="size-4" aria-hidden="true" />
Копировать имя
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
variant="destructive"
onClick={() => setDeleteOpen(true)}
>
<Trash2Icon className="size-4" aria-hidden="true" />
Удалить
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
<AlertDialog open={deleteOpen} onOpenChange={setDeleteOpen}>
<AlertDialogContent size="sm">