diff --git a/static/app/components/badge/groupPriority.tsx b/static/app/components/badge/groupPriority.tsx index b57ce7809be134..00622e02a288de 100644 --- a/static/app/components/badge/groupPriority.tsx +++ b/static/app/components/badge/groupPriority.tsx @@ -247,19 +247,6 @@ export function GroupPriorityDropdown({ ); } -const DropdownButton = styled(Button)` - font-weight: ${p => p.theme.font.weight.sans.regular}; - border: none; - padding: 0; - height: unset; - border-radius: 20px; - box-shadow: none; - - > span > div { - border-radius: 20px; - } -`; - const StyledTag = styled(Tag)` gap: ${p => p.theme.space['2xs']}; position: relative; @@ -267,6 +254,15 @@ const StyledTag = styled(Tag)` overflow: hidden; `; +const DropdownButton = styled(Button)` + padding: 0; + border-radius: ${p => p.theme.radius.full}; + + ${StyledTag} { + border-radius: ${p => p.theme.radius.full}; + } +`; + const InlinePlaceholder = styled(Placeholder)` display: inline-block; vertical-align: middle; diff --git a/static/app/components/core/button/button.spec.tsx b/static/app/components/core/button/button.spec.tsx index fb99d7c1d8c592..1ed0e958195088 100644 --- a/static/app/components/core/button/button.spec.tsx +++ b/static/app/components/core/button/button.spec.tsx @@ -26,6 +26,37 @@ describe('Button', () => { expect(spy).not.toHaveBeenCalled(); }); + + it('does not call `onClick` on busy buttons', async () => { + const spy = jest.fn(); + render( + + ); + await userEvent.click(screen.getByText('Click me')); + + expect(spy).not.toHaveBeenCalled(); + }); + + it('shows spinner when busy', () => { + render(); + + const button = screen.getByRole('button', {name: 'Busy Button'}); + expect(button).toHaveAttribute('aria-busy', 'true'); + const spinner = button.querySelector('[aria-hidden="true"]'); + expect(spinner).toBeInTheDocument(); + }); + + it('hides spinner when not busy', () => { + render(); + + const button = screen.getByRole('button', {name: 'Normal Button'}); + expect(button).not.toHaveAttribute('aria-busy'); + + const spinner = button.querySelector('[aria-hidden="true"]'); + expect(spinner).not.toBeInTheDocument(); + }); }); describe('LinkButton', () => { diff --git a/static/app/components/core/button/button.tsx b/static/app/components/core/button/button.tsx index b84cf750c79b20..5ece056c82a57a 100644 --- a/static/app/components/core/button/button.tsx +++ b/static/app/components/core/button/button.tsx @@ -1,3 +1,4 @@ +import {keyframes} from '@emotion/react'; import styled from '@emotion/styled'; import {Flex} from '@sentry/scraps/layout'; @@ -55,6 +56,7 @@ export function Button({ minWidth="0" height="100%" whiteSpace="nowrap" + visibility={busy ? 'hidden' : undefined} > {props.icon && ( )} {props.children} + {busy && ( + + {({className}) => } + + )} @@ -80,3 +93,22 @@ export function Button({ const StyledButton = styled('button')` ${p => getButtonStyles(p as any)} `; + +const spin = keyframes` + to { + transform: rotate(360deg); + } +`; + +const BusySpinner = styled('span')` + &::after { + content: ''; + display: block; + width: 1em; + height: 1em; + border-radius: 50%; + border: 2px solid currentColor; + border-top-color: transparent; + animation: ${spin} 0.6s linear infinite; + } +`; diff --git a/static/app/components/core/layout/container.tsx b/static/app/components/core/layout/container.tsx index ac395b2476a0f4..3aaf34a011b475 100644 --- a/static/app/components/core/layout/container.tsx +++ b/static/app/components/core/layout/container.tsx @@ -86,6 +86,8 @@ interface ContainerLayoutProps { alignSelf?: Responsive; justifySelf?: Responsive; + visibility?: Responsive<'visible' | 'hidden' | 'collapse'>; + // Text Wrapping whiteSpace?: Responsive< 'break-spaces' | 'normal' | 'nowrap' | 'pre' | 'pre-line' | 'pre-wrap' @@ -225,6 +227,7 @@ const omitContainerProps = new Set([ 'right', 'row', 'top', + 'visibility', 'width', 'whiteSpace', ]); @@ -314,6 +317,7 @@ export const Container = styled( ${p => rc('border-left', p.borderLeft, p.theme, getBorder)}; ${p => rc('border-right', p.borderRight, p.theme, getBorder)}; + ${p => rc('visibility', p.visibility, p.theme)}; ${p => rc('white-space', p.whiteSpace, p.theme)}; /**