{title}
diff --git a/apps/web/src/components/reui-kit/resource-page.tsx b/apps/web/src/components/reui-kit/resource-page.tsx
index f9d9599..90c4084 100644
--- a/apps/web/src/components/reui-kit/resource-page.tsx
+++ b/apps/web/src/components/reui-kit/resource-page.tsx
@@ -1,4 +1,4 @@
-import { useCallback, useMemo, useState, useEffect, useRef, type ReactNode } from 'react'
+import { useCallback, useMemo, useState, type ReactNode } from 'react'
import {
getCoreRowModel,
getPaginationRowModel,
@@ -11,6 +11,7 @@ import {
} from '@tanstack/react-table'
import { FilterIcon, FunnelXIcon } from 'lucide-react'
+import { CountedLineTabs } from '@/components/counted-line-tabs'
import { Badge } from '@/components/reui/badge'
import { DataGrid } from '@/components/reui/data-grid/data-grid'
import { DataGridPagination } from '@/components/reui/data-grid/data-grid-pagination'
@@ -32,11 +33,9 @@ import {
import { Button } from '@cfdm/ui/components/button'
import { Separator } from '@cfdm/ui/components/separator'
import { Skeleton } from '@cfdm/ui/components/skeleton'
-import { Tabs, TabsList, TabsTrigger } from '@cfdm/ui/components/tabs'
import { Alert, AlertDescription, AlertTitle } from '@cfdm/ui/components/alert'
import { EmptyState } from '@/components/empty-state'
import { applyFiltersToData } from './filter-utils'
-import { debugAgentLog } from '@/lib/debug-agent-log'
export interface ResourcePageTab {
id: string
@@ -122,7 +121,6 @@ export function ResourcePage
({
toolbarExtra,
hideHeader = false,
}: ResourcePageProps) {
- const frameRef = useRef(null)
const [internalTab, setInternalTab] = useState(tabs?.[0]?.id ?? 'all')
const activeTab = controlledTab ?? internalTab
@@ -207,21 +205,15 @@ export function ResourcePage({
resetPagination()
}, [onClearFilters, resetPagination])
- useEffect(() => {
- if (isLoading || isError || (data.length === 0 && emptyState)) return
- const el = frameRef.current
- if (!el) return
- debugAgentLog(
- 'resource-page.tsx:mount',
- 'resource page width',
- {
- title,
- clientWidth: el.clientWidth,
- maxWidth: getComputedStyle(el).maxWidth,
- },
- 'A',
- )
- }, [title, isLoading, isError, data.length, emptyState])
+ const countedTabs = useMemo(
+ () =>
+ (tabs ?? []).map((tab) => ({
+ id: tab.id,
+ label: tab.label,
+ count: tabCounts[tab.id] ?? tab.count ?? 0,
+ })),
+ [tabs, tabCounts],
+ )
if (isLoading) {
return
@@ -256,7 +248,7 @@ export function ResourcePage({
const emptyMessage = 'Нет записей по выбранным фильтрам.'
return (
-
+
{selectionToolbar && selectedCount > 0
? selectionToolbar({
selectedIds,
@@ -306,26 +298,14 @@ export function ResourcePage
({
) : null}
- {tabs && tabs.length > 0 ? (
+ {countedTabs.length > 0 ? (
<>
- {/* Line tabs: c-tabs-2 underline; after:bottom-0 — FramePanel overflow-hidden клипает bottom-[-5px] */}
-
-
- {tabs.map((tab) => (
-
- {tab.label}
-
- {tabCounts[tab.id] ?? tab.count ?? 0}
-
-
- ))}
-
-
+
>
diff --git a/apps/web/src/components/reui-kit/settings-shell.tsx b/apps/web/src/components/reui-kit/settings-shell.tsx
index 506f8ad..7ec36c7 100644
--- a/apps/web/src/components/reui-kit/settings-shell.tsx
+++ b/apps/web/src/components/reui-kit/settings-shell.tsx
@@ -1,11 +1,10 @@
-import { useEffect, useRef, type ReactNode } from 'react'
+import type { ReactNode } from 'react'
import { Link, Outlet, useRouterState } from '@tanstack/react-router'
import { SettingsIcon } from 'lucide-react'
import { useIsMobile } from '@cfdm/ui/hooks/use-mobile'
import { cn } from '@cfdm/ui/lib/utils'
import { PageShell } from '@/components/page-shell'
-import { debugAgentLog } from '@/lib/debug-agent-log'
export interface SettingsTabConfig {
id: string
@@ -35,28 +34,11 @@ export function SettingsShell({
tabs = DEFAULT_TABS,
}: SettingsShellProps) {
const isMobile = useIsMobile()
- const rootRef = useRef(null)
const pathname = useRouterState({ select: (s) => s.location.pathname })
- useEffect(() => {
- const el = rootRef.current
- if (!el) return
- debugAgentLog(
- 'settings-shell.tsx:mount',
- 'settings shell width',
- {
- pathname,
- clientWidth: el.clientWidth,
- maxWidth: getComputedStyle(el).maxWidth,
- className: el.className,
- },
- 'A',
- )
- }, [pathname])
-
return (
-
+
{title}
@@ -78,7 +60,7 @@ export function SettingsShell({
className={cn(
'flex gap-1',
isMobile
- ? 'scrollbar-none -mx-1 overflow-x-auto pb-1'
+ ? 'scrollbar-none -mx-1 overflow-x-auto overflow-y-hidden pb-1'
: 'w-44 shrink-0 flex-col',
)}
>
diff --git a/apps/web/src/components/services/services-grouped-catalog.tsx b/apps/web/src/components/services/services-grouped-catalog.tsx
index eea4ba0..4caa9dc 100644
--- a/apps/web/src/components/services/services-grouped-catalog.tsx
+++ b/apps/web/src/components/services/services-grouped-catalog.tsx
@@ -27,6 +27,7 @@ import {
FramePanel,
FrameTitle,
} from '@/components/reui/frame'
+import { CountedLineTabs } from '@/components/counted-line-tabs'
import { EmptyState } from '@/components/empty-state'
import { applyFiltersToData } from '@/components/reui-kit/filter-utils'
import { createServicesGroupedColumns } from '@/components/services/services-grouped-columns'
@@ -53,7 +54,6 @@ import {
} from '@cfdm/ui/components/dropdown-menu'
import { Separator } from '@cfdm/ui/components/separator'
import { Skeleton } from '@cfdm/ui/components/skeleton'
-import { Tabs, TabsList, TabsTrigger } from '@cfdm/ui/components/tabs'
const HEALTH_TABS = [
{ id: 'health-ok', label: 'OK' },
@@ -359,22 +359,15 @@ export function ServicesGroupedCatalog({
-
-
- {ALL_TABS.map((t) => (
-
- {t.label}
-
- {tabCounts[t.id] ?? 0}
-
-
- ))}
-
-
+ ({
+ id: t.id,
+ label: t.label,
+ count: tabCounts[t.id] ?? 0,
+ }))}
+ value={tab}
+ onValueChange={setTab}
+ />
diff --git a/packages/ui/src/components/tabs.tsx b/packages/ui/src/components/tabs.tsx
index 8c914b0..c07b824 100644
--- a/packages/ui/src/components/tabs.tsx
+++ b/packages/ui/src/components/tabs.tsx
@@ -67,8 +67,8 @@ function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
"group-data-[variant=line]/tabs-list:flex-none group-data-[variant=line]/tabs-list:rounded-none group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:shadow-none group-data-[variant=line]/tabs-list:data-active:bg-transparent group-data-[variant=line]/tabs-list:data-active:shadow-none dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
"group-data-[variant=default]/tabs-list:data-active:bg-background group-data-[variant=default]/tabs-list:data-active:text-foreground dark:group-data-[variant=default]/tabs-list:data-active:border-input dark:group-data-[variant=default]/tabs-list:data-active:bg-input/30 dark:group-data-[variant=default]/tabs-list:data-active:text-foreground",
"data-active:text-foreground",
- // Line underline (c-tabs-2): size/position need data-horizontal on root; active → opacity-100
- "after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
+ // Line underline (c-tabs-2): bottom-0 so FramePanel overflow-hidden does not clip
+ "after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-0 group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
className
)}
{...props}