diff --git a/frontend/src/Editor/Components/Steps.jsx b/frontend/src/Editor/Components/Steps.jsx index 8c5be86e74..6011b382b3 100644 --- a/frontend/src/Editor/Components/Steps.jsx +++ b/frontend/src/Editor/Components/Steps.jsx @@ -1,8 +1,9 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useRef } from 'react'; import { isExpectedDataType } from '@/_helpers/utils'; import { ToolTip } from '@/_components/ToolTip'; +import './Steps.scss'; -export const Steps = function Button({ properties, styles, fireEvent, setExposedVariable, height, darkMode, dataCy }) { +export const Steps = function Steps({ properties, styles, fireEvent, setExposedVariable, height, darkMode, dataCy }) { const { stepsSelectable, disabledState } = properties; const visibility = isExpectedDataType(properties.visibility, 'boolean'); const currentStepId = isExpectedDataType(properties.currentStep, 'number'); @@ -15,19 +16,88 @@ export const Steps = function Button({ properties, styles, fireEvent, setExposed const [isVisible, setIsVisible] = useState(visibility); const [isDisabled, setIsDisabled] = useState(disabledState); const [activeStepId, setActiveStepId] = useState(currentStepId); + const theme = properties.variant; + const [progressBarWidth, setProgressBarWidth] = useState(0); + const [labelPadding, setLabelPadding] = useState(0); + const [containerWidth, setContainerWidth] = useState(0); + const firstLabelRef = useRef(null); + const lastLabelRef = useRef(null); + const containerRef = useRef(null); + + // Common function to calculate progress bar width and label padding + const calculateProgressBarWidth = () => { + if (!containerRef.current || theme !== 'titles') return; + + const containerWidth = containerRef.current.offsetWidth; + setContainerWidth(containerWidth); + + const stepWidth = 20; // width of dot + padding + const totalStepsWidth = filteredSteps.length * stepWidth; + const totalProgressBars = filteredSteps.length - 1; + + if (filteredSteps.length === 1) { + setProgressBarWidth(containerWidth); + setLabelPadding(0); // No padding needed for single step + return; + } + + // Calculate progress bar width + const progressBarWidth = (containerWidth - totalStepsWidth) / totalProgressBars; + setProgressBarWidth(Math.min(progressBarWidth, (containerWidth - totalStepsWidth) / filteredSteps.length)); + + // Calculate label padding + if (firstLabelRef.current && lastLabelRef.current) { + // Step 1: Calculate individual label width + const labelWidth = (containerWidth - (filteredSteps.length - 1) - 4) / filteredSteps.length; + + // Step 2: Find max label length + const firstLabelWidth = firstLabelRef.current.offsetWidth; + const lastLabelWidth = lastLabelRef.current.offsetWidth; + const maxLabelWidth = Math.max(firstLabelWidth, lastLabelWidth); + + // Step 3: Calculate label padding + const calculatedPadding = (maxLabelWidth / 2) - 1; + setLabelPadding(Math.max(2, calculatedPadding)); // Ensure minimum padding of 2px + } + }; + + // Add resize observer to track container width and calculate progress bar width + useEffect(() => { + if (theme !== 'titles') return; + + const resizeObserver = new ResizeObserver((entries) => { + for (const entry of entries) { + calculateProgressBarWidth(); + } + }); + + if (containerRef.current) { + resizeObserver.observe(containerRef.current); + } + + return () => resizeObserver.disconnect(); + }, [theme, JSON.stringify(steps)]); + + // Recalculate measurements when steps change + useEffect(() => { + calculateProgressBarWidth(); + }, [theme, JSON.stringify(steps)]); + + // Filter visible steps and find current step index const filteredSteps = (stepsArr || []).filter((step) => step.visible); const currentStepIndex = filteredSteps.findIndex((step) => step.id == activeStepId); + // Sanitize steps data useEffect(() => { - // this is required for legacy support where visible and disabled properties are not present - const sanitizedSteps = JSON.parse(JSON.stringify(steps || [])).map((step) => { - if (!('visible' in step)) step.visible = true; - if (!('disabled' in step)) step.disabled = false; - return step; - }); + const sanitizedSteps = JSON.parse(JSON.stringify(steps || [])).map((step) => ({ + ...step, + visible: 'visible' in step ? step.visible : true, + disabled: 'disabled' in step ? step.disabled : false, + })); setStepsArr(sanitizedSteps); }, [JSON.stringify(steps)]); + // Dynamic styles for theming const dynamicStyle = { '--bgColor': styles.color, '--textColor': textColor, @@ -37,151 +107,127 @@ export const Steps = function Button({ properties, styles, fireEvent, setExposed '--completedLabel': completedLabel === '#1B1F24' ? 'var(--text-primary)' : completedLabel, '--currentStepLabel': currentStepLabel === '#1B1F24' ? 'var(--text-primary)' : currentStepLabel, }; - const theme = properties.variant; - console.log(theme); - console.log(properties); - console.log(styles); - console.log(completedLabel, 'completed'); - const activeStepHandler = (id) => { + // Step click handler + const handleStepClick = (id) => { const step = filteredSteps.find((item) => item.id == id); - if (step) { + if (step && !step.disabled && !isDisabled) { setActiveStepId(step.id); fireEvent('onSelect'); } }; + // Expose variables and methods useEffect(() => { setExposedVariable('isVisible', isVisible); - }, [isVisible]); - - useEffect(() => { - setIsVisible(visibility); - }, [visibility]); - - useEffect(() => { setExposedVariable('isDisabled', isDisabled); - }, [isDisabled]); - - useEffect(() => { setExposedVariable('currentStepId', activeStepId); - }, [activeStepId]); + setExposedVariable('steps', stepsArr); - useEffect(() => { - setIsDisabled(disabledState); - }, [disabledState]); - - useEffect(() => { - setActiveStepId(currentStepId); - }, [currentStepId]); - - useEffect(() => { - setExposedVariable('steps', steps); setExposedVariable('setStepVisible', (stepId, visibility) => { setStepsArr((prev) => { - const updatedSteps = prev.map((item) => { - if (item.id == stepId) { - return { ...item, visible: visibility }; - } - return item; - }); + const updatedSteps = prev.map((item) => + item.id == stepId ? { ...item, visible: visibility } : item + ); setExposedVariable('steps', updatedSteps); return updatedSteps; }); }); + setExposedVariable('setStepDisable', (stepId, disabled) => { setStepsArr((prev) => { - const updatedSteps = prev.map((item) => { - if (item.id == stepId) { - return { ...item, disabled: disabled }; - } - return item; - }); + const updatedSteps = prev.map((item) => + item.id == stepId ? { ...item, disabled: disabled } : item + ); setExposedVariable('steps', updatedSteps); return updatedSteps; }); }); + setExposedVariable('resetSteps', () => { setActiveStepId(stepsArr.filter((step) => step.visible)?.[0]?.id); }); - }, [JSON.stringify(steps), JSON.stringify(stepsArr)]); - useEffect(() => { setExposedVariable('setStep', (stepId) => { - if (disabledState) return; - setActiveStepId(stepId); + if (!disabledState) setActiveStepId(stepId); }); setExposedVariable('setVisibility', (visibility) => setIsVisible(visibility)); setExposedVariable('setDisable', (disabled) => setIsDisabled(disabled)); - }, []); + }, [isVisible, isDisabled, activeStepId, stepsArr, disabledState]); + + // Update state from props + useEffect(() => setIsVisible(visibility), [visibility]); + useEffect(() => setIsDisabled(disabledState), [disabledState]); + useEffect(() => setActiveStepId(currentStepId), [currentStepId]); + + if (!isVisible) return null; return ( - isVisible && ( -