Skip to content

Commit

Permalink
sync onboarding options to global state
Browse files Browse the repository at this point in the history
  • Loading branch information
chargome committed Mar 4, 2025
1 parent ab459a6 commit 8e3a953
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 43 deletions.
7 changes: 7 additions & 0 deletions src/components/codeContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import Cookies from 'js-cookie';

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

import {OnboardingOptionType} from './onboarding';

type ProjectCodeKeywords = {
API_URL: string;
DSN: string;
Expand Down Expand Up @@ -99,12 +101,14 @@ type CodeSelection = {
type CodeContextType = {
codeKeywords: CodeKeywords;
isLoading: boolean;
onboardingOptions: OnboardingOptionType[];
sharedKeywordSelection: [
Record<string, number>,
React.Dispatch<Record<string, number>>,
];
storedCodeSelection: SelectedCodeTabs;
updateCodeSelection: (selection: CodeSelection) => void;
updateOnboardingOptions: (options: OnboardingOptionType[]) => void;
};

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

// populate state using localstorage
useEffect(() => {
Expand Down Expand Up @@ -342,6 +347,8 @@ export function CodeContextProvider({children}: {children: React.ReactNode}) {
updateCodeSelection,
sharedKeywordSelection,
isLoading,
onboardingOptions,
updateOnboardingOptions: options => setOnboardingOptions(options),
};

return <CodeContext.Provider value={result}>{children}</CodeContext.Provider>;
Expand Down
7 changes: 7 additions & 0 deletions src/components/codeTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import styled from '@emotion/styled';
import {CodeBlockProps} from './codeBlock';
import {CodeContext} from './codeContext';
import {KEYWORDS_REGEX, ORG_AUTH_TOKEN_REGEX} from './codeKeywords';
import {updateElementsVisibilityForOptions} from './onboarding';
import {SignInNote} from './signInNote';

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

// react to possible changes in options when switching tabs
useEffect(() => {
updateElementsVisibilityForOptions(codeContext?.onboardingOptions || [], false);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedTabIndex]);

const buttons = possibleChoices.map((choice, idx) => (
<TabButton
key={idx}
Expand Down
107 changes: 64 additions & 43 deletions src/components/onboarding/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
'use client';

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

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

import {CodeContext} from '../codeContext';

const optionDetails: Record<
OptionId,
{
Expand Down Expand Up @@ -74,7 +76,7 @@ const OPTION_IDS = [

type OptionId = (typeof OPTION_IDS)[number];

type OnboardingOptionType = {
export type OnboardingOptionType = {
/**
* Unique identifier for the option, will control the visibility
* of `<OnboardingOption optionId="this_id"` /> somewhere on the page
Expand Down Expand Up @@ -119,12 +121,65 @@ export function OnboardingOption({
);
}

/**
* Updates DOM elements' visibility based on selected onboarding options
*/
export function updateElementsVisibilityForOptions(
options: OnboardingOptionType[],
touchedOptions: boolean
) {
options.forEach(option => {
if (option.disabled) {
return;
}
const targetElements = document.querySelectorAll<HTMLDivElement>(
`[data-onboarding-option="${option.id}"]`
);

targetElements.forEach(el => {
const hiddenForThisOption = el.dataset.hideForThisOption === 'true';
if (hiddenForThisOption) {
el.classList.toggle('hidden', option.checked);
} else {
el.classList.toggle('hidden', !option.checked);
}
// only animate things when user has interacted with the options
if (touchedOptions) {
if (el.classList.contains('code-line')) {
el.classList.toggle('animate-line', option.checked);
}
// animate content, account for inverted logic for hiding
else {
el.classList.toggle(
'animate-content',
hiddenForThisOption ? !option.checked : option.checked
);
}
}
});
if (option.checked && optionDetails[option.id].deps?.length) {
const dependenciesSelector = optionDetails[option.id].deps!.map(
dep => `[data-onboarding-option="${dep}"]`
);
const dependencies = document.querySelectorAll<HTMLDivElement>(
dependenciesSelector.join(', ')
);

dependencies.forEach(dep => {
dep.classList.remove('hidden');
});
}
});
}

export function OnboardingOptionButtons({
options: initialOptions,
}: {
// convenience to allow passing option ids as strings when no additional config is required
options: (OnboardingOptionType | OptionId)[];
}) {
const codeContext = useContext(CodeContext);

const normalizedOptions = initialOptions.map(option => {
if (typeof option === 'string') {
return {id: option, disabled: option === 'error-monitoring'};
Expand Down Expand Up @@ -182,49 +237,15 @@ export function OnboardingOptionButtons({
});
});
}

// sync local state to global
useEffect(() => {
options.forEach(option => {
if (option.disabled) {
return;
}
const targetElements = document.querySelectorAll<HTMLDivElement>(
`[data-onboarding-option="${option.id}"]`
);
targetElements.forEach(el => {
const hiddenForThisOption = el.dataset.hideForThisOption === 'true';
if (hiddenForThisOption) {
el.classList.toggle('hidden', option.checked);
} else {
el.classList.toggle('hidden', !option.checked);
}
// only animate things when user has interacted with the options
if (touchedOptions) {
if (el.classList.contains('code-line')) {
el.classList.toggle('animate-line', option.checked);
}
// animate content, account for inverted logic for hiding
else {
el.classList.toggle(
'animate-content',
hiddenForThisOption ? !option.checked : option.checked
);
}
}
});
if (option.checked && optionDetails[option.id].deps?.length) {
const dependenciesSelecor = optionDetails[option.id].deps!.map(
dep => `[data-onboarding-option="${dep}"]`
);
const dependencies = document.querySelectorAll<HTMLDivElement>(
dependenciesSelecor.join(', ')
);
codeContext?.updateOnboardingOptions(options);
}, [options, codeContext]);

dependencies.forEach(dep => {
dep.classList.remove('hidden');
});
}
});
}, [options, touchedOptions]);
useEffect(() => {
updateElementsVisibilityForOptions(options, touchedOptions);
}, [options, touchOptions, touchedOptions]);

return (
<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">
Expand Down

0 comments on commit 8e3a953

Please sign in to comment.