Skip to content

Commit b6c8cda

Browse files
committed
docs(react/getting-started): replace onboarding UI with StepConnector and add content Break before Customizing Replays
1 parent db9fe9a commit b6c8cda

File tree

4 files changed

+297
-28
lines changed

4 files changed

+297
-28
lines changed

docs/platforms/javascript/guides/react/getting-started.mdx

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,7 @@ description: "Learn how to set up and configure Sentry in your React application
88

99
<PlatformContent includePath="getting-started-prerequisites" />
1010

11-
## Features
12-
13-
Select which Sentry features you'd like to configure to get the corresponding setup instructions below.
14-
15-
<PlatformContent includePath="getting-started-features-expandable" />
16-
17-
<OnboardingOptionButtons
18-
options={[
19-
{id: "error-monitoring", checked: true, disabled: true},
20-
{id: "performance", checked: true},
21-
{id: "session-replay", checked: true},
22-
{id: "user-feedback", checked: false},
23-
{id: "logs", checked: true},
24-
]}
25-
/>
11+
<StepConnector>
2612

2713
## Step 1: Install
2814

@@ -40,8 +26,6 @@ npm install @sentry/react --save
4026

4127
### Initialize the Sentry SDK
4228

43-
<OnboardingOption optionId="error-monitoring">
44-
4529
Add the following to your application's entry point (typically `index.js`, `main.js`, or `App.js`) or to the file before you use any other Sentry method:
4630

4731
```javascript {filename:instrument.js}
@@ -97,8 +81,6 @@ Sentry.init({
9781
});
9882
```
9983

100-
</OnboardingOption>
101-
10284
### Import & Use the Instrument file
10385

10486
Import the `instrument.js` file in your application's entry point _before all other imports_ to initialize the SDK:
@@ -159,8 +141,6 @@ import * as Sentry from "@sentry/react";
159141
[here](features/error-boundary/#manually-capturing-errors).
160142
</Alert>
161143

162-
<OnboardingOption optionId="logs">
163-
164144
## Step 4: Sending Logs
165145

166146
[Structured logging](/platforms/javascript/guides/react/logs/) lets users send text-based log information from their applications to Sentry. Once in Sentry, these logs can be viewed alongside relevant errors, searched by text-string, or searched using their individual attributes.
@@ -189,9 +169,7 @@ logger.error("Payment processing failed", {
189169
logger.warn(logger.fmt`Rate limit exceeded for user: ${userId}`);
190170
```
191171

192-
</OnboardingOption>
193-
194-
<OnboardingOption optionId="session-replay">
172+
<Break />
195173

196174
## Step 5: Customizing Replays
197175

@@ -225,9 +203,7 @@ Sentry.init({
225203
<span data-safe-to-show>Safe user data: {username}</span>
226204
```
227205

228-
</OnboardingOption>
229-
230-
<OnboardingOption optionId="performance">
206+
231207

232208
## Step 6: Custom Traces with Attributes
233209

@@ -281,7 +257,7 @@ if (span) {
281257
}
282258
```
283259

284-
</OnboardingOption>
260+
285261

286262
## Step 7: Avoid Ad Blockers With Tunneling (Optional)
287263

@@ -330,6 +306,8 @@ Now's a good time to customize your setup and look into more advanced topics:
330306

331307
## Additional Resources
332308

309+
</StepConnector>
310+
333311
<Expandable title="Set Up React Router (Optional)">
334312

335313
If you're using `react-router` in your application, you need to set up the Sentry integration for your specific React Router version to trace `navigation` events.
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
"use client";
2+
3+
/**
4+
* Component: StepConnector / StepComponent
5+
*
6+
* Visually connects sequential headings with a vertical rail and circles.
7+
* Supports optional numbering and a user‑checkable “completed” state.
8+
*
9+
* Props overview
10+
* - startAt: number — first step number (default 1)
11+
* - selector: string — heading selector (default 'h2')
12+
* - showNumbers: boolean — show numbers inside circles (default true)
13+
* - checkable: boolean — allow toggling completion (default false)
14+
* - persistence: 'session' | 'none' — completion storage (default 'session')
15+
* - showReset: boolean — show a small “Reset steps” action (default true)
16+
*
17+
* Usage
18+
* <StepConnector checkable />
19+
* <StepConnector showNumbers={false} checkable persistence="none" />
20+
*
21+
* Accessibility
22+
* - Each circle becomes a button when `checkable` is enabled (keyboard + aria‑pressed).
23+
* - When `showNumbers` is false, a subtle dot is shown; numbering remains implicit
24+
* via the DOM order, and buttons include descriptive aria‑labels.
25+
*
26+
* Theming / CSS variables (in style.module.scss)
27+
* - --rail-x, --circle, --gap control rail position and circle size/spacing.
28+
*/
29+
30+
import {useEffect, useMemo, useRef, useState} from "react";
31+
import styles from "./style.module.scss";
32+
33+
type Persistence = "session" | "none";
34+
35+
type Props = {
36+
children: React.ReactNode;
37+
/** Start numbering from this value. @defaultValue 1 */
38+
startAt?: number;
39+
/** Which heading level to connect (CSS selector). @defaultValue 'h2' */
40+
selector?: string;
41+
/** Show numeric labels inside circles. Set false for blank circles. @defaultValue true */
42+
showNumbers?: boolean;
43+
/** Allow users to check off steps (circle becomes a button). @defaultValue false */
44+
checkable?: boolean;
45+
/** Completion storage: 'session' | 'none'. @defaultValue 'session' */
46+
persistence?: Persistence;
47+
/** Show a small "Reset steps" action when checkable. @defaultValue true */
48+
showReset?: boolean;
49+
};
50+
51+
export function StepComponent({
52+
children,
53+
startAt = 1,
54+
selector = "h2",
55+
showNumbers = true,
56+
checkable = false,
57+
persistence = "session",
58+
showReset = true,
59+
}: Props) {
60+
const containerRef = useRef<HTMLDivElement | null>(null);
61+
const [completed, setCompleted] = useState<Set<string>>(new Set());
62+
63+
const storageKey = useMemo(() => {
64+
if (typeof window === "undefined" || persistence !== "session") return null;
65+
try {
66+
const path = window.location?.pathname ?? "";
67+
return `stepConnector:${path}:${selector}:${startAt}`;
68+
} catch {
69+
return null;
70+
}
71+
}, [persistence, selector, startAt]);
72+
73+
useEffect(() => {
74+
const container = containerRef.current;
75+
if (!container) {
76+
// Return empty cleanup function for consistent return
77+
return () => {};
78+
}
79+
80+
const headings = Array.from(
81+
container.querySelectorAll<HTMLElement>(`:scope ${selector}`)
82+
);
83+
84+
headings.forEach(h => {
85+
h.classList.remove(styles.stepHeading);
86+
h.removeAttribute("data-step");
87+
h.removeAttribute("data-completed");
88+
const existingToggle = h.querySelector(`.${styles.stepToggle}`);
89+
if (existingToggle) existingToggle.remove();
90+
});
91+
92+
headings.forEach((h, idx) => {
93+
const stepNumber = startAt + idx;
94+
h.setAttribute("data-step", String(stepNumber));
95+
h.classList.add(styles.stepHeading);
96+
97+
if (checkable) {
98+
const btn = document.createElement("button");
99+
btn.type = "button";
100+
btn.className = styles.stepToggle;
101+
btn.setAttribute("aria-label", `Toggle completion for step ${stepNumber}`);
102+
btn.setAttribute("aria-pressed", completed.has(h.id) ? "true" : "false");
103+
btn.addEventListener("click", () => {
104+
setCompleted(prev => {
105+
const next = new Set(prev);
106+
if (next.has(h.id)) next.delete(h.id);
107+
else next.add(h.id);
108+
return next;
109+
});
110+
});
111+
h.insertBefore(btn, h.firstChild);
112+
}
113+
});
114+
115+
// Cleanup function
116+
return () => {
117+
headings.forEach(h => {
118+
h.classList.remove(styles.stepHeading);
119+
h.removeAttribute("data-step");
120+
h.removeAttribute("data-completed");
121+
const existingToggle = h.querySelector(`.${styles.stepToggle}`);
122+
if (existingToggle) existingToggle.remove();
123+
});
124+
};
125+
// eslint-disable-next-line react-hooks/exhaustive-deps
126+
}, [startAt, selector, checkable]);
127+
128+
useEffect(() => {
129+
if (!storageKey || !checkable) return;
130+
try {
131+
const raw = sessionStorage.getItem(storageKey);
132+
if (raw) setCompleted(new Set(JSON.parse(raw) as string[]));
133+
} catch {
134+
// Ignore storage errors
135+
}
136+
// eslint-disable-next-line react-hooks/exhaustive-deps
137+
}, [storageKey, checkable]);
138+
139+
useEffect(() => {
140+
const container = containerRef.current;
141+
if (!container) return;
142+
const headings = Array.from(
143+
container.querySelectorAll<HTMLElement>(`:scope ${selector}`)
144+
);
145+
headings.forEach(h => {
146+
const isDone = completed.has(h.id);
147+
if (isDone) h.setAttribute("data-completed", "true");
148+
else h.removeAttribute("data-completed");
149+
const btn = h.querySelector(`.${styles.stepToggle}`) as HTMLButtonElement | null;
150+
if (btn) btn.setAttribute("aria-pressed", isDone ? "true" : "false");
151+
});
152+
153+
if (storageKey && checkable) {
154+
try {
155+
sessionStorage.setItem(storageKey, JSON.stringify(Array.from(completed)));
156+
} catch {
157+
// Ignore storage errors
158+
}
159+
}
160+
}, [completed, selector, storageKey, checkable]);
161+
162+
const handleReset = () => {
163+
setCompleted(new Set());
164+
if (storageKey) {
165+
try {
166+
sessionStorage.removeItem(storageKey);
167+
} catch {
168+
// Ignore storage errors
169+
}
170+
}
171+
};
172+
173+
return (
174+
<div
175+
ref={containerRef}
176+
className={styles.stepContainer}
177+
data-shownumbers={showNumbers ? "true" : "false"}
178+
>
179+
{checkable && showReset && (
180+
<div className={styles.resetRow}>
181+
<button type="button" className={styles.resetBtn} onClick={handleReset}>
182+
Reset steps
183+
</button>
184+
</div>
185+
)}
186+
{children}
187+
</div>
188+
);
189+
}
190+
191+
// Alias to match usage <StepConnector>...</StepConnector>
192+
export function StepConnector(props: Props) {
193+
return <StepComponent {...props} />;
194+
}
195+
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
.stepContainer {
2+
position: relative;
3+
--rail-x: 18px;
4+
--circle: 36px;
5+
--gap: 8px;
6+
--pad-left: calc(var(--rail-x) + var(--circle) + var(--gap));
7+
padding-left: var(--pad-left);
8+
}
9+
10+
.stepContainer::before {
11+
content: '';
12+
position: absolute;
13+
left: var(--rail-x);
14+
top: 0;
15+
bottom: 0;
16+
width: 2px;
17+
background: var(--gray-a4);
18+
}
19+
20+
.stepHeading {
21+
position: relative;
22+
scroll-margin-top: var(--header-height, 80px);
23+
}
24+
25+
.stepHeading::before {
26+
content: attr(data-step);
27+
position: absolute;
28+
left: calc(var(--rail-x) - (var(--circle) / 2) - var(--pad-left));
29+
top: 0.05em;
30+
width: var(--circle);
31+
height: var(--circle);
32+
border-radius: 9999px;
33+
display: grid;
34+
place-items: center;
35+
font-size: 1.18rem;
36+
font-weight: 600;
37+
line-height: 1;
38+
z-index: 1;
39+
background: var(--gray-1);
40+
color: var(--gray-12);
41+
border: 1px solid var(--gray-a6);
42+
box-shadow: 0 1px 2px var(--gray-a3);
43+
}
44+
45+
.stepContainer[data-shownumbers='false'] .stepHeading::before { content: ''; }
46+
.stepContainer[data-shownumbers='false'] .stepHeading:not([data-completed='true'])::after {
47+
content: '';
48+
position: absolute;
49+
left: calc(var(--rail-x) - 3px - var(--pad-left));
50+
top: calc(0.05em + (var(--circle) / 2) - 3px);
51+
width: 6px;
52+
height: 6px;
53+
border-radius: 9999px;
54+
background: var(--gray-a8);
55+
z-index: 2;
56+
}
57+
58+
.stepHeading[data-completed='true']::before {
59+
content: '';
60+
background: var(--accent-11);
61+
color: white;
62+
border-color: var(--accent-11);
63+
}
64+
65+
.stepToggle {
66+
position: absolute;
67+
left: calc(var(--rail-x) - (var(--circle) / 2) - var(--pad-left));
68+
top: 0.05em;
69+
width: var(--circle);
70+
height: var(--circle);
71+
border: 0;
72+
padding: 0;
73+
background: transparent;
74+
cursor: pointer;
75+
z-index: 3;
76+
}
77+
.stepToggle:focus-visible {
78+
outline: 2px solid var(--accent);
79+
outline-offset: 2px;
80+
border-radius: 9999px;
81+
}
82+
83+
.resetRow { display: flex; justify-content: flex-end; margin-bottom: 0.5rem; }
84+
.resetBtn {
85+
font-size: 0.8rem;
86+
color: var(--gray-11);
87+
background: transparent;
88+
border: 1px solid var(--gray-a5);
89+
border-radius: 9999px;
90+
padding: 2px 8px;
91+
}
92+
.resetBtn:hover { background: var(--gray-a3); }
93+

src/mdxComponents.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import {SdkApi} from './components/sdkApi';
4343
import {SdkOption} from './components/sdkOption';
4444
import {SignInNote} from './components/signInNote';
4545
import {SmartLink} from './components/smartLink';
46+
import {StepComponent, StepConnector} from './components/stepConnector';
4647
import {TableOfContents} from './components/tableOfContents';
4748
import {VersionRequirement} from './components/version-requirement';
4849
import {VimeoEmbed} from './components/video';
@@ -95,6 +96,8 @@ export function mdxComponents(
9596
RelayMetrics,
9697
SandboxLink,
9798
SignInNote,
99+
StepComponent,
100+
StepConnector,
98101
VimeoEmbed,
99102
VersionRequirement,
100103
a: SmartLink,

0 commit comments

Comments
 (0)