UNPKG

@coreui/react-pro

Version:

UI Components Library for React.js

239 lines (236 loc) 12.5 kB
import { __rest, __assign, __spreadArray } from '../../node_modules/tslib/tslib.es6.js'; import React, { forwardRef, useRef, useId, useState, useEffect, useCallback, useImperativeHandle } from 'react'; import PropTypes from 'prop-types'; import classNames from '../../_virtual/index.js'; import { CCollapse } from '../collapse/CCollapse.js'; import getNextActiveElement from '../../utils/getNextActiveElement.js'; var CStepper = forwardRef(function (_a, ref) { var controlledActiveStepNumber = _a.activeStepNumber, controlledActiveStepIndex = _a.activeStepIndex, className = _a.className, defaultActiveStepNumber = _a.defaultActiveStepNumber, _b = _a.defaultActiveStepIndex, defaultActiveStepIndex = _b === void 0 ? 0 : _b, _c = _a.layout, layout = _c === void 0 ? 'horizontal' : _c, _d = _a.linear, linear = _d === void 0 ? true : _d, onFinish = _a.onFinish, onReset = _a.onReset, onStepChange = _a.onStepChange, onStepValidationComplete = _a.onStepValidationComplete, _e = _a.steps, steps = _e === void 0 ? [] : _e, _f = _a.stepButtonLayout, stepButtonLayout = _f === void 0 ? 'horizontal' : _f, _g = _a.validation, validation = _g === void 0 ? true : _g, rest = __rest(_a, ["activeStepNumber", "activeStepIndex", "className", "defaultActiveStepNumber", "defaultActiveStepIndex", "layout", "linear", "onFinish", "onReset", "onStepChange", "onStepValidationComplete", "steps", "stepButtonLayout", "validation"]); var stepperRef = useRef(null); var stepsRef = useRef(null); var stepButtonRefs = useRef([]); var uniqueId = useId(); // Handle backward compatibility and determine controlled vs uncontrolled mode var isControlledByNumber = controlledActiveStepNumber !== undefined; var isControlledByIndex = controlledActiveStepIndex !== undefined; var isControlled = isControlledByNumber || isControlledByIndex; // Convert between step numbers (1-based) and indices (0-based) var getDefaultActiveStepIndex = function () { if (defaultActiveStepNumber !== undefined) { return Math.max(0, defaultActiveStepNumber - 1); } return defaultActiveStepIndex; }; var getControlledActiveStepIndex = function () { if (isControlledByNumber) { return Math.max(0, controlledActiveStepNumber - 1); } if (isControlledByIndex) { return controlledActiveStepIndex; } return 0; }; var _h = useState(isControlled ? getControlledActiveStepIndex() : getDefaultActiveStepIndex()), internalActiveStepIndex = _h[0], setInternalActiveStepIndex = _h[1]; var _j = useState(false), isFinished = _j[0], setIsFinished = _j[1]; var activeStepIndex = isControlled ? getControlledActiveStepIndex() : internalActiveStepIndex; // Sync state if controlled prop changes useEffect(function () { if (isControlled) { setInternalActiveStepIndex(getControlledActiveStepIndex()); } }, [controlledActiveStepNumber, controlledActiveStepIndex, isControlled]); // Ensure stepButtonRefs array has the correct size useEffect(function () { stepButtonRefs.current = stepButtonRefs.current.slice(0, steps.length); }, [steps.length]); var isStepValid = useCallback(function (index) { var _a; if (!validation) { return true; } var currentStep = steps[index]; var currentStepData = typeof currentStep === 'string' ? { } : currentStep; var form = (_a = currentStepData === null || currentStepData === void 0 ? void 0 : currentStepData.formRef) === null || _a === void 0 ? void 0 : _a.current; if (form) { var isValid = form.checkValidity(); onStepValidationComplete === null || onStepValidationComplete === void 0 ? void 0 : onStepValidationComplete({ stepNumber: index + 1, isValid: isValid, }); if (form && !isValid) { if (!form.noValidate) { form.reportValidity(); } return false; } } return true; }, [steps, validation, onStepValidationComplete]); var setActiveStep = useCallback(function (index, bypassValidation) { if (bypassValidation === void 0) { bypassValidation = false; } if (index < 0 || index >= steps.length || index === activeStepIndex) { return; } // Validate current step before moving forward if (!bypassValidation && index > activeStepIndex && !isStepValid(activeStepIndex)) { return; } if (!isControlled) { setInternalActiveStepIndex(index); } onStepChange === null || onStepChange === void 0 ? void 0 : onStepChange(index + 1); // Always call with step number (1-based) }, [steps.length, activeStepIndex, isStepValid, isControlled, onStepChange]); var handleStepClick = function (index) { if (linear) { setActiveStep(index, index <= activeStepIndex); return; } setActiveStep(index, true); }; var handleNext = function () { if (activeStepIndex < steps.length - 1) { setActiveStep(activeStepIndex + 1); } else { handleFinish(); // Attempt finish if already on last step } }; var handlePrev = function () { if (activeStepIndex > 0) { setActiveStep(activeStepIndex - 1, true); // Bypass validation when going back } }; var handleFinish = function () { if (activeStepIndex === steps.length - 1 && isStepValid(activeStepIndex)) { setIsFinished(true); onFinish === null || onFinish === void 0 ? void 0 : onFinish(); } }; var handleReset = function () { var _a; if (validation) { steps.forEach(function (step) { var _a, _b; var stepData = typeof step === 'string' ? { } : step; (_b = (_a = stepData.formRef) === null || _a === void 0 ? void 0 : _a.current) === null || _b === void 0 ? void 0 : _b.reset(); }); } var resetIndex = getDefaultActiveStepIndex(); if (!isControlled) { setInternalActiveStepIndex(resetIndex); } setIsFinished(false); onStepChange === null || onStepChange === void 0 ? void 0 : onStepChange(resetIndex + 1); // Call with step number (1-based) onReset === null || onReset === void 0 ? void 0 : onReset(); (_a = stepButtonRefs.current[resetIndex]) === null || _a === void 0 ? void 0 : _a.focus(); }; var handleKeyDown = function (event) { var target = event.target; var currentButton = target.closest('button.stepper-step-button'); if (!currentButton || !stepsRef.current) { return; } var buttons = __spreadArray([], stepsRef.current.querySelectorAll('button.stepper-step-button'), true); var nextElement = null; switch (event.key) { case 'ArrowRight': case 'ArrowDown': { nextElement = getNextActiveElement(buttons, currentButton, true, false); break; } case 'ArrowLeft': case 'ArrowUp': { nextElement = getNextActiveElement(buttons, currentButton, false, false); break; } case 'Home': { nextElement = buttons[0]; break; } case 'End': { nextElement = buttons.at(-1); break; } default: { return; } } if (nextElement) { event.preventDefault(); nextElement.focus(); } }; // Expose methods via ref useImperativeHandle(ref, function () { return ({ next: handleNext, prev: handlePrev, finish: handleFinish, reset: handleReset, }); }); var isVertical = layout === 'vertical'; return (React.createElement("div", __assign({ className: classNames('stepper', { 'stepper-vertical': isVertical, }, className), ref: stepperRef }, rest), React.createElement("ol", { className: "stepper-steps", "aria-orientation": isVertical ? 'vertical' : 'horizontal', ref: stepsRef, onKeyDown: handleKeyDown, role: "tablist" }, steps.map(function (step, index) { var _a; var stepData = typeof step === 'string' ? { label: step } : step; var isActive = !isFinished && index === activeStepIndex; var isComplete = isFinished || index < activeStepIndex; var isDisabled = isFinished || (linear && index > activeStepIndex + 1); var stepId = "stepper-".concat(rest.id || uniqueId, "-step-").concat(index); var panelId = "stepper-".concat(rest.id || uniqueId, "-panel-").concat(index); return (React.createElement("li", { key: index, className: classNames('stepper-step', stepButtonLayout), role: "presentation" }, React.createElement("button", __assign({ type: "button", className: classNames('stepper-step-button', { active: isActive, complete: isComplete, }), disabled: isDisabled, id: stepId, role: "tab", onClick: function () { return handleStepClick(index); }, ref: function (el) { stepButtonRefs.current[index] = el; }, "aria-selected": isActive }, (stepData.content && { 'aria-controls': panelId, }), { tabIndex: isActive ? 0 : -1 }), React.createElement("span", { className: "stepper-step-indicator" }, isComplete ? (React.createElement("span", { className: "stepper-step-indicator-icon" })) : (React.createElement("span", { className: "stepper-step-indicator-text" }, (_a = stepData.indicator) !== null && _a !== void 0 ? _a : index + 1))), React.createElement("span", { className: "stepper-step-label" }, stepData.label)), index < steps.length - 1 && React.createElement("div", { className: "stepper-step-connector" }), stepData.content && isVertical && (React.createElement(CCollapse, { className: "stepper-step-content", id: panelId, role: "tabpanel", visible: isActive, "aria-hidden": !isActive, "aria-labelledby": stepId, "aria-live": "polite" }, stepData.content)))); })), !isVertical && steps.some(function (step) { var stepData = typeof step === 'string' ? { } : step; return stepData.content !== undefined && stepData.content !== null; }) && (React.createElement("div", { className: "stepper-content" }, steps.map(function (step, index) { var stepData = typeof step === 'string' ? { } : step; var isActive = !isFinished && index === activeStepIndex; var stepId = "stepper-".concat(rest.id || uniqueId, "-step-").concat(index); var panelId = "stepper-".concat(rest.id || uniqueId, "-panel-").concat(index); return (React.createElement("div", { key: index, className: classNames('stepper-pane', { show: isActive, active: isActive, }), id: panelId, role: "tabpanel", "aria-hidden": !isActive, "aria-labelledby": stepId, "aria-live": "polite" }, stepData.content)); }))))); }); CStepper.displayName = 'CStepper'; CStepper.propTypes = { activeStepNumber: PropTypes.number, activeStepIndex: PropTypes.number, className: PropTypes.string, defaultActiveStepNumber: PropTypes.number, defaultActiveStepIndex: PropTypes.number, layout: PropTypes.oneOf(['horizontal', 'vertical']), linear: PropTypes.bool, onFinish: PropTypes.func, onReset: PropTypes.func, onStepChange: PropTypes.func, onStepValidationComplete: PropTypes.func, steps: PropTypes.arrayOf(PropTypes.oneOfType([ PropTypes.string, PropTypes.shape({ label: PropTypes.node.isRequired, content: PropTypes.node, formRef: PropTypes.object, // Check for object shape might be better }).isRequired, ])).isRequired, stepButtonLayout: PropTypes.oneOf(['horizontal', 'vertical']), validation: PropTypes.bool, }; export { CStepper }; //# sourceMappingURL=CStepper.js.map