Skip to content

Commit 074644f

Browse files
authored
fix(platform): Update onboarding options on tab switch (#12913)
1 parent 1d80502 commit 074644f

File tree

3 files changed

+78
-43
lines changed

3 files changed

+78
-43
lines changed

src/components/codeContext.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import Cookies from 'js-cookie';
55

66
import {isLocalStorageAvailable} from 'sentry-docs/utils';
77

8+
import {OnboardingOptionType} from './onboarding';
9+
810
type ProjectCodeKeywords = {
911
API_URL: string;
1012
DSN: string;
@@ -99,12 +101,14 @@ type CodeSelection = {
99101
type CodeContextType = {
100102
codeKeywords: CodeKeywords;
101103
isLoading: boolean;
104+
onboardingOptions: OnboardingOptionType[];
102105
sharedKeywordSelection: [
103106
Record<string, number>,
104107
React.Dispatch<Record<string, number>>,
105108
];
106109
storedCodeSelection: SelectedCodeTabs;
107110
updateCodeSelection: (selection: CodeSelection) => void;
111+
updateOnboardingOptions: (options: OnboardingOptionType[]) => void;
108112
};
109113

110114
export const CodeContext = createContext<CodeContextType | null>(null);
@@ -297,6 +301,7 @@ export function CodeContextProvider({children}: {children: React.ReactNode}) {
297301
const [codeKeywords, setCodeKeywords] = useState(cachedCodeKeywords ?? DEFAULTS);
298302
const [isLoading, setIsLoading] = useState<boolean>(cachedCodeKeywords ? false : true);
299303
const [storedCodeSelection, setStoredCodeSelection] = useState<SelectedCodeTabs>({});
304+
const [onboardingOptions, setOnboardingOptions] = useState<OnboardingOptionType[]>([]);
300305

301306
// populate state using localstorage
302307
useEffect(() => {
@@ -342,6 +347,8 @@ export function CodeContextProvider({children}: {children: React.ReactNode}) {
342347
updateCodeSelection,
343348
sharedKeywordSelection,
344349
isLoading,
350+
onboardingOptions,
351+
updateOnboardingOptions: options => setOnboardingOptions(options),
345352
};
346353

347354
return <CodeContext.Provider value={result}>{children}</CodeContext.Provider>;

src/components/codeTabs.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import styled from '@emotion/styled';
1414
import {CodeBlockProps} from './codeBlock';
1515
import {CodeContext} from './codeContext';
1616
import {KEYWORDS_REGEX, ORG_AUTH_TOKEN_REGEX} from './codeKeywords';
17+
import {updateElementsVisibilityForOptions} from './onboarding';
1718
import {SignInNote} from './signInNote';
1819

1920
// human readable versions of names
@@ -99,6 +100,12 @@ export function CodeTabs({children}: CodeTabProps) {
99100
}
100101
}, [codeContext?.storedCodeSelection, groupId, possibleChoices]);
101102

103+
// react to possible changes in options when switching tabs
104+
useEffect(() => {
105+
updateElementsVisibilityForOptions(codeContext?.onboardingOptions || [], false);
106+
// eslint-disable-next-line react-hooks/exhaustive-deps
107+
}, [selectedTabIndex]);
108+
102109
const buttons = possibleChoices.map((choice, idx) => (
103110
<TabButton
104111
key={idx}

src/components/onboarding/index.tsx

Lines changed: 64 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
'use client';
22

3-
import {ReactNode, useEffect, useReducer, useState} from 'react';
3+
import {ReactNode, useContext, useEffect, useReducer, useState} from 'react';
44
import {QuestionMarkCircledIcon} from '@radix-ui/react-icons';
55
import * as Tooltip from '@radix-ui/react-tooltip';
66
import {Button, Checkbox, Theme} from '@radix-ui/themes';
77

88
import styles from './styles.module.scss';
99

10+
import {CodeContext} from '../codeContext';
11+
1012
const optionDetails: Record<
1113
OptionId,
1214
{
@@ -74,7 +76,7 @@ const OPTION_IDS = [
7476

7577
type OptionId = (typeof OPTION_IDS)[number];
7678

77-
type OnboardingOptionType = {
79+
export type OnboardingOptionType = {
7880
/**
7981
* Unique identifier for the option, will control the visibility
8082
* of `<OnboardingOption optionId="this_id"` /> somewhere on the page
@@ -119,12 +121,65 @@ export function OnboardingOption({
119121
);
120122
}
121123

124+
/**
125+
* Updates DOM elements' visibility based on selected onboarding options
126+
*/
127+
export function updateElementsVisibilityForOptions(
128+
options: OnboardingOptionType[],
129+
touchedOptions: boolean
130+
) {
131+
options.forEach(option => {
132+
if (option.disabled) {
133+
return;
134+
}
135+
const targetElements = document.querySelectorAll<HTMLDivElement>(
136+
`[data-onboarding-option="${option.id}"]`
137+
);
138+
139+
targetElements.forEach(el => {
140+
const hiddenForThisOption = el.dataset.hideForThisOption === 'true';
141+
if (hiddenForThisOption) {
142+
el.classList.toggle('hidden', option.checked);
143+
} else {
144+
el.classList.toggle('hidden', !option.checked);
145+
}
146+
// only animate things when user has interacted with the options
147+
if (touchedOptions) {
148+
if (el.classList.contains('code-line')) {
149+
el.classList.toggle('animate-line', option.checked);
150+
}
151+
// animate content, account for inverted logic for hiding
152+
else {
153+
el.classList.toggle(
154+
'animate-content',
155+
hiddenForThisOption ? !option.checked : option.checked
156+
);
157+
}
158+
}
159+
});
160+
if (option.checked && optionDetails[option.id].deps?.length) {
161+
const dependenciesSelector = optionDetails[option.id].deps!.map(
162+
dep => `[data-onboarding-option="${dep}"]`
163+
);
164+
const dependencies = document.querySelectorAll<HTMLDivElement>(
165+
dependenciesSelector.join(', ')
166+
);
167+
168+
dependencies.forEach(dep => {
169+
dep.classList.remove('hidden');
170+
});
171+
}
172+
});
173+
}
174+
122175
export function OnboardingOptionButtons({
123176
options: initialOptions,
124177
}: {
125178
// convenience to allow passing option ids as strings when no additional config is required
126179
options: (OnboardingOptionType | OptionId)[];
127180
}) {
181+
const codeContext = useContext(CodeContext);
182+
128183
const normalizedOptions = initialOptions.map(option => {
129184
if (typeof option === 'string') {
130185
return {
@@ -187,49 +242,15 @@ export function OnboardingOptionButtons({
187242
});
188243
});
189244
}
245+
246+
// sync local state to global
190247
useEffect(() => {
191-
options.forEach(option => {
192-
if (option.disabled) {
193-
return;
194-
}
195-
const targetElements = document.querySelectorAll<HTMLDivElement>(
196-
`[data-onboarding-option="${option.id}"]`
197-
);
198-
targetElements.forEach(el => {
199-
const hiddenForThisOption = el.dataset.hideForThisOption === 'true';
200-
if (hiddenForThisOption) {
201-
el.classList.toggle('hidden', option.checked);
202-
} else {
203-
el.classList.toggle('hidden', !option.checked);
204-
}
205-
// only animate things when user has interacted with the options
206-
if (touchedOptions) {
207-
if (el.classList.contains('code-line')) {
208-
el.classList.toggle('animate-line', option.checked);
209-
}
210-
// animate content, account for inverted logic for hiding
211-
else {
212-
el.classList.toggle(
213-
'animate-content',
214-
hiddenForThisOption ? !option.checked : option.checked
215-
);
216-
}
217-
}
218-
});
219-
if (option.checked && optionDetails[option.id].deps?.length) {
220-
const dependenciesSelecor = optionDetails[option.id].deps!.map(
221-
dep => `[data-onboarding-option="${dep}"]`
222-
);
223-
const dependencies = document.querySelectorAll<HTMLDivElement>(
224-
dependenciesSelecor.join(', ')
225-
);
248+
codeContext?.updateOnboardingOptions(options);
249+
}, [options, codeContext]);
226250

227-
dependencies.forEach(dep => {
228-
dep.classList.remove('hidden');
229-
});
230-
}
231-
});
232-
}, [options, touchedOptions]);
251+
useEffect(() => {
252+
updateElementsVisibilityForOptions(options, touchedOptions);
253+
}, [options, touchOptions, touchedOptions]);
233254

234255
return (
235256
<div className="onboarding-options flex flex-wrap gap-3 py-2 bg-[var(--white)] dark:bg-[var(--gray-1)] sticky top-[80px] z-[4] shadow-[var(--shadow-6)] transition">

0 commit comments

Comments
 (0)