Skip to content

Commit 383ea88

Browse files
authored
feat(onboarding): Design adjustments for copy markdown button (#108696)
## Summary - Shortens button text from "Copy setup instructions" to "Copy instructions" across all onboarding surfaces - Makes the button borderless (`priority="transparent"`) on sidebar, cron, releases, and MCP surfaces per updated Figma designs - Repositions the button to be inline with headings/controls instead of standalone above content: - **Sidebar surfaces** (feedback, replay, performance, profiling, feature flags): moved into first `Step` heading row via `trailingItems` - **Full-page GuidedSteps** (issues, metrics, logs, traces, agents, performance, profiling): moved into first `GuidedSteps.Step` heading via `trailingItems` - **MCP**: inline with first `GuidedSteps.Step` heading via `trailingItems` prop on `StepRenderer` - **Releases**: right-aligned on same row as "Select Integration" dropdown - **Crons**: inline with guide selector/tabs - Adds `trailingItems` prop to `GuidedSteps.Step`, rendering content in the heading row outside `<StepButton>` to avoid nesting interactive elements Closes ONB-2 ### Before / After #### Issues onboarding (full-page GuidedSteps pattern — 7 surfaces) Button moves from standalone bordered button above steps → borderless, inline with "1 Install" step heading. | Before | After | |--------|-------| | ![before-issues](https://files.catbox.moe/tad6az.png) | ![after-issues](https://files.catbox.moe/su1jec.png) | #### Performance sidebar (drawer pattern — 5 surfaces) Same repositioning in sidebar/drawer context — button moves into step heading row. | Before | After | |--------|-------| | ![before-sidebar](https://files.catbox.moe/2tuzwo.png) | ![after-sidebar](https://files.catbox.moe/be391a.png) | #### Releases quickstart Bordered standalone button above dropdown → borderless, right-aligned on same row as "Select Integration". | Before | After | |--------|-------| | ![before-releases](https://files.catbox.moe/trnvyk.png) | ![after-releases](https://files.catbox.moe/z76x72.png) | ## Test plan - [x] TypeScript compiles clean - [x] ESLint/Prettier pass - [x] GuidedSteps unit tests pass (4/4) - [x] stepsToMarkdown unit tests pass (55/55) - [x] instrumentationGuide unit tests pass (11/11) - [x] Playwright copy-markdown functional tests pass (50/50) - [x] Playwright tab-persistence tests pass (15/15) - [x] Visual verification against Figma designs on all 17 surfaces
1 parent 3b43a2c commit 383ea88

File tree

18 files changed

+428
-275
lines changed

18 files changed

+428
-275
lines changed

static/app/components/events/featureFlags/onboarding/featureFlagOnboardingLayout.tsx

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@ import {useMemo} from 'react';
22
import styled from '@emotion/styled';
33

44
import {LinkButton} from '@sentry/scraps/button';
5-
import {Container} from '@sentry/scraps/layout';
65

76
import OnboardingAdditionalFeatures from 'sentry/components/events/featureFlags/onboarding/onboardingAdditionalFeatures';
87
import {AuthTokenGeneratorProvider} from 'sentry/components/onboarding/gettingStartedDoc/authTokenGenerator';
98
import {
10-
CopySetupInstructionsGate,
119
OnboardingCopyMarkdownButton,
10+
useCopySetupInstructionsEnabled,
1211
} from 'sentry/components/onboarding/gettingStartedDoc/onboardingCopyMarkdownButton';
1312
import type {OnboardingLayoutProps} from 'sentry/components/onboarding/gettingStartedDoc/onboardingLayout';
1413
import {TabSelectionScope} from 'sentry/components/onboarding/gettingStartedDoc/selectedCodeTabContext';
@@ -42,6 +41,7 @@ export function FeatureFlagOnboardingLayout({
4241
useSourcePackageRegistries(organization);
4342
const selectedOptions = useUrlPlatformOptions(docsConfig.platformOptions);
4443
const {isSelfHosted, urlPrefix} = useLegacyStore(ConfigStore);
44+
const copyEnabled = useCopySetupInstructionsEnabled();
4545

4646
const {steps} = useMemo(() => {
4747
const doc = docsConfig[configType] ?? docsConfig.onboarding;
@@ -95,17 +95,22 @@ export function FeatureFlagOnboardingLayout({
9595
<AuthTokenGeneratorProvider projectSlug={project.slug}>
9696
<TabSelectionScope>
9797
<Wrapper>
98-
<CopySetupInstructionsGate>
99-
<Container paddingBottom="md">
100-
<OnboardingCopyMarkdownButton
101-
steps={steps}
102-
source="feature_flag_onboarding"
103-
/>
104-
</Container>
105-
</CopySetupInstructionsGate>
10698
<Steps>
10799
{steps.map((step, index) => (
108-
<Step key={step.title ?? step.type} stepIndex={index} {...step} />
100+
<Step
101+
key={step.title ?? step.type}
102+
stepIndex={index}
103+
{...step}
104+
trailingItems={
105+
index === 0 && copyEnabled ? (
106+
<OnboardingCopyMarkdownButton
107+
borderless
108+
steps={steps}
109+
source="feature_flag_onboarding"
110+
/>
111+
) : undefined
112+
}
113+
/>
109114
))}
110115
<StyledLinkButton to="/issues/" priority="primary">
111116
{t('Take me to Issues')}

static/app/components/feedback/feedbackOnboarding/feedbackOnboardingLayout.tsx

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import {useMemo, useState} from 'react';
22
import styled from '@emotion/styled';
33

4-
import {Container, Stack} from '@sentry/scraps/layout';
4+
import {Stack} from '@sentry/scraps/layout';
55

66
import FeedbackConfigToggle from 'sentry/components/feedback/feedbackOnboarding/feedbackConfigToggle';
77
import {AuthTokenGeneratorProvider} from 'sentry/components/onboarding/gettingStartedDoc/authTokenGenerator';
88
import {
9-
CopySetupInstructionsGate,
109
OnboardingCopyMarkdownButton,
10+
useCopySetupInstructionsEnabled,
1111
} from 'sentry/components/onboarding/gettingStartedDoc/onboardingCopyMarkdownButton';
1212
import type {OnboardingLayoutProps} from 'sentry/components/onboarding/gettingStartedDoc/onboardingLayout';
1313
import {TabSelectionScope} from 'sentry/components/onboarding/gettingStartedDoc/selectedCodeTabContext';
@@ -41,6 +41,7 @@ export function FeedbackOnboardingLayout({
4141
useSourcePackageRegistries(organization);
4242
const selectedOptions = useUrlPlatformOptions(docsConfig.platformOptions);
4343
const {isSelfHosted, urlPrefix} = useLegacyStore(ConfigStore);
44+
const copyEnabled = useCopySetupInstructionsEnabled();
4445
const {introduction, steps} = useMemo(() => {
4546
const doc = docsConfig[configType] ?? docsConfig.onboarding;
4647

@@ -153,17 +154,22 @@ export function FeedbackOnboardingLayout({
153154
<TabSelectionScope>
154155
<Wrapper>
155156
{introduction && <Stack marginBottom="3xl">{introduction}</Stack>}
156-
<CopySetupInstructionsGate>
157-
<Container paddingBottom="md">
158-
<OnboardingCopyMarkdownButton
159-
steps={transformedSteps}
160-
source="feedback_onboarding"
161-
/>
162-
</Container>
163-
</CopySetupInstructionsGate>
164157
<Steps>
165158
{transformedSteps.map((step, index) => (
166-
<Step key={step.title ?? step.type} stepIndex={index} {...step} />
159+
<Step
160+
key={step.title ?? step.type}
161+
stepIndex={index}
162+
{...step}
163+
trailingItems={
164+
index === 0 && copyEnabled ? (
165+
<OnboardingCopyMarkdownButton
166+
borderless
167+
steps={transformedSteps}
168+
source="feedback_onboarding"
169+
/>
170+
) : undefined
171+
}
172+
/>
167173
))}
168174
</Steps>
169175
</Wrapper>

static/app/components/guidedSteps/guidedSteps.tsx

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ interface StepProps {
4343
isCompleted?: boolean;
4444
onClick?: () => void;
4545
optional?: boolean;
46+
trailingItems?: React.ReactNode;
4647
}
4748

4849
type RegisterStepInfo = Pick<StepProps, 'stepKey' | 'isCompleted'>;
@@ -158,18 +159,42 @@ function Step(props: StepProps) {
158159
}
159160
}, [advanceToNextIncompleteStep, isActive, isCompleted, previousIsCompleted]);
160161

162+
const headingContent = (
163+
<StepButton
164+
hasTrailingItems={!!props.trailingItems}
165+
disabled={!props.onClick}
166+
onClick={props.onClick}
167+
>
168+
<Flex align="center" gap="lg">
169+
<StepNumber isActive={isActive}>{stepNumber}</StepNumber>
170+
<StepHeading isActive={isActive}>
171+
{props.title}
172+
{isCompleted && <StepDoneIcon isActive={isActive} size="sm" />}
173+
</StepHeading>
174+
{props.onClick ? <InteractionStateLayer /> : null}
175+
</Flex>
176+
</StepButton>
177+
);
178+
161179
return (
162180
<StepWrapper data-test-id={`guided-step-${stepNumber}`}>
163-
<StepButton area="heading" disabled={!props.onClick} onClick={props.onClick}>
164-
<Flex align="center" gap="lg">
165-
<StepNumber isActive={isActive}>{stepNumber}</StepNumber>
166-
<StepHeading isActive={isActive}>
167-
{props.title}
168-
{isCompleted && <StepDoneIcon isActive={isActive} size="sm" />}
169-
</StepHeading>
170-
{props.onClick ? <InteractionStateLayer /> : null}
181+
{props.trailingItems ? (
182+
<Flex
183+
direction={{xs: 'column', md: 'row'}}
184+
align={{xs: 'start', md: 'center'}}
185+
paddingLeft={{xs: 'lg', md: '0'}}
186+
justify="between"
187+
gap="sm"
188+
area="heading"
189+
>
190+
{headingContent}
191+
<Flex align="center" onClick={e => e.stopPropagation()}>
192+
{props.trailingItems}
193+
</Flex>
171194
</Flex>
172-
</StepButton>
195+
) : (
196+
headingContent
197+
)}
173198

174199
<StepDetails>
175200
{props.optional ? <StepOptionalLabel>Optional</StepOptionalLabel> : null}
@@ -262,8 +287,11 @@ const StepWrapper = styled('div')`
262287
}
263288
`;
264289

265-
const StepButton = styled('button')<{area: string}>`
266-
grid-area: ${p => p.area};
290+
const StepButton = styled('button')<{hasTrailingItems: boolean}>`
291+
${p =>
292+
p.hasTrailingItems
293+
? `flex: 1; min-width: 0; text-align: left;`
294+
: `grid-area: heading;`}
267295
268296
position: relative;
269297
background: none;

static/app/components/onboarding/gettingStartedDoc/onboardingCopyMarkdownButton.tsx

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import useOrganization from 'sentry/utils/useOrganization';
1313
interface CopyMarkdownButtonProps {
1414
getMarkdown: () => string;
1515
source: string;
16+
borderless?: boolean;
1617
}
1718

1819
/**
@@ -24,22 +25,28 @@ interface CopyMarkdownButtonProps {
2425
* surfaces that need tab-selection and auth-token context, use
2526
* `OnboardingCopyMarkdownButton` instead.
2627
*/
27-
export function CopyMarkdownButton({getMarkdown, source}: CopyMarkdownButtonProps) {
28+
export function CopyMarkdownButton({
29+
getMarkdown,
30+
source,
31+
borderless,
32+
}: CopyMarkdownButtonProps) {
2833
return (
2934
<Tooltip
3035
title={t(
3136
'Copies all steps and code examples as Markdown, optimized for use with an LLM.'
3237
)}
33-
position="right"
38+
position="auto"
3439
>
3540
<Button
41+
priority={borderless ? 'transparent' : undefined}
3642
icon={<IconCopy />}
3743
analyticsEventKey="setup_guide.copy_as_markdown"
3844
analyticsEventName="Setup Guide: Copy as Markdown"
3945
analyticsParams={{format: 'markdown', source}}
4046
onClick={() => copyToClipboard(getMarkdown())}
47+
size="xs"
4148
>
42-
{t('Copy setup instructions')}
49+
{t('Copy instructions')}
4350
</Button>
4451
</Tooltip>
4552
);
@@ -48,6 +55,7 @@ export function CopyMarkdownButton({getMarkdown, source}: CopyMarkdownButtonProp
4855
interface OnboardingCopyMarkdownButtonProps {
4956
source: string;
5057
steps: OnboardingStep[];
58+
borderless?: boolean;
5159
}
5260

5361
/**
@@ -57,6 +65,7 @@ interface OnboardingCopyMarkdownButtonProps {
5765
export function OnboardingCopyMarkdownButton({
5866
steps,
5967
source,
68+
borderless,
6069
}: OnboardingCopyMarkdownButtonProps) {
6170
const authToken = useAuthToken();
6271
const tabSelectionsMap = useTabSelectionsMap();
@@ -72,7 +81,13 @@ export function OnboardingCopyMarkdownButton({
7281
}
7382
};
7483

75-
return <CopyMarkdownButton getMarkdown={getMarkdown} source={source} />;
84+
return (
85+
<CopyMarkdownButton
86+
getMarkdown={getMarkdown}
87+
source={source}
88+
borderless={borderless}
89+
/>
90+
);
7691
}
7792

7893
const FEATURE_FLAG = 'onboarding-copy-setup-instructions';
@@ -82,9 +97,14 @@ const FEATURE_FLAG = 'onboarding-copy-setup-instructions';
8297
* `onboarding-copy-setup-instructions` flag is enabled. Includes spacing
8398
* so callsites don't render an empty Container when the flag is off.
8499
*/
85-
export function CopySetupInstructionsGate({children}: {children: React.ReactNode}) {
100+
export function useCopySetupInstructionsEnabled(): boolean {
86101
const organization = useOrganization();
87-
if (!organization.features.includes(FEATURE_FLAG)) {
102+
return organization.features.includes(FEATURE_FLAG);
103+
}
104+
105+
export function CopySetupInstructionsGate({children}: {children: React.ReactNode}) {
106+
const enabled = useCopySetupInstructionsEnabled();
107+
if (!enabled) {
88108
return null;
89109
}
90110
return children;

static/app/components/performanceOnboarding/sidebar.tsx

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,15 @@ import HighlightTopRightPattern from 'sentry-images/pattern/highlight-top-right.
55

66
import {Alert} from '@sentry/scraps/alert';
77
import {LinkButton} from '@sentry/scraps/button';
8-
import {Container} from '@sentry/scraps/layout';
98

109
import type {MenuItemProps} from 'sentry/components/dropdownMenu';
1110
import {DropdownMenu} from 'sentry/components/dropdownMenu';
1211
import useDrawer from 'sentry/components/globalDrawer';
1312
import IdBadge from 'sentry/components/idBadge';
1413
import LoadingIndicator from 'sentry/components/loadingIndicator';
1514
import {
16-
CopySetupInstructionsGate,
1715
OnboardingCopyMarkdownButton,
16+
useCopySetupInstructionsEnabled,
1817
} from 'sentry/components/onboarding/gettingStartedDoc/onboardingCopyMarkdownButton';
1918
import {TabSelectionScope} from 'sentry/components/onboarding/gettingStartedDoc/selectedCodeTabContext';
2019
import {Step} from 'sentry/components/onboarding/gettingStartedDoc/step';
@@ -234,6 +233,7 @@ function OnboardingContent({currentProject}: {currentProject: Project}) {
234233
const api = useApi();
235234
const organization = useOrganization();
236235
const {isSelfHosted, urlPrefix} = useLegacyStore(ConfigStore);
236+
const copyEnabled = useCopySetupInstructionsEnabled();
237237
const [received, setReceived] = useState<boolean>(false);
238238

239239
const previousProject = usePrevious(currentProject);
@@ -343,17 +343,24 @@ function OnboardingContent({currentProject}: {currentProject: Project}) {
343343
{performanceDocs.introduction && (
344344
<Introduction>{performanceDocs.introduction(docParams)}</Introduction>
345345
)}
346-
<CopySetupInstructionsGate>
347-
<Container paddingBottom="md">
348-
<OnboardingCopyMarkdownButton
349-
steps={steps}
350-
source="performance_sidebar_onboarding"
351-
/>
352-
</Container>
353-
</CopySetupInstructionsGate>
354346
<Steps>
355347
{steps.map((step, index) => {
356-
return <Step key={step.title ?? step.type} stepIndex={index} {...step} />;
348+
return (
349+
<Step
350+
key={step.title ?? step.type}
351+
stepIndex={index}
352+
{...step}
353+
trailingItems={
354+
index === 0 && copyEnabled ? (
355+
<OnboardingCopyMarkdownButton
356+
borderless
357+
steps={steps}
358+
source="performance_sidebar_onboarding"
359+
/>
360+
) : undefined
361+
}
362+
/>
363+
);
357364
})}
358365
</Steps>
359366
<EventWaiter

static/app/components/profiling/profilingOnboardingSidebar.tsx

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import styled from '@emotion/styled';
33
import partition from 'lodash/partition';
44

55
import {CompactSelect} from '@sentry/scraps/compactSelect';
6-
import {Container, Stack} from '@sentry/scraps/layout';
6+
import {Stack} from '@sentry/scraps/layout';
77
import {OverlayTrigger} from '@sentry/scraps/overlayTrigger';
88

99
import useDrawer from 'sentry/components/globalDrawer';
@@ -12,8 +12,8 @@ import LoadingError from 'sentry/components/loadingError';
1212
import LoadingIndicator from 'sentry/components/loadingIndicator';
1313
import {DeprecatedPlatformInfo} from 'sentry/components/onboarding/gettingStartedDoc/deprecatedPlatformInfo';
1414
import {
15-
CopySetupInstructionsGate,
1615
OnboardingCopyMarkdownButton,
16+
useCopySetupInstructionsEnabled,
1717
} from 'sentry/components/onboarding/gettingStartedDoc/onboardingCopyMarkdownButton';
1818
import {TabSelectionScope} from 'sentry/components/onboarding/gettingStartedDoc/selectedCodeTabContext';
1919
import {Step} from 'sentry/components/onboarding/gettingStartedDoc/step';
@@ -276,6 +276,7 @@ function ProfilingOnboardingContent(props: ProfilingOnboardingContentProps) {
276276
useSourcePackageRegistries(organization);
277277

278278
const {isSelfHosted, urlPrefix} = useLegacyStore(ConfigStore);
279+
const copyEnabled = useCopySetupInstructionsEnabled();
279280

280281
if (isLoading) {
281282
return <LoadingIndicator />;
@@ -367,17 +368,24 @@ function ProfilingOnboardingContent(props: ProfilingOnboardingContentProps) {
367368
<TabSelectionScope>
368369
<Wrapper>
369370
{doc.introduction && <Introduction>{doc.introduction(docParams)}</Introduction>}
370-
<CopySetupInstructionsGate>
371-
<Container paddingBottom="md">
372-
<OnboardingCopyMarkdownButton
373-
steps={steps}
374-
source="profiling_sidebar_onboarding"
375-
/>
376-
</Container>
377-
</CopySetupInstructionsGate>
378371
<Steps>
379372
{steps.map((step, index) => {
380-
return <Step key={step.title ?? step.type} stepIndex={index} {...step} />;
373+
return (
374+
<Step
375+
key={step.title ?? step.type}
376+
stepIndex={index}
377+
{...step}
378+
trailingItems={
379+
index === 0 && copyEnabled ? (
380+
<OnboardingCopyMarkdownButton
381+
borderless
382+
steps={steps}
383+
source="profiling_sidebar_onboarding"
384+
/>
385+
) : undefined
386+
}
387+
/>
388+
);
381389
})}
382390
</Steps>
383391
</Wrapper>

0 commit comments

Comments
 (0)