From 14c01d8f80d8952f9ceee0715fd43c4e3f2c2643 Mon Sep 17 00:00:00 2001 From: Brendan Hy Date: Wed, 8 Apr 2026 10:04:22 -0700 Subject: [PATCH] fix(billing): Subscription UI misalignment --- .../usageOverview/components/table.spec.tsx | 23 +++++++++- .../usageOverview/components/tableRow.tsx | 44 +++++++++---------- 2 files changed, 43 insertions(+), 24 deletions(-) diff --git a/static/gsApp/views/subscriptionPage/usageOverview/components/table.spec.tsx b/static/gsApp/views/subscriptionPage/usageOverview/components/table.spec.tsx index 35aea15e6bc392..93639df7efa7b0 100644 --- a/static/gsApp/views/subscriptionPage/usageOverview/components/table.spec.tsx +++ b/static/gsApp/views/subscriptionPage/usageOverview/components/table.spec.tsx @@ -6,7 +6,7 @@ import { SubscriptionFixture, SubscriptionWithLegacySeerFixture, } from 'getsentry-test/fixtures/subscription'; -import {render, screen, within} from 'sentry-test/reactTestingLibrary'; +import {render, screen, userEvent, within} from 'sentry-test/reactTestingLibrary'; import {DataCategory} from 'sentry/types/core'; @@ -48,6 +48,27 @@ describe('UsageOverviewTable', () => { ).toBeGreaterThan(0); }); + it('does not add an extra cell when hovering a product row', async () => { + render( + + ); + + await screen.findByRole('columnheader', {name: 'Feature'}); + + const attachmentsRow = screen.getByTestId('product-row-attachments'); + expect(within(attachmentsRow).getAllByRole('cell')).toHaveLength(3); + + await userEvent.hover(attachmentsRow); + + expect(within(attachmentsRow).getAllByRole('cell')).toHaveLength(3); + }); + it('renders columns for non-billing users', async () => { organization.access = []; render( diff --git a/static/gsApp/views/subscriptionPage/usageOverview/components/tableRow.tsx b/static/gsApp/views/subscriptionPage/usageOverview/components/tableRow.tsx index ba7e3e639697e7..595aa05379df85 100644 --- a/static/gsApp/views/subscriptionPage/usageOverview/components/tableRow.tsx +++ b/static/gsApp/views/subscriptionPage/usageOverview/components/tableRow.tsx @@ -1,4 +1,4 @@ -import {Fragment, useState} from 'react'; +import {Fragment} from 'react'; import {useTheme} from '@emotion/react'; import styled from '@emotion/styled'; @@ -74,7 +74,6 @@ export function UsageOverviewTableRow({ const showPanelInline = useMedia( `(max-width: calc(${theme.breakpoints[SIDE_PANEL_MIN_SCREEN_BREAKPOINT]} - 1px))` ); - const [isHovered, setIsHovered] = useState(false); const showAdditionalSpendColumn = subscription.canSelfServe || supportsPayg(subscription); @@ -238,8 +237,6 @@ export function UsageOverviewTableRow({ setIsHovered(true)} - onMouseLeave={() => setIsHovered(false)} isSelected={isSelected} onClick={() => onRowClick(product)} onKeyDown={e => { @@ -355,8 +352,6 @@ export function UsageOverviewTableRow({ ) : null} - - {(isSelected || isHovered) && } {showPanelInline && isSelected && ( @@ -386,14 +381,11 @@ function DisabledProductRow({ usageData, subscription, }: DisabledProductRowProps) { - const [isHovered, setIsHovered] = useState(false); const isSelected = selectedProduct === product; return ( setIsHovered(true)} - onMouseLeave={() => setIsHovered(false)} isSelected={isSelected} onClick={() => onRowClick(product)} onKeyDown={e => { @@ -448,7 +440,6 @@ function DisabledProductRow({ )} - {(isSelected || isHovered) && } {showPanelInline && isSelected && ( @@ -493,6 +484,26 @@ const ProductRow = styled(Row)<{isSelected: boolean}>` &:active { background: ${p => p.theme.tokens.interactive.transparent.neutral.background.active}; } + + &::after { + content: ''; + position: absolute; + right: -1px; + top: 30%; + width: 4px; + height: 22px; + border-radius: 2px; + background: ${p => + p.isSelected + ? p.theme.tokens.graphics.accent.vibrant + : p.theme.tokens.graphics.neutral.moderate}; + opacity: ${p => (p.isSelected ? 1 : 0)}; + pointer-events: none; + } + + &:hover::after { + opacity: 1; + } `; const MobilePanelContainer = styled('td')` @@ -501,19 +512,6 @@ const MobilePanelContainer = styled('td')` grid-column: 1 / -1; `; -const SelectedPill = styled('td')<{isSelected: boolean}>` - position: absolute; - right: -1px; - top: 30%; - width: 4px; - height: 22px; - border-radius: 2px; - background: ${p => - p.isSelected - ? p.theme.tokens.graphics.accent.vibrant - : p.theme.tokens.graphics.neutral.moderate}; -`; - const IconContainer = styled('span')` display: inline-block; vertical-align: middle;