From b21d903080c7de89ae65b49593ac1565cbdc3bd1 Mon Sep 17 00:00:00 2001 From: TkDodo Date: Thu, 21 May 2026 11:47:38 +0200 Subject: [PATCH 1/5] ref: give a type parameter to ListItems --- eslint.config.ts | 2 ++ .../core/compactSelect/gridList/index.tsx | 11 ++++++----- .../core/compactSelect/gridList/section.tsx | 17 ++++++++++++----- .../core/compactSelect/listBox/index.tsx | 12 +++++------- .../core/compactSelect/listBox/section.tsx | 15 +++++++++------ .../app/components/core/compactSelect/types.tsx | 3 +++ 6 files changed, 37 insertions(+), 23 deletions(-) diff --git a/eslint.config.ts b/eslint.config.ts index 323d3a70330f..8c31cf017df1 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -1406,6 +1406,8 @@ export default typescript.config([ ...(enableTypeAwareLinting && { '@typescript-eslint/no-unsafe-argument': 'error', '@typescript-eslint/no-unsafe-call': 'error', + '@typescript-eslint/no-unsafe-enum-comparison': 'error', + '@typescript-eslint/no-unsafe-member-access': 'error', '@typescript-eslint/no-unsafe-return': 'error', }), }, diff --git a/static/app/components/core/compactSelect/gridList/index.tsx b/static/app/components/core/compactSelect/gridList/index.tsx index 0353809e9e78..99969f386b0c 100644 --- a/static/app/components/core/compactSelect/gridList/index.tsx +++ b/static/app/components/core/compactSelect/gridList/index.tsx @@ -14,6 +14,7 @@ import { SizeLimitMessage, useVirtualizedItems, } from '@sentry/scraps/compactSelect'; +import type {ListItemBase} from '@sentry/scraps/compactSelect/types'; import {Container} from '@sentry/scraps/layout'; import {t} from 'sentry/locale'; @@ -21,7 +22,7 @@ import {t} from 'sentry/locale'; import {GridListOption, type GridListOptionProps} from './option'; import {GridListSection} from './section'; -interface GridListProps +interface GridListProps extends Omit, 'children'>, Omit< @@ -43,8 +44,8 @@ interface GridListProps * Object containing the selection state and focus position, needed for * `useGridList()`. */ - listState: ListState; - children?: CollectionChildren; + listState: ListState; + children?: CollectionChildren; /** * Text label to be rendered as heading on top of grid list. */ @@ -70,7 +71,7 @@ interface GridListProps * inside. Grid lists allow users to focus on those child elements (using the Arrow * Left/Right keys) and interact with them, which isn't possible with list boxes. */ -function GridList({ +function GridList({ listState, size = 'md', label, @@ -78,7 +79,7 @@ function GridList({ keyDownHandler, virtualized, ...props -}: GridListProps) { +}: GridListProps) { const ref = useRef(null); const labelId = useId(); const {gridProps} = useGridList( diff --git a/static/app/components/core/compactSelect/gridList/section.tsx b/static/app/components/core/compactSelect/gridList/section.tsx index 228c1295e65e..b97042cb67d9 100644 --- a/static/app/components/core/compactSelect/gridList/section.tsx +++ b/static/app/components/core/compactSelect/gridList/section.tsx @@ -12,12 +12,13 @@ import { SectionWrap, SelectFilterContext, } from '@sentry/scraps/compactSelect'; +import type {ListItemBase} from '@sentry/scraps/compactSelect/types'; import {GridListOption, type GridListOptionProps} from './option'; -interface GridListSectionProps { - listState: ListState; - node: Node; +interface GridListSectionProps { + listState: ListState; + node: Node; size: GridListOptionProps['size']; } @@ -25,13 +26,19 @@ interface GridListSectionProps { * A
  • element that functions as a grid list section (renders a nested
      * inside). https://react-spectrum.adobe.com/react-aria/useGridList.html */ -export function GridListSection({node, listState, size}: GridListSectionProps) { +export function GridListSection({ + node, + listState, + size, +}: GridListSectionProps) { const titleId = useId(); const {separatorProps} = useSeparator({elementType: 'li'}); const showToggleAllButton = listState.selectionManager.selectionMode === 'multiple' && - node.value.showToggleAllButton; + !!node.value && + 'showToggleAllButton' in node.value && + !!node.value.showToggleAllButton; const hiddenOptions = useContext(SelectFilterContext); const childNodes = useMemo( diff --git a/static/app/components/core/compactSelect/listBox/index.tsx b/static/app/components/core/compactSelect/listBox/index.tsx index a486ae5990b7..9ba1ae183e59 100644 --- a/static/app/components/core/compactSelect/listBox/index.tsx +++ b/static/app/components/core/compactSelect/listBox/index.tsx @@ -13,6 +13,7 @@ import { SizeLimitMessage, } from '@sentry/scraps/compactSelect'; import type {SelectKey} from '@sentry/scraps/compactSelect'; +import type {ListItemBase} from '@sentry/scraps/compactSelect/types'; import {Container} from '@sentry/scraps/layout'; import {t} from 'sentry/locale'; @@ -21,11 +22,7 @@ import type {FormSize} from 'sentry/utils/theme'; import {ListBoxOption} from './option'; import {ListBoxSection} from './section'; -// explicitly using object here because Record requires an index signature -// eslint-disable-next-line @typescript-eslint/no-restricted-types -type ObjectLike = object; - -interface ListBoxProps +interface ListBoxProps extends Omit< React.HTMLAttributes, @@ -122,7 +119,7 @@ const DEFAULT_KEY_DOWN_HANDLER = () => true; * If interactive children are necessary, consider using grid lists instead (by setting * the `grid` prop on CompactSelect to true). */ -export function ListBox({ +export function ListBox({ ref, listState, size = 'md', @@ -296,7 +293,7 @@ const heightEstimations = { */ const listPaddingVertical = 4; -function useVirtualizedItems({ +function useVirtualizedItems({ listItems, virtualized = false, size, @@ -315,6 +312,7 @@ function useVirtualizedItems({ getScrollElement: () => scrollElementRef?.current, estimateSize: index => { const item = listItems[index]; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (item?.props?.details) { return heightEstimation.large; } diff --git a/static/app/components/core/compactSelect/listBox/section.tsx b/static/app/components/core/compactSelect/listBox/section.tsx index 66a9e5430dbe..961ce7ca1c52 100644 --- a/static/app/components/core/compactSelect/listBox/section.tsx +++ b/static/app/components/core/compactSelect/listBox/section.tsx @@ -14,13 +14,14 @@ import { SectionWrap, } from '@sentry/scraps/compactSelect'; import type {SelectKey} from '@sentry/scraps/compactSelect'; +import type {ListItemBase} from '@sentry/scraps/compactSelect/types'; import {ListBoxOption, type ListBoxOptionProps} from './option'; -interface ListBoxSectionProps extends AriaListBoxSectionProps { +interface ListBoxSectionProps extends AriaListBoxSectionProps { hiddenOptions: Set; - item: Node; - listState: ListState; + item: Node; + listState: ListState; showSectionHeaders: boolean; size: ListBoxOptionProps['size']; 'data-index'?: number; @@ -32,7 +33,7 @@ interface ListBoxSectionProps extends AriaListBoxSectionProps { * A
    • element that functions as a list box section (renders a nested
        * inside). https://react-spectrum.adobe.com/react-aria/useListBox.html */ -export function ListBoxSection({ +export function ListBoxSection({ item, listState, size, @@ -41,7 +42,7 @@ export function ListBoxSection({ showDetails = true, ref, 'data-index': dataIndex, -}: ListBoxSectionProps) { +}: ListBoxSectionProps) { const {itemProps, headingProps, groupProps} = useListBoxSection({ heading: item.rendered, 'aria-label': item['aria-label'], @@ -51,7 +52,9 @@ export function ListBoxSection({ const showToggleAllButton = listState.selectionManager.selectionMode === 'multiple' && - item.value.showToggleAllButton; + !!item.value && + 'showToggleAllButton' in item.value && + !!item.value.showToggleAllButton; const childItems = useMemo( () => [...item.childNodes].filter(child => !hiddenOptions.has(child.key)), diff --git a/static/app/components/core/compactSelect/types.tsx b/static/app/components/core/compactSelect/types.tsx index 5a33ae55542c..785d5d2b2bfb 100644 --- a/static/app/components/core/compactSelect/types.tsx +++ b/static/app/components/core/compactSelect/types.tsx @@ -1,5 +1,8 @@ import type {SelectValue} from 'sentry/types/core'; +// explicitly using object here because Record requires an index signature +// eslint-disable-next-line @typescript-eslint/no-restricted-types +export type ListItemBase = object; export type SelectKey = string | number; export interface SelectOption extends SelectValue { From 84ce254cec2d7e64d0ce5262a0f4be2e9bace0ff Mon Sep 17 00:00:00 2001 From: TkDodo Date: Thu, 21 May 2026 11:54:43 +0200 Subject: [PATCH 2/5] fix: better access to showToggleAllButton --- .../app/components/core/compactSelect/gridList/section.tsx | 4 +--- static/app/components/core/compactSelect/list.tsx | 5 +++-- static/app/components/core/compactSelect/listBox/section.tsx | 4 +--- static/app/components/core/compactSelect/types.tsx | 2 +- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/static/app/components/core/compactSelect/gridList/section.tsx b/static/app/components/core/compactSelect/gridList/section.tsx index b97042cb67d9..dedca3e0c7c0 100644 --- a/static/app/components/core/compactSelect/gridList/section.tsx +++ b/static/app/components/core/compactSelect/gridList/section.tsx @@ -36,9 +36,7 @@ export function GridListSection({ const showToggleAllButton = listState.selectionManager.selectionMode === 'multiple' && - !!node.value && - 'showToggleAllButton' in node.value && - !!node.value.showToggleAllButton; + node.value?.showToggleAllButton; const hiddenOptions = useContext(SelectFilterContext); const childNodes = useMemo( diff --git a/static/app/components/core/compactSelect/list.tsx b/static/app/components/core/compactSelect/list.tsx index df69c281a15d..f15f844f0cf2 100644 --- a/static/app/components/core/compactSelect/list.tsx +++ b/static/app/components/core/compactSelect/list.tsx @@ -12,6 +12,7 @@ import {ControlContext} from './control'; import {GridList} from './gridList'; import {ListBox} from './listBox'; import type { + ListItemBase, SelectKey, SelectOption, SelectOptionOrSectionWithKey, @@ -184,7 +185,7 @@ export function List({ /** * Props to be passed into useListState() */ - const listStateProps = useMemo>>(() => { + const listStateProps = useMemo>>(() => { const disabledKeys = [ ...getDisabledOptions(items, isOptionDisabled), ...hiddenOptions, @@ -374,7 +375,7 @@ export function List({ )} {multiple && sections.map(section => - section.value.showToggleAllButton ? ( + section.value?.showToggleAllButton ? ( ({ const showToggleAllButton = listState.selectionManager.selectionMode === 'multiple' && - !!item.value && - 'showToggleAllButton' in item.value && - !!item.value.showToggleAllButton; + item.value?.showToggleAllButton; const childItems = useMemo( () => [...item.childNodes].filter(child => !hiddenOptions.has(child.key)), diff --git a/static/app/components/core/compactSelect/types.tsx b/static/app/components/core/compactSelect/types.tsx index 785d5d2b2bfb..5bad32ef9e21 100644 --- a/static/app/components/core/compactSelect/types.tsx +++ b/static/app/components/core/compactSelect/types.tsx @@ -2,7 +2,7 @@ import type {SelectValue} from 'sentry/types/core'; // explicitly using object here because Record requires an index signature // eslint-disable-next-line @typescript-eslint/no-restricted-types -export type ListItemBase = object; +export type ListItemBase = object & {showToggleAllButton?: boolean}; export type SelectKey = string | number; export interface SelectOption extends SelectValue { From d0038f4cc004cc6e316b351d0de63e6a844e16de Mon Sep 17 00:00:00 2001 From: TkDodo Date: Thu, 21 May 2026 12:03:43 +0200 Subject: [PATCH 3/5] fix: className access --- static/app/components/core/layout/container.tsx | 6 ++++-- static/app/components/core/layout/surface.tsx | 9 +++++---- static/app/components/core/text/heading.tsx | 4 ++-- static/app/components/core/text/text.tsx | 4 ++-- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/static/app/components/core/layout/container.tsx b/static/app/components/core/layout/container.tsx index 3f939d44b2ed..b6855d3983b0 100644 --- a/static/app/components/core/layout/container.tsx +++ b/static/app/components/core/layout/container.tsx @@ -241,11 +241,13 @@ const omitContainerProps = new Set([ export const Container = styled( ( - props: ContainerProps | ContainerPropsWithRenderFunction + props: (ContainerProps | ContainerPropsWithRenderFunction) & { + className?: string; + } ) => { if (typeof props.children === 'function') { // When using render prop, only pass className to the child function - return props.children({className: (props as any).className}); + return props.children({className: props.className ?? ''}); } const {as, ...rest} = props; diff --git a/static/app/components/core/layout/surface.tsx b/static/app/components/core/layout/surface.tsx index 1ee39900f929..50250b34c27d 100644 --- a/static/app/components/core/layout/surface.tsx +++ b/static/app/components/core/layout/surface.tsx @@ -27,13 +27,13 @@ interface OverlaySurfaceProps extends Omit< } interface FlatSurfacePropsWithRenderFunction { - children: (props: {className: string}) => React.ReactNode; + children: (props: {className: string | undefined}) => React.ReactNode; elevation?: never; variant?: SurfaceVariant; } interface OverlaySurfacePropsWithRenderFunction { - children: (props: {className: string}) => React.ReactNode; + children: (props: {className: string | undefined}) => React.ReactNode; variant: 'overlay'; elevation?: 'low' | 'medium' | 'high'; } @@ -83,16 +83,17 @@ function isRenderFunction( export const Surface = styled( ( - props: + props: ( | SurfaceProps | FlatSurfacePropsWithRenderFunction | OverlaySurfacePropsWithRenderFunction + ) & {className?: string} ) => { if (isRenderFunction(props)) { // When using render prop, only pass className to the child function. T // The className in this case is internally generated by emotion, and not part of the // passed props. - return props.children({className: (props as any).className ?? ''}); + return props.children({className: props.className ?? ''}); } const {variant, elevation: _, ...rest} = props; diff --git a/static/app/components/core/text/heading.tsx b/static/app/components/core/text/heading.tsx index 2fadf3be7f2a..b588e8771288 100644 --- a/static/app/components/core/text/heading.tsx +++ b/static/app/components/core/text/heading.tsx @@ -56,10 +56,10 @@ export type HeadingPropsWithRenderFunction = BaseHeadingProps & >; export const Heading = styled( - (props: HeadingProps | HeadingPropsWithRenderFunction) => { + (props: (HeadingProps | HeadingPropsWithRenderFunction) & {className?: string}) => { if (typeof props.children === 'function') { // When using render prop, only pass className to the child function - return props.children({className: (props as any).className}); + return props.children({className: props.className ?? ''}); } const {children, as, ...rest} = props as HeadingProps; const HeadingComponent = as; diff --git a/static/app/components/core/text/text.tsx b/static/app/components/core/text/text.tsx index fe918b898c6c..5f69583e2186 100644 --- a/static/app/components/core/text/text.tsx +++ b/static/app/components/core/text/text.tsx @@ -184,11 +184,11 @@ export type TextPropsWithRenderFunction = export const Text = styled( ( - props: TextProps | TextPropsWithRenderFunction + props: (TextProps | TextPropsWithRenderFunction) & {className?: string} ) => { if (typeof props.children === 'function') { // When using render prop, only pass className to the child function - return props.children({className: (props as any).className}); + return props.children({className: props.className ?? ''}); } const {children, ...rest} = props as TextProps; const Component = props.as || 'span'; From 371eafce584b6f91ff56ad326517338433bb9e1d Mon Sep 17 00:00:00 2001 From: TkDodo Date: Thu, 21 May 2026 14:53:55 +0200 Subject: [PATCH 4/5] fix: itemProps --- .../core/compactSelect/gridList/index.tsx | 2 +- .../core/compactSelect/gridList/option.tsx | 17 +++++++---- .../core/compactSelect/gridList/section.tsx | 2 +- .../core/compactSelect/listBox/option.tsx | 2 +- .../segmentedControl/segmentedControl.tsx | 1 + static/app/components/core/select/option.tsx | 3 ++ static/app/components/core/select/select.tsx | 5 +++- static/app/components/core/tabs/tabList.tsx | 15 ++++++---- static/app/components/core/tabs/tabPanels.tsx | 1 + .../widgetBuilder/addToDashboardModal.tsx | 29 ++++++++++--------- .../widgetBuilder/linkToDashboardModal.tsx | 29 ++++++++++--------- static/app/utils/useOwnerOptions.tsx | 27 ++++++++++------- .../components/visualize/selectRow.tsx | 2 +- 13 files changed, 82 insertions(+), 53 deletions(-) diff --git a/static/app/components/core/compactSelect/gridList/index.tsx b/static/app/components/core/compactSelect/gridList/index.tsx index 99969f386b0c..df495c58031f 100644 --- a/static/app/components/core/compactSelect/gridList/index.tsx +++ b/static/app/components/core/compactSelect/gridList/index.tsx @@ -50,7 +50,7 @@ interface GridListProps * Text label to be rendered as heading on top of grid list. */ label?: React.ReactNode; - size?: GridListOptionProps['size']; + size?: GridListOptionProps['size']; /** * Message to be displayed when some options are hidden due to `sizeLimit`. */ diff --git a/static/app/components/core/compactSelect/gridList/option.tsx b/static/app/components/core/compactSelect/gridList/option.tsx index 676b6b0df039..50fece393cdd 100644 --- a/static/app/components/core/compactSelect/gridList/option.tsx +++ b/static/app/components/core/compactSelect/gridList/option.tsx @@ -9,6 +9,7 @@ import type {Node} from '@react-types/shared'; import {Checkbox} from '@sentry/scraps/checkbox'; import {LeadWrap} from '@sentry/scraps/compactSelect'; +import type {ListItemBase} from '@sentry/scraps/compactSelect/types'; import { InnerWrap, MenuListItem, @@ -18,9 +19,11 @@ import { import {IconCheckmark} from 'sentry/icons'; import type {FormSize} from 'sentry/utils/theme'; -export interface GridListOptionProps extends AriaGridListItemOptions { - listState: ListState; - node: Node; +export interface GridListOptionProps< + T extends ListItemBase, +> extends AriaGridListItemOptions { + listState: ListState; + node: Node; size: FormSize; } @@ -28,7 +31,11 @@ export interface GridListOptionProps extends AriaGridListItemOptions { * A
      • element with accessibile behaviors & attributes. * https://react-spectrum.adobe.com/react-aria/useGridList.html */ -export function GridListOption({node, listState, size}: GridListOptionProps) { +export function GridListOption({ + node, + listState, + size, +}: GridListOptionProps) { const ref = useRef(null); const { label, @@ -73,7 +80,7 @@ export function GridListOption({node, listState, size}: GridListOptionProps) { [label] ); - const leadingItems: MenuListItemProps['leadingItems'] = node.props.leadingItems; + const leadingItems = (node.props as MenuListItemProps).leadingItems; const leadingItemsMemo = useMemo(() => { const checkboxSize = size === 'xs' ? 'xs' : 'sm'; diff --git a/static/app/components/core/compactSelect/gridList/section.tsx b/static/app/components/core/compactSelect/gridList/section.tsx index dedca3e0c7c0..838b05cc4849 100644 --- a/static/app/components/core/compactSelect/gridList/section.tsx +++ b/static/app/components/core/compactSelect/gridList/section.tsx @@ -19,7 +19,7 @@ import {GridListOption, type GridListOptionProps} from './option'; interface GridListSectionProps { listState: ListState; node: Node; - size: GridListOptionProps['size']; + size: GridListOptionProps['size']; } /** diff --git a/static/app/components/core/compactSelect/listBox/option.tsx b/static/app/components/core/compactSelect/listBox/option.tsx index 7cfb7b1d794f..3c39e6d5f98e 100644 --- a/static/app/components/core/compactSelect/listBox/option.tsx +++ b/static/app/components/core/compactSelect/listBox/option.tsx @@ -65,7 +65,7 @@ export function ListBoxOption({ [labelProps.id, label] ); - const leadingItems: MenuListItemProps['leadingItems'] = item.props.leadingItems; + const leadingItems = (item.props as MenuListItemProps).leadingItems; const leadingItemsMemo = useMemo(() => { const checkboxSize = size === 'xs' ? 'xs' : 'sm'; diff --git a/static/app/components/core/segmentedControl/segmentedControl.tsx b/static/app/components/core/segmentedControl/segmentedControl.tsx index 3f6c73423bd7..4f7a0624618f 100644 --- a/static/app/components/core/segmentedControl/segmentedControl.tsx +++ b/static/app/components/core/segmentedControl/segmentedControl.tsx @@ -138,6 +138,7 @@ export function SegmentedControl({ nextKey={option.nextKey} prevKey={option.prevKey} value={String(option.key)} + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access isDisabled={option.props.disabled || disabled} state={state} size={size} diff --git a/static/app/components/core/select/option.tsx b/static/app/components/core/select/option.tsx index 4ba6daab332d..3428d94b1c30 100644 --- a/static/app/components/core/select/option.tsx +++ b/static/app/components/core/select/option.tsx @@ -57,9 +57,11 @@ export function SelectOption(props: Props) { innerWrapProps={{'data-test-id': value}} labelProps={{as: typeof label === 'string' ? 'p' : 'div'}} leadingItems={ + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access itemProps.__isNew__ ? ( + {/* eslint-disable-next-line @typescript-eslint/no-unsafe-member-access */} {data.leadingItems} ) : ( @@ -72,6 +74,7 @@ export function SelectOption(props: Props) { /> )} + {/* eslint-disable-next-line @typescript-eslint/no-unsafe-member-access */} {data.leadingItems} ) diff --git a/static/app/components/core/select/select.tsx b/static/app/components/core/select/select.tsx index 4d26d276502d..7b603a3b3650 100644 --- a/static/app/components/core/select/select.tsx +++ b/static/app/components/core/select/select.tsx @@ -79,7 +79,7 @@ const getStylesConfig = ({ // Unfortunately we cannot use emotions `css` helper here, since react-select // *requires* object styles, which the css helper cannot produce. const indicatorStyles: StylesConfig['clearIndicator'] & - StylesConfig['loadingIndicator'] = (provided, state: any) => ({ + StylesConfig['loadingIndicator'] = (provided, state: {isDisabled?: boolean}) => ({ ...provided, padding: '0 4px 0 4px', alignItems: 'center', @@ -639,13 +639,16 @@ export function Select !!opt.disabled} showDividers={props.showDividers} options={options || (choicesOrOptions as OptionsType)} openMenuOnFocus={props.openMenuOnFocus} + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access blurInputOnSelect={!props.multiple && !anyProps.multi} + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access closeMenuOnSelect={!(props.multiple || anyProps.multi)} hideSelectedOptions={false} tabSelectsValue={false} diff --git a/static/app/components/core/tabs/tabList.tsx b/static/app/components/core/tabs/tabList.tsx index daf22a2e48dd..f51041e501e1 100644 --- a/static/app/components/core/tabs/tabList.tsx +++ b/static/app/components/core/tabs/tabList.tsx @@ -254,20 +254,23 @@ function BaseTabList({outerWrapStyles, variant = 'flat', ...props}: BaseTabListP (a, b) => sortedKeys.indexOf(a) - sortedKeys.indexOf(b) ); - return sortedOverflowTabs.flatMap>(key => { + return sortedOverflowTabs.flatMap(key => { const item = state.collection.getItem(key); if (!item) { return []; } + const itemProps: TabListItemProps = item.props; + return { value: key, - label: item.props.children, - disabled: item.props.disabled, - tooltip: item.props.tooltip, + label: itemProps.children, + disabled: itemProps.disabled, + tooltip: itemProps.tooltip?.title, + tooltipOptions: itemProps.tooltip, textValue: item.textValue, - }; + } satisfies SelectOption; }); }, [state.collection, overflowTabs]); @@ -287,7 +290,7 @@ function BaseTabList({outerWrapStyles, variant = 'flat', ...props}: BaseTabListP orientation={orientation} size={size} overflowing={orientation === 'horizontal' && overflowTabs.includes(item.key)} - tooltipProps={item.props.tooltip} + tooltipProps={(item.props as TabListItemProps).tooltip} ref={element => { tabItemsRef.current[item.key] = element; }} diff --git a/static/app/components/core/tabs/tabPanels.tsx b/static/app/components/core/tabs/tabPanels.tsx index 862cc6273ff3..9299c47c7602 100644 --- a/static/app/components/core/tabs/tabPanels.tsx +++ b/static/app/components/core/tabs/tabPanels.tsx @@ -49,6 +49,7 @@ export function TabPanels(props: TabPanelsProps) { orientation={orientation} key={tabListState?.selectedKey} > + {/* eslint-disable-next-line @typescript-eslint/no-unsafe-member-access */} {selectedPanel?.props.children} ); diff --git a/static/app/components/modals/widgetBuilder/addToDashboardModal.tsx b/static/app/components/modals/widgetBuilder/addToDashboardModal.tsx index 25aea3bfb605..ef8fb192d73b 100644 --- a/static/app/components/modals/widgetBuilder/addToDashboardModal.tsx +++ b/static/app/components/modals/widgetBuilder/addToDashboardModal.tsx @@ -384,24 +384,27 @@ function AddToDashboardModal({ disabled: hasReachedDashboardLimit || isLoading, tooltip: hasReachedDashboardLimit ? limitMessage : undefined, tooltipOptions: {position: 'right', isHoverable: true}, - }, + } satisfies SelectValue, ...dashboards .filter(dashboard => // if adding from a dashboard, currentDashboardId will be set and we'll remove it from the list of options currentDashboardId ? dashboard.id !== currentDashboardId : true ) - .map(({title, id, widgetDisplay}) => ({ - label: title, - value: id, - disabled: widgetDisplay.length + widgets.length >= MAX_WIDGETS, - tooltip: - widgetDisplay.length + widgets.length >= MAX_WIDGETS && - tct('Max widgets ([maxWidgets]) per dashboard reached.', { - maxWidgets: MAX_WIDGETS, - }), - tooltipOptions: {position: 'right'}, - })), - ].filter(Boolean) as Array>; + .map( + ({title, id, widgetDisplay}) => + ({ + label: title, + value: id, + disabled: widgetDisplay.length + widgets.length >= MAX_WIDGETS, + tooltip: + widgetDisplay.length + widgets.length >= MAX_WIDGETS && + tct('Max widgets ([maxWidgets]) per dashboard reached.', { + maxWidgets: MAX_WIDGETS, + }), + tooltipOptions: {position: 'right'}, + }) satisfies SelectValue + ), + ].filter(Boolean); }, [currentDashboardId, dashboards, widgets.length] ); diff --git a/static/app/components/modals/widgetBuilder/linkToDashboardModal.tsx b/static/app/components/modals/widgetBuilder/linkToDashboardModal.tsx index a079286c0a63..dbdceeb06c62 100644 --- a/static/app/components/modals/widgetBuilder/linkToDashboardModal.tsx +++ b/static/app/components/modals/widgetBuilder/linkToDashboardModal.tsx @@ -149,24 +149,27 @@ export function LinkToDashboardModal({ disabled: hasReachedDashboardLimit || isLoading, tooltip: hasReachedDashboardLimit ? limitMessage : undefined, tooltipOptions: {position: 'right', isHoverable: true}, - }, + } satisfies SelectValue, ...dashboards .filter(dashboard => // if adding from a dashboard, currentDashboardId will be set and we'll remove it from the list of options currentDashboardId ? dashboard.id !== currentDashboardId : true ) - .map(({title, id, widgetDisplay}) => ({ - label: title, - value: id, - disabled: widgetDisplay.length >= MAX_WIDGETS, - tooltip: - widgetDisplay.length >= MAX_WIDGETS && - tct('Max widgets ([maxWidgets]) per dashboard reached.', { - maxWidgets: MAX_WIDGETS, - }), - tooltipOptions: {position: 'right'}, - })), - ].filter(Boolean) as Array>; + .map( + ({title, id, widgetDisplay}) => + ({ + label: title, + value: id, + disabled: widgetDisplay.length >= MAX_WIDGETS, + tooltip: + widgetDisplay.length >= MAX_WIDGETS && + tct('Max widgets ([maxWidgets]) per dashboard reached.', { + maxWidgets: MAX_WIDGETS, + }), + tooltipOptions: {position: 'right'}, + }) satisfies SelectValue + ), + ].filter(Boolean); }, [currentDashboardId, dashboards] ); diff --git a/static/app/utils/useOwnerOptions.tsx b/static/app/utils/useOwnerOptions.tsx index fed242ffbc15..81f30b46614f 100644 --- a/static/app/utils/useOwnerOptions.tsx +++ b/static/app/utils/useOwnerOptions.tsx @@ -4,6 +4,7 @@ import type {AvatarProps} from '@sentry/scraps/avatar'; import {TeamAvatar, UserAvatar} from '@sentry/scraps/avatar'; import {t} from 'sentry/locale'; +import type {SelectValue} from 'sentry/types/core'; import type {DetailedTeam, Team} from 'sentry/types/organization'; import type {User} from 'sentry/types/user'; @@ -40,11 +41,14 @@ export function useOwnerOptions({ // frustratingly that is difficult likely because we're recreating this // object on every re-render. const memberOptions = - members?.map(member => ({ - value: `user:${member.id}`, - label: member.name, - leadingItems: , - })) ?? []; + members?.map( + member => + ({ + value: `user:${member.id}`, + label: member.name, + leadingItems: , + }) satisfies SelectValue + ) ?? []; const makeTeamOption = (team: Team) => ({ value: `team:${team.id}`, @@ -52,12 +56,13 @@ export function useOwnerOptions({ leadingItems: , }); - const makeDisabledTeamOption = (team: Team) => ({ - ...makeTeamOption(team), - disabled: true, - tooltip: t('%s is not a member of the selected projects', `#${team.slug}`), - tooltipOptions: {position: 'left'}, - }); + const makeDisabledTeamOption = (team: Team) => + ({ + ...makeTeamOption(team), + disabled: true, + tooltip: t('%s is not a member of the selected projects', `#${team.slug}`), + tooltipOptions: {position: 'left'}, + }) satisfies SelectValue; const {disabledTeams, memberTeams, otherTeams} = groupBy( teams as DetailedTeam[], diff --git a/static/app/views/dashboards/widgetBuilder/components/visualize/selectRow.tsx b/static/app/views/dashboards/widgetBuilder/components/visualize/selectRow.tsx index d058c939142b..f0ebc7b01365 100644 --- a/static/app/views/dashboards/widgetBuilder/components/visualize/selectRow.tsx +++ b/static/app/views/dashboards/widgetBuilder/components/visualize/selectRow.tsx @@ -67,7 +67,7 @@ interface SelectRowProps { } function validateParameter( - columnOptions: Array>, + columnOptions: Array<{value: string}>, parameter: AggregateParameter, value: string | undefined ) { From b7bc201240f87f5654911894267bc8a717cdf92f Mon Sep 17 00:00:00 2001 From: TkDodo Date: Thu, 21 May 2026 19:40:20 +0200 Subject: [PATCH 5/5] streamline types --- static/app/components/core/layout/surface.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/app/components/core/layout/surface.tsx b/static/app/components/core/layout/surface.tsx index 50250b34c27d..2f0f0929221c 100644 --- a/static/app/components/core/layout/surface.tsx +++ b/static/app/components/core/layout/surface.tsx @@ -27,13 +27,13 @@ interface OverlaySurfaceProps extends Omit< } interface FlatSurfacePropsWithRenderFunction { - children: (props: {className: string | undefined}) => React.ReactNode; + children: (props: {className: string}) => React.ReactNode; elevation?: never; variant?: SurfaceVariant; } interface OverlaySurfacePropsWithRenderFunction { - children: (props: {className: string | undefined}) => React.ReactNode; + children: (props: {className: string}) => React.ReactNode; variant: 'overlay'; elevation?: 'low' | 'medium' | 'high'; }