Add onboarding flow using Reactour (#4635)

# Description of Changes
Add onboarding flow
This commit is contained in:
James Brunton
2025-10-20 15:07:40 +01:00
committed by GitHub
parent 3e6236d957
commit 3e23dc59b6
34 changed files with 2381 additions and 91 deletions
@@ -155,4 +155,4 @@ const AppConfigModal: React.FC<AppConfigModalProps> = ({ opened, onClose }) => {
);
};
export default AppConfigModal;
export default AppConfigModal;
@@ -51,6 +51,7 @@ const FileCard = ({ file, fileStub, onRemove, onDoubleClick, onView, onEdit, isS
onMouseLeave={() => setIsHovered(false)}
onClick={onSelect}
data-testid="file-card"
data-tour="file-card-checkbox"
>
<Stack gap={6} align="center">
<Box
@@ -14,6 +14,7 @@ import AllToolsNavButton from './AllToolsNavButton';
import ActiveToolButton from "./quickAccessBar/ActiveToolButton";
import AppConfigModal from './AppConfigModal';
import { useAppConfig } from '../../hooks/useAppConfig';
import { useOnboarding } from '../../contexts/OnboardingContext';
import {
isNavButtonActive,
getNavButtonStyle,
@@ -27,6 +28,7 @@ const QuickAccessBar = forwardRef<HTMLDivElement>((_, ref) => {
const { handleReaderToggle, handleToolSelect, selectedToolKey, leftPanelView, toolRegistry, readerMode, resetTool } = useToolWorkflow();
const { getToolNavigation } = useSidebarNavigation();
const { config } = useAppConfig();
const { startTour } = useOnboarding();
const [configModalOpen, setConfigModalOpen] = useState(false);
const [activeButton, setActiveButton] = useState<string>('tools');
const scrollableRef = useRef<HTMLDivElement>(null);
@@ -60,7 +62,12 @@ const QuickAccessBar = forwardRef<HTMLDivElement>((_, ref) => {
// Render navigation button with conditional URL support
return (
<div key={config.id} className="flex flex-col items-center gap-1" style={{ marginTop: index === 0 ? '0.5rem' : "0rem" }}>
<div
key={config.id}
className="flex flex-col items-center gap-1"
style={{ marginTop: index === 0 ? '0.5rem' : "0rem" }}
data-tour={`${config.id}-button`}
>
<ActionIcon
{...(navProps ? {
component: "a" as const,
@@ -88,8 +95,7 @@ const QuickAccessBar = forwardRef<HTMLDivElement>((_, ref) => {
);
};
const buttonConfigs: ButtonConfig[] = [
const mainButtons: ButtonConfig[] = [
{
id: 'read',
name: t("quickAccess.read", "Read"),
@@ -131,6 +137,9 @@ const QuickAccessBar = forwardRef<HTMLDivElement>((_, ref) => {
}
}
},
];
const middleButtons: ButtonConfig[] = [
{
id: 'files',
name: t("quickAccess.files", "Files"),
@@ -150,6 +159,20 @@ const QuickAccessBar = forwardRef<HTMLDivElement>((_, ref) => {
// type: 'navigation',
// onClick: () => setActiveButton('activity')
//},
];
const bottomButtons: ButtonConfig[] = [
{
id: 'help',
name: t("quickAccess.help", "Help"),
icon: <LocalIcon icon="help-rounded" width="1.5rem" height="1.5rem" />,
isRound: true,
size: 'lg',
type: 'action',
onClick: () => {
startTour();
},
},
{
id: 'config',
name: config?.enableLogin ? t("quickAccess.account", "Account") : t("quickAccess.config", "Config"),
@@ -162,8 +185,6 @@ const QuickAccessBar = forwardRef<HTMLDivElement>((_, ref) => {
}
];
return (
<div
ref={ref}
@@ -198,49 +219,41 @@ const QuickAccessBar = forwardRef<HTMLDivElement>((_, ref) => {
}}
>
<div className="scrollable-content">
{/* Top section with main buttons */}
{/* Main navigation section */}
<Stack gap="lg" align="center">
{buttonConfigs.slice(0, -1).map((config, index) => (
{mainButtons.map((config, index) => (
<React.Fragment key={config.id}>
{renderNavButton(config, index)}
{/* Add divider after Automate button (index 1) and Files button (index 2) */}
{index === 1 && (
<Divider
size="xs"
className="content-divider"
/>
)}
</React.Fragment>
))}
</Stack>
{/* Spacer to push Config button to bottom */}
{/* Divider after main buttons */}
<Divider
size="xs"
className="content-divider"
/>
{/* Middle section */}
<Stack gap="lg" align="center">
{middleButtons.map((config, index) => (
<React.Fragment key={config.id}>
{renderNavButton(config, index)}
</React.Fragment>
))}
</Stack>
{/* Spacer to push bottom buttons to bottom */}
<div className="spacer" />
{/* Config button at the bottom */}
{buttonConfigs
.filter(config => config.id === 'config')
.map(config => (
<div key={config.id} className="flex flex-col items-center gap-1">
<ActionIcon
size={config.size || 'lg'}
variant="subtle"
onClick={config.onClick}
style={getNavButtonStyle(config, activeButton, isFilesModalOpen, configModalOpen, selectedToolKey, leftPanelView)}
className={isNavButtonActive(config, activeButton, isFilesModalOpen, configModalOpen, selectedToolKey, leftPanelView) ? 'activeIconScale' : ''}
aria-label={config.name}
data-testid={`${config.id}-button`}
>
<span className="iconContainer">
{config.icon}
</span>
</ActionIcon>
<span className={`button-text ${isNavButtonActive(config, activeButton, isFilesModalOpen, configModalOpen, selectedToolKey, leftPanelView) ? 'active' : 'inactive'}`}>
{config.name}
</span>
</div>
{/* Bottom section */}
<Stack gap="lg" align="center">
{bottomButtons.map((config, index) => (
<React.Fragment key={config.id}>
{renderNavButton(config, index)}
</React.Fragment>
))}
</Stack>
</div>
</div>
+2 -2
View File
@@ -168,7 +168,7 @@ export default function RightRail() {
<div className="right-rail-inner">
{sectionsWithButtons.map(({ section, buttons: sectionButtons }) => (
<React.Fragment key={section}>
<div className="right-rail-section">
<div className="right-rail-section" data-tour="right-rail-controls">
{sectionButtons.map((btn, index) => {
const content = renderButton(btn);
if (!content) return null;
@@ -186,7 +186,7 @@ export default function RightRail() {
<Divider className="right-rail-divider" />
</React.Fragment>
))}
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '1rem' }}>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '1rem' }} data-tour="right-rail-settings">
{renderWithTooltip(
<ActionIcon
variant="subtle"
@@ -104,7 +104,7 @@ const createViewOptions = (
fileEditorOption,
];
const customOptions = (customViews ?? [])
const customOptions = (customViews ?? [])
.filter((view) => view.data != null)
.map((view) => ({
label: (
@@ -169,6 +169,7 @@ const TopControls = ({
<div className="absolute left-0 w-full top-0 z-[100] pointer-events-none">
<div className="flex justify-center mt-[0.5rem]">
<SegmentedControl
data-tour="view-switcher"
data={createViewOptions(currentView, switchingTo, activeFiles, currentFileIndex, onFileSelect, customViews)}
value={currentView}
onChange={handleViewChange}
@@ -27,7 +27,7 @@ export interface ConfigColors {
export const createConfigNavSections = (
Overview: React.ComponentType<{ onLogoutClick: () => void }>,
onLogoutClick: () => void
onLogoutClick: () => void,
): ConfigNavSection[] => {
const sections: ConfigNavSection[] = [
{
@@ -61,4 +61,4 @@ export const createConfigNavSections = (
];
return sections;
};
};
@@ -145,6 +145,7 @@
.content-divider {
width: 3.75rem;
border-color: var(--color-gray-300);
margin: 1rem 0;
}
/* Spacer */