Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
5d976e7
docs: Add TopBar actions slot migration plan
JonasBa Apr 8, 2026
3a3bc2d
ref(nav): Migrate Layout.HeaderActions to TopBar.Slot name=actions
JonasBa Apr 8, 2026
8b4e818
ref(nav): Remove size props from buttons inside TopBar actions slot
JonasBa Apr 8, 2026
c645187
ref(nav): Migrate IssueViewsHeader actions to TopBar actions slot
JonasBa Apr 8, 2026
a1c425d
ref(issueViews): Remove size prop from realtime toggle button in non-…
JonasBa Apr 8, 2026
667536e
fix(issueViews): Add size sm to realtime toggle button in pageframe b…
JonasBa Apr 8, 2026
e42b312
docs: Remove TopBar actions slot migration plan
JonasBa Apr 8, 2026
045dc29
fix(discover): Convert ResultsHeader to named export
JonasBa Apr 8, 2026
d2392c7
fix(nav): restore size="sm" prop on realtime button in non-pageframe …
cursoragent Apr 9, 2026
56298eb
Merge master into jb/ref/topbar-actions
cursoragent Apr 9, 2026
45cb40d
Merge master into jb/ref/topbar-actions
JonasBa Apr 9, 2026
e29f050
fix(nav): Add size="sm" to buttons inside TopBar.Slot actions
JonasBa Apr 9, 2026
9df0880
fix(nav): Remove duplicate hasPageFrameFeature declarations
JonasBa Apr 9, 2026
5ebb006
Merge branch 'master' into jb/ref/topbar-actions
JonasBa Apr 9, 2026
8eb4b4a
Merge branch 'master' into jb/ref/topbar-actions
JonasBa Apr 9, 2026
59ad5cd
ref(slots): add providers option (#112596)
natemoo-re Apr 9, 2026
2ee0193
ref(nav): fix contextual SetupLogsButton sizing
natemoo-re Apr 9, 2026
5351eae
ref(nav): remove errant prop
natemoo-re Apr 9, 2026
85ac09d
Merge branch 'master' into jb/ref/topbar-actions
JonasBa Apr 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions static/app/components/core/slot/slot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,12 @@ type SlotModule<T extends Slot> = React.FunctionComponent<SlotConsumerProps<T>>
Provider: React.ComponentType<SlotProviderProps>;
};

function makeSlotConsumer<T extends Slot>(
context: React.Context<SlotContextValue<T> | null>
) {
function makeSlotConsumer<T extends Slot>(options: {
context: React.Context<SlotContextValue<T> | null>;
providers?: React.ComponentType<{children: React.ReactNode}>;
}) {
const {context, providers: Providers} = options;

function SlotConsumer(props: SlotConsumerProps<T>): React.ReactNode {
const ctx = useContext(context);
if (!ctx) {
Expand All @@ -138,11 +141,13 @@ function makeSlotConsumer<T extends Slot>(
}, [dispatch, name]);

const element = state[name]?.element;
const content = Providers ? <Providers>{props.children}</Providers> : props.children;

if (!element) {
// Render in place as a fallback when no target element is registered yet
return props.children;
return content;
}
return createPortal(props.children, element);
return createPortal(content, element);
}

SlotConsumer.displayName = 'Slot.Consumer';
Expand Down Expand Up @@ -230,13 +235,19 @@ function makeSlotProvider<T extends Slot>(
return SlotProvider as (props: SlotProviderProps) => React.ReactNode;
}

export function slot<T extends readonly Slot[]>(names: T): SlotModule<T[number]> {
export function slot<T extends readonly Slot[]>(
names: T,
options?: {providers?: React.ComponentType<{children: React.ReactNode}>}
): SlotModule<T[number]> {
type SlotName = T[number];

const SlotContext = createContext<SlotContextValue<SlotName> | null>(null);
const OutletNameContext = createContext<SlotName | null>(null);

const Slot = makeSlotConsumer<SlotName>(SlotContext) as SlotModule<SlotName>;
const Slot = makeSlotConsumer<SlotName>({
context: SlotContext,
providers: options?.providers,
}) as SlotModule<SlotName>;
Slot.Provider = makeSlotProvider<SlotName>(SlotContext);
Slot.Outlet = makeSlotOutlet<SlotName>(SlotContext, OutletNameContext);
Slot.Fallback = makeSlotFallback<SlotName>(SlotContext, OutletNameContext);
Expand Down
31 changes: 20 additions & 11 deletions static/app/components/profiling/continuousProfileHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {useCallback, useMemo} from 'react';
import {Fragment, useCallback, useMemo} from 'react';
import styled from '@emotion/styled';

import {LinkButton} from '@sentry/scraps/button';
Expand Down Expand Up @@ -53,20 +53,29 @@ export function ContinuousProfileHeader({transaction}: ContinuousProfileHeader)
<ProfilingBreadcrumbs organization={organization} trails={breadCrumbs} />
</SmallerProfilingBreadcrumbsWrapper>
</SmallerHeaderContent>
<StyledHeaderActions>
{hasPageFrameFeature ? (
{hasPageFrameFeature ? (
<Fragment>
{transactionTarget && (
<TopBar.Slot name="actions">
<LinkButton onClick={handleGoToTransaction} to={transactionTarget}>
{t('Go to Trace')}
</LinkButton>
</TopBar.Slot>
)}
<TopBar.Slot name="feedback">
<FeedbackButton>{null}</FeedbackButton>
</TopBar.Slot>
) : (
</Fragment>
) : (
<StyledHeaderActions>
<FeedbackButton />
)}
{transactionTarget && (
<LinkButton size="sm" onClick={handleGoToTransaction} to={transactionTarget}>
{t('Go to Trace')}
</LinkButton>
)}
</StyledHeaderActions>
{transactionTarget && (
<LinkButton size="sm" onClick={handleGoToTransaction} to={transactionTarget}>
{t('Go to Trace')}
</LinkButton>
)}
</StyledHeaderActions>
)}
</SmallerLayoutHeader>
);
}
Expand Down
31 changes: 20 additions & 11 deletions static/app/components/profiling/profileHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {useCallback, useMemo} from 'react';
import {Fragment, useCallback, useMemo} from 'react';
import styled from '@emotion/styled';

import {LinkButton} from '@sentry/scraps/button';
Expand Down Expand Up @@ -91,20 +91,29 @@ function ProfileHeader({transaction, projectId, eventId}: ProfileHeaderProps) {
<ProfilingBreadcrumbs organization={organization} trails={breadcrumbTrails} />
</SmallerProfilingBreadcrumbsWrapper>
</SmallerHeaderContent>
<StyledHeaderActions>
{hasPageFrameFeature ? (
{hasPageFrameFeature ? (
<Fragment>
{transactionTarget && (
<TopBar.Slot name="actions">
<LinkButton onClick={handleGoToTransaction} to={transactionTarget}>
{t('Go to Trace')}
</LinkButton>
</TopBar.Slot>
)}
<TopBar.Slot name="feedback">
<FeedbackButton>{null}</FeedbackButton>
</TopBar.Slot>
) : (
</Fragment>
) : (
<StyledHeaderActions>
<FeedbackButton />
)}
{transactionTarget && (
<LinkButton size="sm" onClick={handleGoToTransaction} to={transactionTarget}>
{t('Go to Trace')}
</LinkButton>
)}
</StyledHeaderActions>
{transactionTarget && (
<LinkButton size="sm" onClick={handleGoToTransaction} to={transactionTarget}>
{t('Go to Trace')}
</LinkButton>
)}
</StyledHeaderActions>
)}
</SmallerLayoutHeader>
);
}
Expand Down
9 changes: 8 additions & 1 deletion static/app/components/workflowEngine/layout/list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import * as Layout from 'sentry/components/layouts/thirds';
import {NoProjectMessage} from 'sentry/components/noProjectMessage';
import {PageHeadingQuestionTooltip} from 'sentry/components/pageHeadingQuestionTooltip';
import {useOrganization} from 'sentry/utils/useOrganization';
import {TopBar} from 'sentry/views/navigation/topBar';
import {useHasPageFrameFeature} from 'sentry/views/navigation/useHasPageFrameFeature';

interface WorkflowEngineListLayoutProps {
actions: React.ReactNode;
Expand All @@ -26,6 +28,7 @@ export function WorkflowEngineListLayout({
docsUrl,
}: WorkflowEngineListLayoutProps) {
const organization = useOrganization();
const hasPageFrameFeature = useHasPageFrameFeature();

return (
<Stack flex={1}>
Expand All @@ -37,7 +40,11 @@ export function WorkflowEngineListLayout({
<PageHeadingQuestionTooltip docsUrl={docsUrl} title={description} />
</Layout.Title>
</Layout.HeaderContent>
<Layout.HeaderActions>{actions}</Layout.HeaderActions>
{hasPageFrameFeature ? (
<TopBar.Slot name="actions">{actions}</TopBar.Slot>
) : (
<Layout.HeaderActions>{actions}</Layout.HeaderActions>
)}
</Layout.Header>
<Layout.Body>
<Layout.Main width="full">
Expand Down
87 changes: 56 additions & 31 deletions static/app/views/alerts/list/header.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {Fragment} from 'react';

import {LinkButton} from '@sentry/scraps/button';
import {Grid} from '@sentry/scraps/layout';
import {TabList} from '@sentry/scraps/tabs';
Expand Down Expand Up @@ -66,38 +68,61 @@ export function AlertHeader({activeTab}: Props) {
/>
</Layout.Title>
</Layout.HeaderContent>
<Layout.HeaderActions>
<Grid flow="column" align="center" gap="md">
<CreateAlertButton
organization={organization}
iconProps={{size: 'sm'}}
size="sm"
priority="primary"
referrer="alert_stream"
projectSlug={
selection.projects.length === 1
? ProjectsStore.getById(`${selection.projects[0]}`)?.slug
: undefined
}
>
{t('Create Alert')}
</CreateAlertButton>
{hasPageFrameFeature ? (
<TopBar.Slot name="feedback">
<FeedbackButton>{null}</FeedbackButton>
</TopBar.Slot>
) : (
{hasPageFrameFeature ? (
<Fragment>
<TopBar.Slot name="actions">
<CreateAlertButton
organization={organization}
iconProps={{size: 'sm'}}
priority="primary"
referrer="alert_stream"
projectSlug={
selection.projects.length === 1
? ProjectsStore.getById(`${selection.projects[0]}`)?.slug
: undefined
}
>
{t('Create Alert')}
</CreateAlertButton>
<LinkButton
onClick={handleNavigateToSettings}
href="#"
icon={<IconSettings size="sm" />}
aria-label={t('Settings')}
/>
</TopBar.Slot>
<TopBar.Slot name="feedback">
<FeedbackButton>{null}</FeedbackButton>
</TopBar.Slot>
</Fragment>
) : (
<Layout.HeaderActions>
<Grid flow="column" align="center" gap="md">
<CreateAlertButton
organization={organization}
iconProps={{size: 'sm'}}
size="sm"
priority="primary"
referrer="alert_stream"
projectSlug={
selection.projects.length === 1
? ProjectsStore.getById(`${selection.projects[0]}`)?.slug
: undefined
}
>
{t('Create Alert')}
</CreateAlertButton>
<FeedbackButton />
)}
<LinkButton
size="sm"
onClick={handleNavigateToSettings}
href="#"
icon={<IconSettings size="sm" />}
aria-label={t('Settings')}
/>
</Grid>
</Layout.HeaderActions>
<LinkButton
size="sm"
onClick={handleNavigateToSettings}
href="#"
icon={<IconSettings size="sm" />}
aria-label={t('Settings')}
/>
</Grid>
</Layout.HeaderActions>
)}
<Layout.HeaderTabs value={activeTab}>
<TabList>
{alertRulesLink}
Expand Down
55 changes: 49 additions & 6 deletions static/app/views/alerts/rules/issue/details/ruleDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ import {APIUsageWarningBanner} from 'sentry/views/alerts/rules/APIUsageWarningBa
import {findIncompatibleRules} from 'sentry/views/alerts/rules/issue';
import {ALERT_DEFAULT_CHART_PERIOD} from 'sentry/views/alerts/rules/metric/details/constants';
import {UserSnoozeDeprecationBanner} from 'sentry/views/alerts/rules/userSnoozeDeprecationBanner';
import {TopBar} from 'sentry/views/navigation/topBar';
import {useHasPageFrameFeature} from 'sentry/views/navigation/useHasPageFrameFeature';

import {IssueAlertDetailsChart} from './alertChart';
import {AlertRuleIssuesList} from './issuesList';
Expand Down Expand Up @@ -78,6 +80,7 @@ const getIssueAlertDetailsQueryKey = ({
];

export default function AlertRuleDetails() {
const hasPageFrameFeature = useHasPageFrameFeature();
const queryClient = useQueryClient();
const organization = useOrganization();
const api = useApi();
Expand Down Expand Up @@ -422,8 +425,8 @@ export default function AlertRuleDetails() {
{rule.name}
</Layout.Title>
</Layout.HeaderContent>
<Layout.HeaderActions>
<Grid flow="column" align="center" gap="md">
{hasPageFrameFeature ? (
<TopBar.Slot name="actions">
<Access access={['alerts:write']}>
{({hasAccess}) => (
<SnoozeAlert
Expand All @@ -438,15 +441,13 @@ export default function AlertRuleDetails() {
)}
</Access>
<LinkButton
size="sm"
icon={<IconCopy />}
to={duplicateLink}
disabled={rule.status === 'disabled'}
>
{t('Duplicate')}
</LinkButton>
<LinkButton
size="sm"
icon={<IconEdit />}
to={makeAlertsPathname({
path: `/rules/${projectSlug}/${ruleId}/`,
Expand All @@ -461,8 +462,50 @@ export default function AlertRuleDetails() {
>
{rule.status === 'disabled' ? t('Edit to enable') : t('Edit Rule')}
</LinkButton>
</Grid>
</Layout.HeaderActions>
</TopBar.Slot>
) : (
<Layout.HeaderActions>
<Grid flow="column" align="center" gap="md">
<Access access={['alerts:write']}>
{({hasAccess}) => (
<SnoozeAlert
isSnoozed={rule.snoozeForEveryone ?? false}
onSnooze={onSnooze}
ruleId={rule.id}
projectSlug={projectSlug}
hasAccess={hasAccess}
type="issue"
disabled={rule.status === 'disabled'}
/>
)}
</Access>
<LinkButton
size="sm"
icon={<IconCopy />}
to={duplicateLink}
disabled={rule.status === 'disabled'}
>
{t('Duplicate')}
</LinkButton>
<LinkButton
size="sm"
icon={<IconEdit />}
to={makeAlertsPathname({
path: `/rules/${projectSlug}/${ruleId}/`,
organization,
})}
onClick={() =>
trackAnalytics('issue_alert_rule_details.edit_clicked', {
organization,
rule_id: parseInt(ruleId, 10),
})
}
>
{rule.status === 'disabled' ? t('Edit to enable') : t('Edit Rule')}
</LinkButton>
</Grid>
</Layout.HeaderActions>
)}
</Layout.Header>
<Layout.Body>
<Layout.Main>
Expand Down
Loading
Loading