2025-07-16 17:53:50 +01:00
|
|
|
import React, { createContext, useContext, useMemo, useRef } from 'react';
|
2025-08-15 14:43:30 +01:00
|
|
|
import { Text, Stack, Box, Flex, Divider } from '@mantine/core';
|
2025-08-01 14:22:19 +01:00
|
|
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
|
|
|
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
2025-08-08 12:09:41 +01:00
|
|
|
import { Tooltip } from '../../shared/Tooltip';
|
|
|
|
import { TooltipTip } from '../../shared/tooltip/TooltipContent';
|
2025-08-15 14:43:30 +01:00
|
|
|
import { createFilesToolStep, FilesToolStepProps } from './FilesToolStep';
|
|
|
|
import { createReviewToolStep, ReviewToolStepProps } from './ReviewToolStep';
|
2025-07-16 17:53:50 +01:00
|
|
|
|
|
|
|
interface ToolStepContextType {
|
|
|
|
visibleStepCount: number;
|
|
|
|
}
|
|
|
|
|
|
|
|
const ToolStepContext = createContext<ToolStepContextType | null>(null);
|
|
|
|
|
|
|
|
export interface ToolStepProps {
|
|
|
|
title: string;
|
|
|
|
isVisible?: boolean;
|
|
|
|
isCollapsed?: boolean;
|
|
|
|
onCollapsedClick?: () => void;
|
|
|
|
children?: React.ReactNode;
|
|
|
|
helpText?: string;
|
|
|
|
showNumber?: boolean;
|
2025-08-15 14:43:30 +01:00
|
|
|
_stepNumber?: number; // Internal prop set by ToolStepContainer
|
|
|
|
_excludeFromCount?: boolean; // Internal prop to exclude from visible count calculation
|
|
|
|
_noPadding?: boolean; // Internal prop to exclude from default left padding
|
2025-08-08 12:09:41 +01:00
|
|
|
tooltip?: {
|
|
|
|
content?: React.ReactNode;
|
|
|
|
tips?: TooltipTip[];
|
|
|
|
header?: {
|
|
|
|
title: string;
|
|
|
|
logo?: React.ReactNode;
|
|
|
|
};
|
|
|
|
};
|
2025-07-16 17:53:50 +01:00
|
|
|
}
|
|
|
|
|
2025-08-08 12:09:41 +01:00
|
|
|
const renderTooltipTitle = (
|
|
|
|
title: string,
|
|
|
|
tooltip: ToolStepProps['tooltip'],
|
|
|
|
isCollapsed: boolean
|
|
|
|
) => {
|
|
|
|
if (tooltip && !isCollapsed) {
|
|
|
|
return (
|
|
|
|
<Tooltip
|
|
|
|
content={tooltip.content}
|
|
|
|
tips={tooltip.tips}
|
|
|
|
header={tooltip.header}
|
|
|
|
sidebarTooltip={true}
|
|
|
|
>
|
|
|
|
<Flex align="center" gap="xs" onClick={(e) => e.stopPropagation()}>
|
|
|
|
<Text fw={500} size="lg">
|
|
|
|
{title}
|
|
|
|
</Text>
|
|
|
|
<span className="material-symbols-rounded" style={{ fontSize: '1.2rem', color: 'var(--icon-files-color)' }}>
|
|
|
|
gpp_maybe
|
|
|
|
</span>
|
|
|
|
</Flex>
|
|
|
|
</Tooltip>
|
|
|
|
);
|
|
|
|
}
|
2025-08-15 14:43:30 +01:00
|
|
|
|
2025-08-08 12:09:41 +01:00
|
|
|
return (
|
|
|
|
<Text fw={500} size="lg">
|
|
|
|
{title}
|
|
|
|
</Text>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2025-07-16 17:53:50 +01:00
|
|
|
const ToolStep = ({
|
|
|
|
title,
|
|
|
|
isVisible = true,
|
|
|
|
isCollapsed = false,
|
|
|
|
onCollapsedClick,
|
|
|
|
children,
|
|
|
|
helpText,
|
2025-08-08 12:09:41 +01:00
|
|
|
showNumber,
|
2025-08-15 14:43:30 +01:00
|
|
|
_stepNumber,
|
|
|
|
_noPadding,
|
2025-08-08 12:09:41 +01:00
|
|
|
tooltip
|
2025-07-16 17:53:50 +01:00
|
|
|
}: ToolStepProps) => {
|
|
|
|
if (!isVisible) return null;
|
|
|
|
|
2025-08-01 14:22:19 +01:00
|
|
|
const parent = useContext(ToolStepContext);
|
2025-08-15 14:43:30 +01:00
|
|
|
|
2025-07-16 17:53:50 +01:00
|
|
|
// Auto-detect if we should show numbers based on sibling count
|
|
|
|
const shouldShowNumber = useMemo(() => {
|
|
|
|
if (showNumber !== undefined) return showNumber;
|
|
|
|
return parent ? parent.visibleStepCount >= 3 : false;
|
2025-08-01 14:22:19 +01:00
|
|
|
}, [showNumber, parent]);
|
2025-07-16 17:53:50 +01:00
|
|
|
|
2025-08-15 14:43:30 +01:00
|
|
|
const stepNumber = _stepNumber;
|
2025-07-16 17:53:50 +01:00
|
|
|
|
|
|
|
return (
|
2025-08-15 14:43:30 +01:00
|
|
|
<div>
|
|
|
|
<div
|
|
|
|
style={{
|
|
|
|
padding: '1rem',
|
|
|
|
opacity: isCollapsed ? 0.8 : 1,
|
|
|
|
color: isCollapsed ? 'var(--mantine-color-dimmed)' : 'inherit',
|
|
|
|
transition: 'opacity 0.2s ease, color 0.2s ease'
|
|
|
|
}}
|
|
|
|
>
|
2025-08-01 14:22:19 +01:00
|
|
|
{/* Chevron icon to collapse/expand the step */}
|
2025-08-15 14:43:30 +01:00
|
|
|
<Flex
|
|
|
|
align="center"
|
|
|
|
justify="space-between"
|
2025-08-01 14:22:19 +01:00
|
|
|
mb="sm"
|
|
|
|
style={{
|
|
|
|
cursor: onCollapsedClick ? 'pointer' : 'default'
|
|
|
|
}}
|
|
|
|
onClick={onCollapsedClick}
|
|
|
|
>
|
|
|
|
<Flex align="center" gap="sm">
|
|
|
|
{shouldShowNumber && (
|
|
|
|
<Text fw={500} size="lg" c="dimmed">
|
|
|
|
{stepNumber}
|
|
|
|
</Text>
|
|
|
|
)}
|
2025-08-08 12:09:41 +01:00
|
|
|
{renderTooltipTitle(title, tooltip, isCollapsed)}
|
2025-08-01 14:22:19 +01:00
|
|
|
</Flex>
|
2025-08-15 14:43:30 +01:00
|
|
|
|
2025-08-01 14:22:19 +01:00
|
|
|
{isCollapsed ? (
|
2025-08-15 14:43:30 +01:00
|
|
|
<ChevronRightIcon style={{
|
|
|
|
fontSize: '1.2rem',
|
2025-08-01 14:22:19 +01:00
|
|
|
color: 'var(--mantine-color-dimmed)',
|
|
|
|
opacity: onCollapsedClick ? 1 : 0.5
|
|
|
|
}} />
|
|
|
|
) : (
|
2025-08-15 14:43:30 +01:00
|
|
|
<ExpandMoreIcon style={{
|
|
|
|
fontSize: '1.2rem',
|
2025-08-01 14:22:19 +01:00
|
|
|
color: 'var(--mantine-color-dimmed)',
|
|
|
|
opacity: onCollapsedClick ? 1 : 0.5
|
|
|
|
}} />
|
|
|
|
)}
|
|
|
|
</Flex>
|
2025-07-16 17:53:50 +01:00
|
|
|
|
2025-08-15 14:43:30 +01:00
|
|
|
{!isCollapsed && (
|
|
|
|
<Stack gap="md" pl={_noPadding ? 0 : "md"}>
|
2025-07-16 17:53:50 +01:00
|
|
|
{helpText && (
|
|
|
|
<Text size="sm" c="dimmed">
|
|
|
|
{helpText}
|
|
|
|
</Text>
|
|
|
|
)}
|
|
|
|
{children}
|
|
|
|
</Stack>
|
|
|
|
)}
|
2025-08-15 14:43:30 +01:00
|
|
|
</div>
|
|
|
|
<Divider style={{ marginLeft: '1rem', marginRight: '-0.5rem' }} />
|
|
|
|
</div>
|
2025-07-16 17:53:50 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2025-08-15 14:43:30 +01:00
|
|
|
// ToolStepFactory for creating numbered steps
|
|
|
|
export function createToolSteps() {
|
|
|
|
let stepNumber = 1;
|
|
|
|
const steps: React.ReactElement[] = [];
|
|
|
|
|
|
|
|
const create = (
|
|
|
|
title: string,
|
|
|
|
props: Omit<ToolStepProps, 'title' | '_stepNumber'> = {},
|
|
|
|
children?: React.ReactNode
|
|
|
|
): React.ReactElement => {
|
|
|
|
const isVisible = props.isVisible !== false;
|
|
|
|
const currentStepNumber = isVisible ? stepNumber++ : undefined;
|
|
|
|
|
|
|
|
const step = React.createElement(ToolStep, {
|
|
|
|
...props,
|
|
|
|
title,
|
|
|
|
_stepNumber: currentStepNumber,
|
|
|
|
children,
|
|
|
|
key: `step-${title.toLowerCase().replace(/\s+/g, '-')}`
|
|
|
|
});
|
2025-07-16 17:53:50 +01:00
|
|
|
|
2025-08-15 14:43:30 +01:00
|
|
|
steps.push(step);
|
|
|
|
return step;
|
|
|
|
};
|
|
|
|
|
|
|
|
const createFilesStep = (props: FilesToolStepProps): React.ReactElement => {
|
|
|
|
return createFilesToolStep(create, props);
|
|
|
|
};
|
|
|
|
|
|
|
|
const createReviewStep = <TParams = unknown>(props: ReviewToolStepProps<TParams>): React.ReactElement => {
|
|
|
|
return createReviewToolStep(create, props);
|
|
|
|
};
|
2025-07-16 17:53:50 +01:00
|
|
|
|
2025-08-15 14:43:30 +01:00
|
|
|
const getVisibleCount = () => {
|
|
|
|
return steps.filter(step => {
|
|
|
|
const props = step.props as ToolStepProps;
|
|
|
|
const isVisible = props.isVisible !== false;
|
|
|
|
const excludeFromCount = props._excludeFromCount === true;
|
|
|
|
return isVisible && !excludeFromCount;
|
|
|
|
}).length;
|
|
|
|
};
|
|
|
|
|
|
|
|
return { create, createFilesStep, createReviewStep, getVisibleCount, steps };
|
|
|
|
}
|
|
|
|
|
|
|
|
// Context provider wrapper for tools using the factory
|
|
|
|
export function ToolStepProvider({ children }: { children: React.ReactNode }) {
|
|
|
|
// Count visible steps from children that are ToolStep elements
|
2025-07-16 17:53:50 +01:00
|
|
|
const visibleStepCount = useMemo(() => {
|
|
|
|
let count = 0;
|
|
|
|
React.Children.forEach(children, (child) => {
|
|
|
|
if (React.isValidElement(child) && child.type === ToolStep) {
|
2025-08-15 14:43:30 +01:00
|
|
|
const props = child.props as ToolStepProps;
|
|
|
|
const isVisible = props.isVisible !== false;
|
|
|
|
const excludeFromCount = props._excludeFromCount === true;
|
|
|
|
if (isVisible && !excludeFromCount) count++;
|
2025-07-16 17:53:50 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
return count;
|
|
|
|
}, [children]);
|
|
|
|
|
|
|
|
const contextValue = useMemo(() => ({
|
2025-08-15 14:43:30 +01:00
|
|
|
visibleStepCount
|
2025-07-16 17:53:50 +01:00
|
|
|
}), [visibleStepCount]);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<ToolStepContext.Provider value={contextValue}>
|
|
|
|
{children}
|
|
|
|
</ToolStepContext.Provider>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2025-08-15 14:43:30 +01:00
|
|
|
export type { FilesToolStepProps } from './FilesToolStep';
|
|
|
|
export type { ReviewToolStepProps } from './ReviewToolStep';
|
2025-07-16 17:53:50 +01:00
|
|
|
export default ToolStep;
|