From 02887dc52eeda86a376df68599e7f7c0f672dd94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Fri, 24 Jul 2026 10:18:29 -0400 Subject: [PATCH 1/9] fix(tables): Add aria-sort to sortable table headers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SimpleTable's sortable column headers only rendered a visual sort indicator (an aria-hidden arrow), so screen readers had no way to announce sort state. Set aria-sort=ascending|descending on the columnheader when a column is sorted. Also dedupe the Alignments type, which was defined in both gridEditable/sortLink and utils/discover/fields. Keep the canonical definition in utils/discover/fields, export it, and import it directly everywhere; sortLink no longer re-exports it. Behavior-preserving — the shared type is now stricter, dropping a stray | undefined that no caller relied on. Refs DE-1399 Fixes DE-1393 --- static/app/components/tables/gridEditable/sortLink.tsx | 2 +- static/app/components/tables/simpleTable/index.tsx | 1 + static/app/utils/discover/fields.tsx | 2 +- .../alerts/rules/metric/details/relatedTransactions.tsx | 2 +- static/app/views/explore/components/table.tsx | 2 +- .../explore/multiQueryMode/queryVisualizations/table.tsx | 2 +- .../common/components/tableCells/renderHeadCell.tsx | 3 +-- static/app/views/organizationStats/usageStatsProjects.tsx | 6 ++---- 8 files changed, 9 insertions(+), 11 deletions(-) diff --git a/static/app/components/tables/gridEditable/sortLink.tsx b/static/app/components/tables/gridEditable/sortLink.tsx index 51c601afa12c..4732040145d3 100644 --- a/static/app/components/tables/gridEditable/sortLink.tsx +++ b/static/app/components/tables/gridEditable/sortLink.tsx @@ -5,9 +5,9 @@ import type {LocationDescriptorObject} from 'history'; import {Link} from '@sentry/scraps/link'; import {IconArrow} from 'sentry/icons'; +import type {Alignments} from 'sentry/utils/discover/fields'; import {useNavigate} from 'sentry/utils/useNavigate'; -export type Alignments = 'left' | 'right' | undefined; export type Directions = 'desc' | 'asc' | undefined; type Props = { diff --git a/static/app/components/tables/simpleTable/index.tsx b/static/app/components/tables/simpleTable/index.tsx index 2c8eef2d9d0f..c028e17f8c4d 100644 --- a/static/app/components/tables/simpleTable/index.tsx +++ b/static/app/components/tables/simpleTable/index.tsx @@ -53,6 +53,7 @@ function HeaderCell({ return ( { diff --git a/static/app/views/explore/multiQueryMode/queryVisualizations/table.tsx b/static/app/views/explore/multiQueryMode/queryVisualizations/table.tsx index 8686ba2c4345..b1c1f12f8139 100644 --- a/static/app/views/explore/multiQueryMode/queryVisualizations/table.tsx +++ b/static/app/views/explore/multiQueryMode/queryVisualizations/table.tsx @@ -9,13 +9,13 @@ import {Tooltip} from '@sentry/scraps/tooltip'; import {EmptyStateWarning} from 'sentry/components/emptyStateWarning'; import {LoadingIndicator} from 'sentry/components/loadingIndicator'; -import type {Alignments} from 'sentry/components/tables/gridEditable/sortLink'; import {GridBodyCell, GridHeadCell} from 'sentry/components/tables/gridEditable/styles'; import {IconArrow} from 'sentry/icons/iconArrow'; import {IconStack} from 'sentry/icons/iconStack'; import {IconWarning} from 'sentry/icons/iconWarning'; import {t} from 'sentry/locale'; import {defined} from 'sentry/utils/defined'; +import type {Alignments} from 'sentry/utils/discover/fields'; import { fieldAlignment, parseFunction, diff --git a/static/app/views/insights/common/components/tableCells/renderHeadCell.tsx b/static/app/views/insights/common/components/tableCells/renderHeadCell.tsx index c3f4ddc7c298..01abd1b595f3 100644 --- a/static/app/views/insights/common/components/tableCells/renderHeadCell.tsx +++ b/static/app/views/insights/common/components/tableCells/renderHeadCell.tsx @@ -4,9 +4,8 @@ import type {Location} from 'history'; import {Tooltip} from '@sentry/scraps/tooltip'; import type {GridColumnHeader} from 'sentry/components/tables/gridEditable'; -import type {Alignments} from 'sentry/components/tables/gridEditable/sortLink'; import {SortLink} from 'sentry/components/tables/gridEditable/sortLink'; -import type {Sort} from 'sentry/utils/discover/fields'; +import type {Alignments, Sort} from 'sentry/utils/discover/fields'; import { aggregateFunctionOutputType, fieldAlignment, diff --git a/static/app/views/organizationStats/usageStatsProjects.tsx b/static/app/views/organizationStats/usageStatsProjects.tsx index 7ae6121de044..2a648900d2d8 100644 --- a/static/app/views/organizationStats/usageStatsProjects.tsx +++ b/static/app/views/organizationStats/usageStatsProjects.tsx @@ -10,10 +10,7 @@ import type {DateTimeObject} from 'sentry/components/charts/utils'; import {getSeriesApiInterval} from 'sentry/components/charts/utils'; import {ALL_ACCESS_PROJECTS} from 'sentry/components/pageFilters/constants'; import {SearchBar} from 'sentry/components/searchBar'; -import type { - Alignments, - Directions, -} from 'sentry/components/tables/gridEditable/sortLink'; +import type {Directions} from 'sentry/components/tables/gridEditable/sortLink'; import {SortLink} from 'sentry/components/tables/gridEditable/sortLink'; import {DATA_CATEGORY_INFO, DEFAULT_STATS_PERIOD} from 'sentry/constants'; import {t} from 'sentry/locale'; @@ -21,6 +18,7 @@ import type {DataCategoryInfo} from 'sentry/types/core'; import {DataCategoryExact, Outcome} from 'sentry/types/core'; import type {Project} from 'sentry/types/project'; import {getApiUrl} from 'sentry/utils/api/getApiUrl'; +import type {Alignments} from 'sentry/utils/discover/fields'; import {hasDynamicSamplingCustomFeature} from 'sentry/utils/dynamicSampling/features'; import {useApiQuery} from 'sentry/utils/queryClient'; import {useOrganization} from 'sentry/utils/useOrganization'; From 8abae8619dae9c092133af0ed312007ac9b0c588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Fri, 24 Jul 2026 11:05:59 -0400 Subject: [PATCH 2/9] Move to gridEditable/types --- static/app/components/tables/gridEditable/sortLink.tsx | 2 +- static/app/components/tables/gridEditable/types.ts | 2 ++ static/app/utils/discover/fields.tsx | 3 +-- .../views/alerts/rules/metric/details/relatedTransactions.tsx | 3 +-- static/app/views/explore/components/table.tsx | 2 +- .../explore/multiQueryMode/queryVisualizations/table.tsx | 2 +- .../insights/common/components/tableCells/renderHeadCell.tsx | 4 ++-- static/app/views/organizationStats/usageStatsProjects.tsx | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/static/app/components/tables/gridEditable/sortLink.tsx b/static/app/components/tables/gridEditable/sortLink.tsx index 4732040145d3..be0d995f915a 100644 --- a/static/app/components/tables/gridEditable/sortLink.tsx +++ b/static/app/components/tables/gridEditable/sortLink.tsx @@ -4,8 +4,8 @@ import type {LocationDescriptorObject} from 'history'; import {Link} from '@sentry/scraps/link'; +import type {Alignments} from 'sentry/components/tables/gridEditable'; import {IconArrow} from 'sentry/icons'; -import type {Alignments} from 'sentry/utils/discover/fields'; import {useNavigate} from 'sentry/utils/useNavigate'; export type Directions = 'desc' | 'asc' | undefined; diff --git a/static/app/components/tables/gridEditable/types.ts b/static/app/components/tables/gridEditable/types.ts index a143776807e3..2fe3aaa6df48 100644 --- a/static/app/components/tables/gridEditable/types.ts +++ b/static/app/components/tables/gridEditable/types.ts @@ -6,6 +6,8 @@ type ObjectKey = string | number; +export type Alignments = 'left' | 'right'; + export type GridColumn = { key: K; width?: number; diff --git a/static/app/utils/discover/fields.tsx b/static/app/utils/discover/fields.tsx index 0ea3366f5b7b..43aea463ba65 100644 --- a/static/app/utils/discover/fields.tsx +++ b/static/app/utils/discover/fields.tsx @@ -3,6 +3,7 @@ import isEqual from 'lodash/isEqual'; import type {SelectValue} from '@sentry/scraps/select'; import type {FilterKeySection} from 'sentry/components/searchQueryBuilder/types'; +import type {Alignments} from 'sentry/components/tables/gridEditable'; import {RELEASE_ADOPTION_STAGES} from 'sentry/constants'; import type {Organization} from 'sentry/types/organization'; import {assert} from 'sentry/types/utils'; @@ -118,8 +119,6 @@ export type QueryFieldValue = // Column is just an alias of a Query value export type Column = QueryFieldValue; -export type Alignments = 'left' | 'right'; - export type CountUnit = 'count'; export type PercentageUnit = 'percentage'; diff --git a/static/app/views/alerts/rules/metric/details/relatedTransactions.tsx b/static/app/views/alerts/rules/metric/details/relatedTransactions.tsx index b47084201805..6df1ca4d2160 100644 --- a/static/app/views/alerts/rules/metric/details/relatedTransactions.tsx +++ b/static/app/views/alerts/rules/metric/details/relatedTransactions.tsx @@ -5,14 +5,13 @@ import type {Location} from 'history'; import {Link} from '@sentry/scraps/link'; -import {GridEditable} from 'sentry/components/tables/gridEditable'; +import {GridEditable, type Alignments} from 'sentry/components/tables/gridEditable'; import {useStateBasedColumnResize} from 'sentry/components/tables/gridEditable/useStateBasedColumnResize'; import type {Organization} from 'sentry/types/organization'; import type {Project} from 'sentry/types/project'; import type {TableData, TableDataRow} from 'sentry/utils/discover/discoverQuery'; import {DiscoverQuery} from 'sentry/utils/discover/discoverQuery'; import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers'; -import type {Alignments} from 'sentry/utils/discover/fields'; import {fieldAlignment} from 'sentry/utils/discover/fields'; import {useNavigate} from 'sentry/utils/useNavigate'; import type {MetricRule} from 'sentry/views/alerts/rules/metric/types'; diff --git a/static/app/views/explore/components/table.tsx b/static/app/views/explore/components/table.tsx index 7e5b94adbba0..0fa70ac0f86d 100644 --- a/static/app/views/explore/components/table.tsx +++ b/static/app/views/explore/components/table.tsx @@ -4,6 +4,7 @@ import {css} from '@emotion/react'; import styled from '@emotion/styled'; import {COL_WIDTH_MINIMUM} from 'sentry/components/tables/gridEditable'; +import type {Alignments} from 'sentry/components/tables/gridEditable'; import { Grid as _Table, Body as _TableWrapper, @@ -15,7 +16,6 @@ import { GridRow, } from 'sentry/components/tables/gridEditable/styles'; import {defined} from 'sentry/utils/defined'; -import type {Alignments} from 'sentry/utils/discover/fields'; import {Actions} from 'sentry/views/discover/table/cellAction'; interface TableProps extends React.ComponentProps { diff --git a/static/app/views/explore/multiQueryMode/queryVisualizations/table.tsx b/static/app/views/explore/multiQueryMode/queryVisualizations/table.tsx index b1c1f12f8139..4ce0414acde2 100644 --- a/static/app/views/explore/multiQueryMode/queryVisualizations/table.tsx +++ b/static/app/views/explore/multiQueryMode/queryVisualizations/table.tsx @@ -9,13 +9,13 @@ import {Tooltip} from '@sentry/scraps/tooltip'; import {EmptyStateWarning} from 'sentry/components/emptyStateWarning'; import {LoadingIndicator} from 'sentry/components/loadingIndicator'; +import type {Alignments} from 'sentry/components/tables/gridEditable'; import {GridBodyCell, GridHeadCell} from 'sentry/components/tables/gridEditable/styles'; import {IconArrow} from 'sentry/icons/iconArrow'; import {IconStack} from 'sentry/icons/iconStack'; import {IconWarning} from 'sentry/icons/iconWarning'; import {t} from 'sentry/locale'; import {defined} from 'sentry/utils/defined'; -import type {Alignments} from 'sentry/utils/discover/fields'; import { fieldAlignment, parseFunction, diff --git a/static/app/views/insights/common/components/tableCells/renderHeadCell.tsx b/static/app/views/insights/common/components/tableCells/renderHeadCell.tsx index 01abd1b595f3..b6aef63b1765 100644 --- a/static/app/views/insights/common/components/tableCells/renderHeadCell.tsx +++ b/static/app/views/insights/common/components/tableCells/renderHeadCell.tsx @@ -3,9 +3,9 @@ import type {Location} from 'history'; import {Tooltip} from '@sentry/scraps/tooltip'; -import type {GridColumnHeader} from 'sentry/components/tables/gridEditable'; +import type {Alignments, GridColumnHeader} from 'sentry/components/tables/gridEditable'; import {SortLink} from 'sentry/components/tables/gridEditable/sortLink'; -import type {Alignments, Sort} from 'sentry/utils/discover/fields'; +import type {Sort} from 'sentry/utils/discover/fields'; import { aggregateFunctionOutputType, fieldAlignment, diff --git a/static/app/views/organizationStats/usageStatsProjects.tsx b/static/app/views/organizationStats/usageStatsProjects.tsx index 2a648900d2d8..658bf1955091 100644 --- a/static/app/views/organizationStats/usageStatsProjects.tsx +++ b/static/app/views/organizationStats/usageStatsProjects.tsx @@ -10,6 +10,7 @@ import type {DateTimeObject} from 'sentry/components/charts/utils'; import {getSeriesApiInterval} from 'sentry/components/charts/utils'; import {ALL_ACCESS_PROJECTS} from 'sentry/components/pageFilters/constants'; import {SearchBar} from 'sentry/components/searchBar'; +import type {Alignments} from 'sentry/components/tables/gridEditable'; import type {Directions} from 'sentry/components/tables/gridEditable/sortLink'; import {SortLink} from 'sentry/components/tables/gridEditable/sortLink'; import {DATA_CATEGORY_INFO, DEFAULT_STATS_PERIOD} from 'sentry/constants'; @@ -18,7 +19,6 @@ import type {DataCategoryInfo} from 'sentry/types/core'; import {DataCategoryExact, Outcome} from 'sentry/types/core'; import type {Project} from 'sentry/types/project'; import {getApiUrl} from 'sentry/utils/api/getApiUrl'; -import type {Alignments} from 'sentry/utils/discover/fields'; import {hasDynamicSamplingCustomFeature} from 'sentry/utils/dynamicSampling/features'; import {useApiQuery} from 'sentry/utils/queryClient'; import {useOrganization} from 'sentry/utils/useOrganization'; From f41a886336225fe1b429dccbab6cfa7d63d795d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Fri, 24 Jul 2026 17:17:06 -0400 Subject: [PATCH 3/9] ref(tables): Share one sortable header cell across table shells SimpleTable.HeaderCell, the explore table kit, and the replay grids each drew their own sortable header: a label, an optional tooltip, and an arrow whose direction was recomputed from a different sort shape every time. Consolidate them into one SortableHeaderCell in components/tables. Each shell keeps its own padding, border, background, and typography; the shared cell owns the interactive element, the label, truncation, and the sort indicator. Sortable headers now behave the same everywhere: a