From 7c7e429ee03047ea508a7c804d61e4adf27915a3 Mon Sep 17 00:00:00 2001 From: nsdeschenes Date: Fri, 27 Mar 2026 09:30:17 -0400 Subject: [PATCH 1/7] feat(compactSelect): Add ClearButton to CompositeSelect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add CompositeSelect.ClearButton as a static property, following the same pattern as CompositeSelect.Region. Renders a styled 'Clear' button that delegates all behavior to the caller's onClick — no auto-close, matching CompactSelect's built-in clear button behavior. Co-Authored-By: Claude Sonnet 4.6 --- .../core/compactSelect/composite.tsx | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/static/app/components/core/compactSelect/composite.tsx b/static/app/components/core/compactSelect/composite.tsx index 7d67012bf994f7..41524fa0e31d12 100644 --- a/static/app/components/core/compactSelect/composite.tsx +++ b/static/app/components/core/compactSelect/composite.tsx @@ -4,6 +4,8 @@ import {FocusScope} from '@react-aria/focus'; import {Item} from '@react-stately/collections'; import type {DistributedOmit} from 'type-fest'; +import {Button, type ButtonProps} from '@sentry/scraps/button'; + import {t} from 'sentry/locale'; import type {ControlProps} from './control'; @@ -118,6 +120,16 @@ CompositeSelect.Region = function ( return null; }; +CompositeSelect.ClearButton = function CompositeSelectClearButton( + props: DistributedOmit +) { + return ( + + {t('Clear')} + + ); +}; + export {CompositeSelect}; type RegionProps = CompositeSelectRegion & { @@ -152,6 +164,14 @@ function Region({ ); } +const StyledClearButton = styled(Button)` + font-size: inherit; /* Inherit font size from MenuHeader */ + font-weight: ${p => p.theme.font.weight.sans.regular}; + color: ${p => p.theme.tokens.content.secondary}; + padding: 0 ${p => p.theme.space.xs}; + margin: -${p => p.theme.space.sm} -${p => p.theme.space.xs}; +`; + const RegionsWrap = styled('div')` min-height: 0; overflow: auto; From f0f9039eb15d77d9ffb571667b85acb30acbbf1d Mon Sep 17 00:00:00 2001 From: nsdeschenes Date: Fri, 27 Mar 2026 09:31:12 -0400 Subject: [PATCH 2/7] docs(compactSelect): Document CompositeSelect.ClearButton Add a Clear Button section to the CompositeSelect story with a live demo showing conditional rendering and the correct usage pattern. Update the compound components list and clarify that the menu stays open after clearing, matching CompactSelect's behavior. Co-Authored-By: Claude Sonnet 4.6 --- .../core/compactSelect/composite.mdx | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/static/app/components/core/compactSelect/composite.mdx b/static/app/components/core/compactSelect/composite.mdx index 55f9b2a1126477..8c5ab1f6618518 100644 --- a/static/app/components/core/compactSelect/composite.mdx +++ b/static/app/components/core/compactSelect/composite.mdx @@ -53,6 +53,7 @@ Use `` when you need a dropdown with multiple independent selec - **``**: The wrapper component that manages the dropdown - **``**: Individual selection sections within the dropdown +- **``**: A "Clear" button for use in `menuHeaderTrailingItems` that calls your `onClick` to reset all regions Each region acts like an independent ``, requiring its own `value`, `onChange`, and `options`. @@ -124,6 +125,102 @@ const [day, setDay] = useState('1'); ; ``` +## Clear Button + +Use `` in `menuHeaderTrailingItems` to add a "Clear" button to the menu header. When clicked, it calls your `onClick` handler where you reset each region's value. The menu stays open, matching the behavior of the built-in clear button in ``. + +Only render the button when there is an active selection to clear. + +export function ClearButtonDemo() { + const [month, setMonth] = useState(null); + const [tags, setTags] = useState([]); + const monthOptions = [ + {value: 'jan', label: 'January'}, + {value: 'feb', label: 'February'}, + {value: 'mar', label: 'March'}, + ]; + const tagOptions = [ + {value: 'cool', label: 'cool'}, + {value: 'funny', label: 'funny'}, + {value: 'awesome', label: 'awesome'}, + ]; + const hasSelection = month !== null || tags.length > 0; + return ( + { + setMonth(null); + setTags([]); + }} + /> + ) : null + } + trigger={props => ( + } {...props}> + Filters + + )} + > + setMonth(selection.value)} + options={monthOptions} + /> + setTags(selection.map(s => s.value))} + options={tagOptions} + /> + + ); +} + + + + + +```jsx +const [month, setMonth] = useState(null); +const [tags, setTags] = useState([]); +const hasSelection = month !== null || tags.length > 0; + + { + setMonth(null); + setTags([]); + }} + /> + ) : null + } + trigger={props => Filters} +> + setMonth(selection.value)} + options={monthOptions} + /> + setTags(selection.map(s => s.value))} + options={tagOptions} + /> +; +``` + ## Multi-Select Regions Individual regions can enable multi-select by setting the `multiple` prop. This allows mixing single and multi-select behavior within the same dropdown. From 8febc1e70466e4a6a0ff7283c682ec360b505169 Mon Sep 17 00:00:00 2001 From: nsdeschenes Date: Fri, 27 Mar 2026 09:31:23 -0400 Subject: [PATCH 3/7] feat(metrics): Use CompositeSelect.ClearButton in aggregate dropdown Add a clear button to the metric aggregate selector's menu header. Hide it when the current selection is already the default value for the metric type, since clearing would be a no-op. Co-Authored-By: Claude Sonnet 4.6 --- .../explore/metrics/metricToolbar/aggregateDropdown.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/static/app/views/explore/metrics/metricToolbar/aggregateDropdown.tsx b/static/app/views/explore/metrics/metricToolbar/aggregateDropdown.tsx index d308eea86c3bef..f306397aaa67a1 100644 --- a/static/app/views/explore/metrics/metricToolbar/aggregateDropdown.tsx +++ b/static/app/views/explore/metrics/metricToolbar/aggregateDropdown.tsx @@ -48,10 +48,18 @@ export function AggregateDropdown({traceMetric}: {traceMetric: TraceMetric}) { } const selectedList = [...selectedNames].filter(Boolean); + const defaultValue = DEFAULT_YAXIS_BY_TYPE[traceMetric.type]; + const isDefaultSelection = + selectedList.length === 1 && selectedList[0] === defaultValue; return ( handleChange([])} /> + } style={{width: '100%'}} trigger={triggerProps => ( Date: Fri, 27 Mar 2026 10:17:11 -0400 Subject: [PATCH 4/7] ref(compactSelect): Re-export ClearButton from control in CompositeSelect Instead of maintaining a duplicate styled component in composite.tsx, re-export the ClearButton from control.tsx so both CompactSelect and CompositeSelect share the same implementation. Co-Authored-By: Claude Sonnet 4.6 --- .../components/core/compactSelect/composite.mdx | 4 ++-- .../components/core/compactSelect/composite.tsx | 16 ++++------------ .../components/core/compactSelect/control.tsx | 2 +- 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/static/app/components/core/compactSelect/composite.mdx b/static/app/components/core/compactSelect/composite.mdx index 8c5ab1f6618518..2468a6805170b3 100644 --- a/static/app/components/core/compactSelect/composite.mdx +++ b/static/app/components/core/compactSelect/composite.mdx @@ -53,7 +53,7 @@ Use `` when you need a dropdown with multiple independent selec - **``**: The wrapper component that manages the dropdown - **``**: Individual selection sections within the dropdown -- **``**: A "Clear" button for use in `menuHeaderTrailingItems` that calls your `onClick` to reset all regions +- **``**: A "Clear" button for use in `menuHeaderTrailingItems` that calls your `onClick` to reset all regions. Re-exported from ``'s internal clear button for visual consistency Each region acts like an independent ``, requiring its own `value`, `onChange`, and `options`. @@ -127,7 +127,7 @@ const [day, setDay] = useState('1'); ## Clear Button -Use `` in `menuHeaderTrailingItems` to add a "Clear" button to the menu header. When clicked, it calls your `onClick` handler where you reset each region's value. The menu stays open, matching the behavior of the built-in clear button in ``. +Use `` in `menuHeaderTrailingItems` to add a "Clear" button to the menu header. When clicked, it calls your `onClick` handler where you reset each region's value. The menu stays open, matching the behavior of the built-in clear button in ``. The component is the same styled button used internally by ``, ensuring visual consistency. Only render the button when there is an active selection to clear. diff --git a/static/app/components/core/compactSelect/composite.tsx b/static/app/components/core/compactSelect/composite.tsx index 41524fa0e31d12..34ea6b5ce98abf 100644 --- a/static/app/components/core/compactSelect/composite.tsx +++ b/static/app/components/core/compactSelect/composite.tsx @@ -4,12 +4,12 @@ import {FocusScope} from '@react-aria/focus'; import {Item} from '@react-stately/collections'; import type {DistributedOmit} from 'type-fest'; -import {Button, type ButtonProps} from '@sentry/scraps/button'; +import {type ButtonProps} from '@sentry/scraps/button'; import {t} from 'sentry/locale'; +import {ClearButton, Control} from './control'; import type {ControlProps} from './control'; -import {Control} from './control'; import type {MultipleListProps, SingleListProps} from './list'; import {List} from './list'; import {EmptyMessage} from './styles'; @@ -124,9 +124,9 @@ CompositeSelect.ClearButton = function CompositeSelectClearButton( props: DistributedOmit ) { return ( - + {t('Clear')} - + ); }; @@ -164,14 +164,6 @@ function Region({ ); } -const StyledClearButton = styled(Button)` - font-size: inherit; /* Inherit font size from MenuHeader */ - font-weight: ${p => p.theme.font.weight.sans.regular}; - color: ${p => p.theme.tokens.content.secondary}; - padding: 0 ${p => p.theme.space.xs}; - margin: -${p => p.theme.space.sm} -${p => p.theme.space.xs}; -`; - const RegionsWrap = styled('div')` min-height: 0; overflow: auto; diff --git a/static/app/components/core/compactSelect/control.tsx b/static/app/components/core/compactSelect/control.tsx index 493efbef4a61c2..bc99eecc9b20f2 100644 --- a/static/app/components/core/compactSelect/control.tsx +++ b/static/app/components/core/compactSelect/control.tsx @@ -643,7 +643,7 @@ const StyledLoadingIndicator = styled(LoadingIndicator)` } `; -const ClearButton = styled(Button)` +export const ClearButton = styled(Button)` font-size: inherit; /* Inherit font size from MenuHeader */ font-weight: ${p => p.theme.font.weight.sans.regular}; color: ${p => p.theme.tokens.content.secondary}; From d867c223268520901b82b9e0553d7d2054979ffc Mon Sep 17 00:00:00 2001 From: nsdeschenes Date: Fri, 27 Mar 2026 10:47:50 -0400 Subject: [PATCH 5/7] fix(compactSelect): Fix TypeScript errors in CompositeSelect MDX docs Add SelectKey type import and annotate useState calls with explicit types to resolve TS2322 and TS2345 errors surfaced by typecheck:mdx. Co-Authored-By: Claude Sonnet 4 --- static/app/components/core/compactSelect/composite.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/static/app/components/core/compactSelect/composite.mdx b/static/app/components/core/compactSelect/composite.mdx index 2468a6805170b3..26c71371cdc452 100644 --- a/static/app/components/core/compactSelect/composite.mdx +++ b/static/app/components/core/compactSelect/composite.mdx @@ -14,7 +14,7 @@ resources: import {useState} from 'react'; -import {CompositeSelect} from '@sentry/scraps/compactSelect'; +import {CompositeSelect, type SelectKey} from '@sentry/scraps/compactSelect'; import {OverlayTrigger} from '@sentry/scraps/overlayTrigger'; import {IconSentry} from 'sentry/icons'; @@ -132,8 +132,8 @@ Use `` in `menuHeaderTrailingItems` to add a "Clear Only render the button when there is an active selection to clear. export function ClearButtonDemo() { - const [month, setMonth] = useState(null); - const [tags, setTags] = useState([]); + const [month, setMonth] = useState(null); + const [tags, setTags] = useState([]); const monthOptions = [ {value: 'jan', label: 'January'}, {value: 'feb', label: 'February'}, From 63e9b8de76747392ada717682264e40e159f363d Mon Sep 17 00:00:00 2001 From: nsdeschenes Date: Fri, 27 Mar 2026 12:43:54 -0400 Subject: [PATCH 6/7] fix(compactSelect): Fix lint errors in CompositeSelect MDX docs Replace TypeScript-syntax state annotations with JS-compatible initializers that TypeScript can still infer correctly. MDX files are parsed by acorn which does not support TypeScript syntax, so inline type annotations caused eslint parse errors. Use useState('') for month (infers string via generic inference from options) and tagOptions.slice(0,0).map(o => o.value) for tags (infers string[] from the mapped options array). Co-Authored-By: Claude Sonnet 4 --- static/app/components/core/compactSelect/composite.mdx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/static/app/components/core/compactSelect/composite.mdx b/static/app/components/core/compactSelect/composite.mdx index 26c71371cdc452..94c4a9045cdb45 100644 --- a/static/app/components/core/compactSelect/composite.mdx +++ b/static/app/components/core/compactSelect/composite.mdx @@ -14,7 +14,7 @@ resources: import {useState} from 'react'; -import {CompositeSelect, type SelectKey} from '@sentry/scraps/compactSelect'; +import {CompositeSelect} from '@sentry/scraps/compactSelect'; import {OverlayTrigger} from '@sentry/scraps/overlayTrigger'; import {IconSentry} from 'sentry/icons'; @@ -132,8 +132,6 @@ Use `` in `menuHeaderTrailingItems` to add a "Clear Only render the button when there is an active selection to clear. export function ClearButtonDemo() { - const [month, setMonth] = useState(null); - const [tags, setTags] = useState([]); const monthOptions = [ {value: 'jan', label: 'January'}, {value: 'feb', label: 'February'}, @@ -144,7 +142,9 @@ export function ClearButtonDemo() { {value: 'funny', label: 'funny'}, {value: 'awesome', label: 'awesome'}, ]; - const hasSelection = month !== null || tags.length > 0; + const [month, setMonth] = useState(''); + const [tags, setTags] = useState(tagOptions.slice(0, 0).map(o => o.value)); + const hasSelection = month !== '' || tags.length > 0; return ( { - setMonth(null); + setMonth(''); setTags([]); }} /> From a61572345e594ad88863296ac2b190073ebb2cb5 Mon Sep 17 00:00:00 2001 From: nsdeschenes Date: Tue, 31 Mar 2026 14:04:41 -0300 Subject: [PATCH 7/7] remove extra type casting --- .../metrics/metricToolbar/aggregateDropdown.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/static/app/views/explore/metrics/metricToolbar/aggregateDropdown.tsx b/static/app/views/explore/metrics/metricToolbar/aggregateDropdown.tsx index f306397aaa67a1..ab1dc14f95dd1a 100644 --- a/static/app/views/explore/metrics/metricToolbar/aggregateDropdown.tsx +++ b/static/app/views/explore/metrics/metricToolbar/aggregateDropdown.tsx @@ -96,11 +96,11 @@ export function AggregateDropdown({traceMetric}: {traceMetric: TraceMetric}) { return ( >) => handleChange(opts)} + onChange={handleChange} /> ); } @@ -108,10 +108,10 @@ export function AggregateDropdown({traceMetric}: {traceMetric: TraceMetric}) { return ( ) => handleChange([opt])} + value={activeValues[0]} + onChange={opt => handleChange([opt])} /> ); })}