Canonicalize grouped table row presentation

This commit is contained in:
rcourtman 2026-04-29 10:24:51 +01:00
parent a15f5ad51b
commit e4514dfd8b
17 changed files with 89 additions and 42 deletions

View file

@ -507,7 +507,7 @@ work extends shared components instead of creating new local variants.
reset action stays as one compact header-level `Clear` control with an
accessible `Clear selection` label, not a second page-level scope strip,
search-row accessory, or filter-bar badge.
13. Keep summary-linked table row emphasis on the shared primitive contract. Workloads, infrastructure, and storage rows that mirror the active summary entity must expose that state through `data-summary-row-active` and let the shared presentation in `frontend-modern/src/index.css` render the row emphasis, rather than carrying page-local sky or blue fill classes inside each row renderer. Group-scoped preview and pin must use that same shared presentation boundary: child rows that belong to a hovered or pinned summary group should expose `data-summary-group-member-active="preview|pinned"` so the block-level emphasis stays subtle, consistent, and reversible instead of each table inventing its own outline, badge, or full-strength fill treatment. Static grouped row headers on workloads, infrastructure, storage, recovery, and future grouped tables must use `frontend-modern/src/components/shared/groupedTableRowPresentation.ts` plus the `.grouped-table-row` CSS contract in `frontend-modern/src/index.css`, rather than rebuilding local `bg-surface-alt` variants with subtly different light/dark behavior or page-local left-accent markers. Storage-backed reusable row presenters under `frontend-modern/src/features/storageBackups/` must also keep row height and alert accents on class/data-attribute presentation instead of runtime inline style maps, so the shared table contract stays CSP-safe on both steady-state and alert-highlighted routes.
13. Keep summary-linked table row emphasis on the shared primitive contract. Workloads, infrastructure, and storage rows that mirror the active summary entity must expose that state through `data-summary-row-active` and let the shared presentation in `frontend-modern/src/index.css` render the row emphasis, rather than carrying page-local sky or blue fill classes inside each row renderer. Group-scoped preview and pin must use that same shared presentation boundary: child rows that belong to a hovered or pinned summary group should expose `data-summary-group-member-active="preview|pinned"` so the block-level emphasis stays subtle, consistent, and reversible instead of each table inventing its own outline, badge, or full-strength fill treatment. Static grouped row headers on workloads, infrastructure, storage, recovery, and future grouped tables must use `frontend-modern/src/components/shared/groupedTableRowPresentation.ts` plus the `.grouped-table-row` CSS contract in `frontend-modern/src/index.css`, rather than rebuilding local `bg-surface-alt` variants with subtly different light/dark behavior or page-local left-accent markers. That shared grouped-table primitive owns the subgroup cell padding, typography, small metadata, and badge treatment as well as the row background token, so a future adjustment to the subgroup visual language changes every grouped product table from one owner. Storage-backed reusable row presenters under `frontend-modern/src/features/storageBackups/` must also keep row height and alert accents on class/data-attribute presentation instead of runtime inline style maps, so the shared table contract stays CSP-safe on both steady-state and alert-highlighted routes.
14. Keep retained-value data loading honest at the ownership boundary. Helpers
that prevent a feature surface from falling through the app-level Suspense
boundary during in-flight refresh should stay feature-local until multiple

View file

@ -1,5 +1,6 @@
import { Show } from 'solid-js';
import { GROUPED_TABLE_ROW_BADGE_CLASS } from '@/components/shared/groupedTableRowPresentation';
import type { GroupHeaderMeta } from '@/features/alerts/thresholds/tableTypes';
interface AlertResourceGroupHeaderProps {
@ -13,12 +14,12 @@ export function AlertResourceGroupHeader(props: AlertResourceGroupHeaderProps) {
return (
<Show
when={props.meta?.type === 'agent'}
fallback={<span class="text-xs font-medium text-muted">{groupLabel()}</span>}
fallback={<span>{groupLabel()}</span>}
>
<div class="flex flex-wrap items-center gap-3">
<Show
when={props.meta?.host}
fallback={<span class="text-sm font-medium text-base-content">{groupLabel()}</span>}
fallback={<span>{groupLabel()}</span>}
>
{(host) => (
<a
@ -26,7 +27,7 @@ export function AlertResourceGroupHeader(props: AlertResourceGroupHeaderProps) {
target="_blank"
rel="noopener noreferrer"
onClick={(e) => e.stopPropagation()}
class="text-sm font-medium text-base-content transition-colors duration-150 hover:text-sky-600 dark:hover:text-sky-400"
class="text-base-content transition-colors duration-150 hover:text-sky-600 dark:hover:text-sky-400"
title={`Open ${groupLabel()} web interface`}
>
{groupLabel()}
@ -34,7 +35,7 @@ export function AlertResourceGroupHeader(props: AlertResourceGroupHeaderProps) {
)}
</Show>
<Show when={props.meta?.clusterName}>
<span class="rounded px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300">
<span class={GROUPED_TABLE_ROW_BADGE_CLASS}>
{props.meta?.clusterName}
</span>
</Show>

View file

@ -13,7 +13,10 @@ import {
} from '@/components/shared/Table';
import { SectionHeader } from '@/components/shared/SectionHeader';
import { HelpIcon } from '@/components/shared/HelpIcon';
import { getGroupedTableRowClass } from '@/components/shared/groupedTableRowPresentation';
import {
getGroupedTableRowCellClass,
getGroupedTableRowClass,
} from '@/components/shared/groupedTableRowPresentation';
import { AlertResourceTableRow } from './AlertResourceTableRow';
import { AlertResourceGroupHeader } from './AlertResourceGroupHeader';
import {
@ -445,7 +448,7 @@ export function AlertResourceTableDesktop(props: AlertResourceTableDesktopProps)
<TableRow class={getGroupedTableRowClass()}>
<TableCell
colspan={totalColumnCount()}
class="p-1 px-2 text-xs font-medium text-muted"
class={getGroupedTableRowCellClass()}
>
<AlertResourceGroupHeader
groupKey={nodeName}

View file

@ -1,7 +1,11 @@
import { createMemo, Index, Show } from 'solid-js';
import { ComponentErrorBoundary } from '@/components/ErrorBoundary';
import { getInteractiveGroupedTableRowClass } from '@/components/shared/groupedTableRowPresentation';
import {
GROUPED_TABLE_ROW_BADGE_CLASS,
getGroupedTableRowCellClass,
getInteractiveGroupedTableRowClass,
} from '@/components/shared/groupedTableRowPresentation';
import { NodeGroupHeader } from '@/components/shared/NodeGroupHeader';
import { createSummaryInteractiveRowPreviewHandlers } from '@/components/shared/summaryInteractionA11y';
import { buildSummaryDisclosureControlsId } from '@/components/shared/summaryInteractionA11y';
@ -119,7 +123,7 @@ export function WorkloadPanel(props: WorkloadPanelProps) {
>
<TableCell
colspan={props.totalColumns()}
class="py-0.5 pr-1.5 sm:pr-2 pl-2 sm:pl-3 text-[12px] sm:text-sm font-semibold text-base-content"
class={getGroupedTableRowCellClass()}
>
{(() => {
const label = props.getGroupLabel(groupKey(), fullGroupGuests());
@ -127,7 +131,7 @@ export function WorkloadPanel(props: WorkloadPanelProps) {
<div class="flex items-center gap-3">
<span>{label.name}</span>
<Show when={label.type}>
<span class="inline-flex items-center rounded px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300">
<span class={GROUPED_TABLE_ROW_BADGE_CLASS}>
{label.type}
</span>
</Show>

View file

@ -775,6 +775,7 @@ describe('Dashboard performance contract', () => {
expect(workloadPanelSource).toContain('data-summary-group-id');
expect(workloadPanelSource).toContain('setHoveredWorkloadGroupScope');
expect(workloadPanelSource).toContain('getInteractiveGroupedTableRowClass');
expect(workloadPanelSource).toContain('getGroupedTableRowCellClass');
expect(workloadPanelSource).not.toContain('cursor-pointer bg-surface-alt');
expect(dashboardWorkloadTableSource).not.toContain(
'createMemo(() => getCanonicalWorkloadId(guest()))',
@ -985,6 +986,7 @@ describe('Dashboard performance contract', () => {
expect(workloadPanelSource).toContain('createSummaryInteractiveRowPreviewHandlers');
expect(workloadPanelSource).toContain('resolveSummaryGroupMemberInteractionState');
expect(workloadPanelSource).toContain('getInteractiveGroupedTableRowClass');
expect(workloadPanelSource).toContain('getGroupedTableRowCellClass');
expect(workloadPanelSource).not.toContain('style={{');
expect(workloadPanelSource).not.toContain('style={');
expect(workloadPanelSource).not.toContain('kind="scope"');

View file

@ -3,7 +3,12 @@ import type { Component } from 'solid-js';
import type { Disk } from '@/types/api';
import { formatBytes, formatSpeed, formatUptime, normalizeDiskArray } from '@/utils/format';
import { formatTemperature } from '@/utils/temperature';
import { getInteractiveGroupedTableRowClass } from '@/components/shared/groupedTableRowPresentation';
import {
GROUPED_TABLE_ROW_BADGE_CLASS,
GROUPED_TABLE_ROW_META_CLASS,
getGroupedTableRowCellClass,
getInteractiveGroupedTableRowClass,
} from '@/components/shared/groupedTableRowPresentation';
import { TableCardHeader } from '@/components/shared/TableCardHeader';
import { TableCard } from '@/components/shared/TableCard';
import {
@ -193,7 +198,7 @@ export const UnifiedResourceHostTableCard: Component<UnifiedResourceHostTableCar
>
<TableCell
colspan={9}
class="py-1 pr-2 pl-4 text-[12px] sm:text-sm font-semibold text-base-content"
class={getGroupedTableRowCellClass()}
>
<div class="flex items-center gap-2">
<Show
@ -202,12 +207,12 @@ export const UnifiedResourceHostTableCard: Component<UnifiedResourceHostTableCar
>
<span>{group.cluster}</span>
<Show when={shouldShowClusterGroupTypeLabel(group.cluster)}>
<span class="inline-flex items-center rounded px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300">
<span class={GROUPED_TABLE_ROW_BADGE_CLASS}>
Cluster
</span>
</Show>
</Show>
<span class="text-[10px] text-muted font-normal">
<span class={GROUPED_TABLE_ROW_META_CLASS}>
{group.resources.length}{' '}
{group.resources.length === 1 ? 'resource' : 'resources'}
</span>

View file

@ -516,6 +516,7 @@ describe('UnifiedResourceTable performance contract', () => {
expect(unifiedResourceHostTableCardSource).not.toContain('kind="scope"');
expect(unifiedResourceHostTableCardSource).toContain('getInteractiveGroupedTableRowClass');
expect(unifiedResourceHostTableCardSource).toContain('getGroupedTableRowCellClass');
expect(unifiedResourceHostTableCardSource).not.toContain('cursor-pointer bg-surface-alt');
expect(unifiedResourceHostTableCardSource).toContain('TableCard');
expect(unifiedResourceHostTableCardSource).toContain('TableCardHeader');

View file

@ -18,7 +18,10 @@ import {
TableHeader,
TableRow,
} from '@/components/shared/Table';
import { getGroupedTableRowClass } from '@/components/shared/groupedTableRowPresentation';
import {
getGroupedTableRowCellClass,
getGroupedTableRowClass,
} from '@/components/shared/groupedTableRowPresentation';
import {
connectionAgentVersionPresentation,
fleetSignalClassName,
@ -619,7 +622,6 @@ export const InfrastructureSourceManager: Component<InfrastructureSourceManagerP
const discoveredRows = () => groupedDiscoveredRows().get(product.type) ?? [];
const groupRowClass = () =>
getGroupedTableRowClass('border-b border-border-subtle');
const groupLabelClass = () => 'text-[15px] font-semibold text-base-content';
return (
<>
@ -627,18 +629,18 @@ export const InfrastructureSourceManager: Component<InfrastructureSourceManagerP
when={actionColumnVisible()}
fallback={
<TableRow class={groupRowClass()}>
<TableCell colspan={5} class="px-3 py-1.5">
<TableCell colspan={5} class={getGroupedTableRowCellClass()}>
<div class="flex min-w-0 items-center gap-2">
<span class={groupLabelClass()}>{product.label}</span>
<span>{product.label}</span>
</div>
</TableCell>
</TableRow>
}
>
<TableRow class={groupRowClass()}>
<TableCell colspan={6} class="px-3 py-1.5">
<TableCell colspan={6} class={getGroupedTableRowCellClass()}>
<div class="flex items-center justify-between gap-2 whitespace-nowrap">
<span class={groupLabelClass()}>{product.label}</span>
<span>{product.label}</span>
<Show when={!props.readOnly && props.onAddSource}>
<button
type="button"

View file

@ -3,7 +3,11 @@ import type { Node } from '@/types/api';
import { getNodeDisplayName, hasAlternateDisplayName } from '@/utils/nodes';
import { StatusDot } from '@/components/shared/StatusDot';
import { getNodeStatusIndicator } from '@/utils/status';
import { getGroupedTableRowClass } from './groupedTableRowPresentation';
import {
GROUPED_TABLE_ROW_BADGE_CLASS,
getGroupedTableRowCellClass,
getGroupedTableRowClass,
} from './groupedTableRowPresentation';
interface NodeGroupHeaderProps {
node: Node;
@ -50,11 +54,11 @@ export const NodeGroupHeader: Component<NodeGroupHeaderProps> = (props) => {
<Show when={props.node.isClusterMember !== undefined}>
<span
class={`rounded px-2 py-0.5 text-[10px] font-medium whitespace-nowrap ${
class={
props.node.isClusterMember
? 'bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300'
: 'bg-surface-alt text-muted'
}`}
? GROUPED_TABLE_ROW_BADGE_CLASS
: 'rounded px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-surface-alt text-muted'
}
>
{props.node.isClusterMember ? props.node.clusterName : 'Standalone'}
</span>
@ -76,17 +80,14 @@ export const NodeGroupHeader: Component<NodeGroupHeaderProps> = (props) => {
when={props.renderAs === 'tr'}
fallback={
<div class="bg-surface-alt w-full">
<div class="py-0.5 pr-2 pl-4 text-[12px] sm:text-sm font-semibold text-base-content">
<div class={getGroupedTableRowCellClass()}>
<InnerContent />
</div>
</div>
}
>
<tr class={getGroupedTableRowClass(props.trClass)} {...props.trProps}>
<td
colspan={props.colspan}
class="py-0.5 pr-2 pl-4 text-[12px] sm:text-sm font-semibold text-base-content"
>
<td colspan={props.colspan} class={getGroupedTableRowCellClass()}>
<InnerContent />
</td>
</tr>

View file

@ -439,6 +439,8 @@ describe('shared primitive guardrails', () => {
);
expect(frontendIndexCssSource).not.toContain('--color-grouped-table-row-bg: theme(');
expect(groupedTableRowPresentationSource).toContain('GROUPED_TABLE_ROW_CLASS');
expect(groupedTableRowPresentationSource).toContain('GROUPED_TABLE_ROW_CELL_CLASS');
expect(groupedTableRowPresentationSource).toContain('getGroupedTableRowCellClass');
expect(groupedTableRowPresentationSource).not.toContain('GROUPED_TABLE_ROW_DIVIDER_CLASS');
expect(guestRowSource).toContain('data-summary-row-active');
@ -463,14 +465,21 @@ describe('shared primitive guardrails', () => {
expect(storagePoolRowSource).toContain('data-summary-group-member-active');
expect(storageGroupRowSource).toContain('STORAGE_GROUP_ROW_CLASS');
expect(storageGroupPresentationSource).toContain('getInteractiveGroupedTableRowClass');
expect(storageGroupPresentationSource).toContain('getGroupedTableRowCellClass');
expect(nodeGroupHeaderSource).toContain('getGroupedTableRowClass');
expect(nodeGroupHeaderSource).toContain('getGroupedTableRowCellClass');
expect(workloadPanelSource).toContain('getInteractiveGroupedTableRowClass');
expect(workloadPanelSource).toContain('getGroupedTableRowCellClass');
expect(unifiedResourceHostTableCardSource).toContain('getInteractiveGroupedTableRowClass');
expect(recoveryTablePresentationSource).toContain('GROUPED_TABLE_ROW_BASE_CLASS');
expect(unifiedResourceHostTableCardSource).toContain('getGroupedTableRowCellClass');
expect(recoveryTablePresentationSource).toContain('getGroupedTableRowCellClass');
expect(alertHistoryTableGroupRowSource).toContain('getGroupedTableRowClass');
expect(alertHistoryTableGroupRowSource).toContain('getGroupedTableRowCellClass');
expect(alertHistoryTableGroupRowSource).not.toContain('class="bg-surface-alt"');
expect(alertResourceTableDesktopSource).toContain('getGroupedTableRowClass');
expect(alertResourceTableDesktopSource).toContain('getGroupedTableRowCellClass');
expect(infrastructureSourceManagerSource).toContain('getGroupedTableRowClass');
expect(infrastructureSourceManagerSource).toContain('getGroupedTableRowCellClass');
expect(infrastructureSourceManagerSource).not.toContain('bg-base hover:bg-base');
expect(unifiedResourceHostTableCardSource).toContain('data-summary-group-member-active');
});

View file

@ -1,9 +1,17 @@
export const GROUPED_TABLE_ROW_CLASS = 'grouped-table-row';
export const GROUPED_TABLE_ROW_BASE_CLASS = `${GROUPED_TABLE_ROW_CLASS} transition-colors`;
export const GROUPED_TABLE_ROW_INTERACTIVE_CLASS = `${GROUPED_TABLE_ROW_BASE_CLASS} cursor-pointer select-none duration-150`;
export const GROUPED_TABLE_ROW_CELL_CLASS =
'!px-2 !py-1 align-middle text-[12px] sm:text-sm font-semibold text-base-content';
export const GROUPED_TABLE_ROW_META_CLASS = 'text-[10px] font-medium text-muted';
export const GROUPED_TABLE_ROW_BADGE_CLASS =
'inline-flex items-center rounded px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300';
export const getGroupedTableRowClass = (className = ''): string =>
`${GROUPED_TABLE_ROW_BASE_CLASS} ${className}`.trim();
export const getInteractiveGroupedTableRowClass = (className = ''): string =>
`${GROUPED_TABLE_ROW_INTERACTIVE_CLASS} ${className}`.trim();
export const getGroupedTableRowCellClass = (className = ''): string =>
`${GROUPED_TABLE_ROW_CELL_CLASS} ${className}`.trim();

View file

@ -1,5 +1,9 @@
import { TableCell, TableRow } from '@/components/shared/Table';
import { getGroupedTableRowClass } from '@/components/shared/groupedTableRowPresentation';
import {
GROUPED_TABLE_ROW_META_CLASS,
getGroupedTableRowCellClass,
getGroupedTableRowClass,
} from '@/components/shared/groupedTableRowPresentation';
import type { AlertHistoryState } from './useAlertHistoryState';
@ -29,12 +33,12 @@ function getGroupSummaryLabel(group: AlertHistoryGroup) {
export function AlertHistoryTableGroupRow(props: AlertHistoryTableGroupRowProps) {
return (
<TableRow class={getGroupedTableRowClass()}>
<TableCell colspan={10} class="py-1.5 pr-3 pl-4 text-[12px] font-semibold sm:text-sm">
<TableCell colspan={10} class={getGroupedTableRowCellClass()}>
<div class="flex flex-col gap-1 sm:flex-row sm:items-center sm:justify-between sm:gap-3">
<span class="truncate" title={props.group.fullLabel}>
{props.group.label}
</span>
<span class="text-[10px] font-medium text-muted">
<span class={GROUPED_TABLE_ROW_META_CLASS}>
{getGroupSummaryLabel(props.group)}
</span>
</div>

View file

@ -1,5 +1,8 @@
import { formatPercent } from '@/utils/format';
import { getInteractiveGroupedTableRowClass } from '@/components/shared/groupedTableRowPresentation';
import {
getGroupedTableRowCellClass,
getInteractiveGroupedTableRowClass,
} from '@/components/shared/groupedTableRowPresentation';
import type { NormalizedHealth } from './models';
import type { StorageGroupedRecords } from '@/components/Storage/useStorageModel';
import { getStorageHealthPresentation } from './healthPresentation';
@ -21,7 +24,7 @@ export interface StorageGroupRowPresentation {
}
export const STORAGE_GROUP_ROW_CLASS = getInteractiveGroupedTableRowClass('border-b border-border');
export const STORAGE_GROUP_ROW_CELL_CLASS = 'px-1.5 sm:px-2 py-0.5';
export const STORAGE_GROUP_ROW_CELL_CLASS = getGroupedTableRowCellClass();
export const STORAGE_GROUP_ROW_CONTENT_CLASS = 'flex items-center gap-3';
export const STORAGE_GROUP_ROW_LABEL_CLASS =
'text-[11px] font-semibold text-base-content w-[140px] flex-shrink-0 truncate';

View file

@ -471,7 +471,9 @@ describe('tab path helpers', () => {
expect(alertHistoryTableGroupRowSource).toContain('export function AlertHistoryTableGroupRow');
expect(alertHistoryTableGroupRowSource).toContain('getGroupSummaryLabel');
expect(alertHistoryTableGroupRowSource).toContain('getGroupedTableRowClass');
expect(alertHistoryTableGroupRowSource).toContain('getGroupedTableRowCellClass');
expect(alertResourceTableDesktopSource).toContain('getGroupedTableRowClass');
expect(alertResourceTableDesktopSource).toContain('getGroupedTableRowCellClass');
expect(alertHistoryTableAlertRowSource).toContain('export function AlertHistoryTableAlertRow');
expect(alertHistoryTableAlertRowSource).toContain('IncidentTimelinePanel');
expect(alertHistoryTableAlertRowSource).toContain('InvestigateAlertButton');

View file

@ -1265,7 +1265,7 @@ describe('frontend resource type boundaries', () => {
expect(recoveryTablePresentationSource).toContain(
'export function getRecoveryArtifactColumnHeaderClass',
);
expect(recoveryTablePresentationSource).toContain('GROUPED_TABLE_ROW_BASE_CLASS');
expect(recoveryTablePresentationSource).toContain('getGroupedTableRowCellClass');
expect(recoveryTablePresentationSource).toContain('getRecoveryItemTypeLabel');
expect(recoveryTablePresentationSource).toContain('getRecoveryLocationFacetLabel');
expect(recoveryTablePresentationSource).not.toContain('const titleize =');

View file

@ -30,7 +30,7 @@ import {
describe('recoveryTablePresentation', () => {
it('exposes shared recovery table classes', () => {
expect(RECOVERY_GROUP_HEADER_ROW_CLASS).toContain('grouped-table-row');
expect(RECOVERY_GROUP_HEADER_TEXT_CLASS).toContain('font-medium');
expect(RECOVERY_GROUP_HEADER_TEXT_CLASS).toContain('font-semibold');
expect(RECOVERY_ADVANCED_FILTER_LABEL_CLASS).toContain('text-muted');
expect(RECOVERY_ADVANCED_FILTER_FIELD_CLASS).toContain('focus:border-blue-500');
expect(RECOVERY_GROUP_NO_TIMESTAMP_LABEL).toBe('No Timestamp');

View file

@ -1,4 +1,7 @@
import { GROUPED_TABLE_ROW_BASE_CLASS } from '@/components/shared/groupedTableRowPresentation';
import {
getGroupedTableRowCellClass,
getGroupedTableRowClass,
} from '@/components/shared/groupedTableRowPresentation';
import type { ProtectionRollup, RecoveryOutcome, RecoveryPoint } from '@/types/recovery';
import {
getRecoveryItemTypeBadgeClass,
@ -15,9 +18,8 @@ import type { RecoveryIssueTone } from '@/utils/recoveryIssuePresentation';
export const STALE_ISSUE_THRESHOLD_MS = 7 * 24 * 60 * 60 * 1000;
export const AGING_THRESHOLD_MS = 2 * 24 * 60 * 60 * 1000;
export const RECOVERY_GROUP_HEADER_ROW_CLASS = GROUPED_TABLE_ROW_BASE_CLASS;
export const RECOVERY_GROUP_HEADER_TEXT_CLASS =
'py-1 pr-3 pl-4 text-[11px] font-medium text-base-content';
export const RECOVERY_GROUP_HEADER_ROW_CLASS = getGroupedTableRowClass();
export const RECOVERY_GROUP_HEADER_TEXT_CLASS = getGroupedTableRowCellClass();
export const RECOVERY_ADVANCED_FILTER_LABEL_CLASS = 'text-[11px] font-medium text-muted';
export const RECOVERY_ADVANCED_FILTER_FIELD_CLASS =
'min-h-[2.25rem] w-full rounded-md border border-border bg-surface px-2.5 py-1.5 text-sm text-base-content outline-none focus:border-blue-500';