import type { ComponentType, JSXElement } from '../../jsx'; import { getElementBounds, Group, Path, Polygon, Rect, Text } from '../../jsx'; import { BtnAdd, BtnRemove, BtnsGroup, ItemsGroup } from '../components'; import { FlexLayout } from '../layouts'; import { getColorPrimary, getPaletteColor } from '../utils'; import { registerStructure } from './registry'; import type { BaseStructureProps } from './types'; export interface SequenceStairsFrontProps extends BaseStructureProps { gap?: number; perspectiveFactor?: number; width?: number; } export const SequenceStairsFront: ComponentType = ( props, ) => { const { Title, Item, data, options, gap = 16, perspectiveFactor = 0.2, width = 720, } = props; const { title, desc, items = [] } = data; const TitleComponent = Title; const titleContent = TitleComponent ? ( ) : null; if (items.length === 0) { const btnAddElement = ; return ( {titleContent} {btnAddElement} ); } const colorPrimary = getColorPrimary(options); const btnBounds = getElementBounds(); const itemBounds = getElementBounds( , ); const connectorGap = 12; const baseConnectorWidth = Math.min(width * 0.1, 100); const bottomStepWidth = Math.min( width * 0.45, Math.max(80, width - itemBounds.width - connectorGap - baseConnectorWidth), ); const baseDepth = Math.max(24, bottomStepWidth * perspectiveFactor * 0.35); const baseStepHeight = 56; const minStepHeight = baseStepHeight * 0.7; const heightStep = items.length > 1 ? (baseStepHeight - minStepHeight) / (items.length - 1) : 0; const topMargin = Math.max(btnBounds.height + baseDepth + 20, baseDepth + 28); const centerX = bottomStepWidth / 2; const itemX = bottomStepWidth + baseConnectorWidth + connectorGap; const stepHeights = items.map( (_, index) => baseStepHeight - heightStep * index, ); const stepDepths = items.map((_, index) => Math.max(baseDepth * (1 - index * 0.05), baseDepth * 0.7), ); const stepYs: number[] = []; const depthFromTop: number[] = []; const lastIndex = items.length - 1; stepYs[lastIndex] = topMargin; depthFromTop[lastIndex] = 0; for (let i = lastIndex - 1; i >= 0; i -= 1) { depthFromTop[i] = depthFromTop[i + 1] + stepDepths[i] + gap; stepYs[i] = stepYs[i + 1] + stepDepths[i] + stepHeights[i + 1] + gap; } const totalDepthSpan = depthFromTop[0] || stepDepths[0] || baseDepth; const minStepWidth = bottomStepWidth * (0.55 + perspectiveFactor * 0.1); const widthShrink = bottomStepWidth - minStepWidth; const stepWidths = items.map((_, index) => { const ratio = totalDepthSpan === 0 ? 1 : depthFromTop[index] / totalDepthSpan; return minStepWidth + widthShrink * ratio; }); const connectorWidths = stepWidths.map((stepWidth) => { const connectorStartX = centerX + stepWidth / 2; return Math.max(0, itemX - connectorGap - connectorStartX); }); const stepElements: JSXElement[] = []; const itemElements: JSXElement[] = []; const btnElements: JSXElement[] = []; const connectorElements: JSXElement[] = []; const spineElements: JSXElement[] = []; const spineTop = 0; const spineBottom = (stepYs[0] || topMargin) + (stepHeights[0] || baseStepHeight); const arrowHeight = spineBottom - spineTop; const arrowHeadHeight = 35; const topStepWidth = stepWidths[lastIndex] || minStepWidth; const arrowTopWidth = topStepWidth * 0.8; const arrowNeckWidth = arrowTopWidth * 0.65; const arrowBottomWidth = (stepWidths[0] || bottomStepWidth) * 0.9; spineElements.push( , ); let previousCenterY = stepYs[lastIndex] + stepHeights[lastIndex] / 2; items.forEach((item, index) => { const indexes = [index]; const stepWidth = stepWidths[index]; const stepDepth = stepDepths[index]; const stepX = centerX - stepWidth / 2; const stepHeight = stepHeights[index]; const stepY = stepYs[index]; const topY = stepY - stepDepth; const rectCenterY = stepY + stepHeight / 2; const stepColor = getPaletteColor(options, indexes) || colorPrimary; stepElements.push( , , {String(index + 1).padStart(2, '0')} , ); const connectorStartX = stepX + stepWidth; const connectorEndY = rectCenterY; const connectorWidth = connectorWidths[index]; const lineEndX = connectorStartX + connectorWidth; connectorElements.push( , , ); const itemY = rectCenterY - itemBounds.height / 2; itemElements.push( , ); btnElements.push( , ); if (index === 0) { btnElements.push( , ); } else { btnElements.push( , ); } previousCenterY = rectCenterY; }); const lastCenterY = previousCenterY; const lastItemY = lastCenterY - itemBounds.height / 2; btnElements.push( , ); return ( {titleContent} {spineElements} {stepElements} {connectorElements} {itemElements} {btnElements} ); }; registerStructure('sequence-stairs-front', { component: SequenceStairsFront, composites: ['title', 'item'], });