From 1811c48d7aaed71db8f8bad1a13d3088e08483e0 Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Tue, 23 Sep 2025 17:49:59 +0200 Subject: [PATCH 01/34] feat: add fileTree shape type with inline editing and size restrictions --- src/core/model/index.ts | 4 +++- src/pods/canvas/model/inline-editable.model.ts | 4 ++++ src/pods/canvas/model/shape-size.mapper.ts | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/core/model/index.ts b/src/core/model/index.ts index 6885155c..d0056053 100644 --- a/src/core/model/index.ts +++ b/src/core/model/index.ts @@ -85,7 +85,8 @@ export type ShapeType = | 'circleLow' | 'textScribbled' | 'paragraphScribbled' - | 'fabButton'; + | 'fabButton' + | 'fileTree'; export const ShapeDisplayName: Record = { multiple: 'multiple', @@ -160,6 +161,7 @@ export const ShapeDisplayName: Record = { textScribbled: 'Text Scribbled', paragraphScribbled: 'Paragraph Scribbled', fabButton: 'Fab Button', + fileTree: 'File Tree', }; export type EditType = 'input' | 'textarea' | 'imageupload'; diff --git a/src/pods/canvas/model/inline-editable.model.ts b/src/pods/canvas/model/inline-editable.model.ts index fd465059..d271de86 100644 --- a/src/pods/canvas/model/inline-editable.model.ts +++ b/src/pods/canvas/model/inline-editable.model.ts @@ -39,6 +39,7 @@ const inlineEditableShapes = new Set([ 'modalDialog', 'gauge', 'loading-indicator', + 'fileTree', ]); // Check if a shape type allows inline editing @@ -82,6 +83,7 @@ const shapeTypesWithDefaultText = new Set([ 'modalDialog', 'loading-indicator', 'gauge', + 'fileTree', ]); // Map of ShapeTypes to their default text values @@ -115,6 +117,7 @@ const defaultTextValueMap: Partial> = { gauge: '10%', buttonBar: 'Button 1, Button 2, Button 3', tabsBar: 'Tab 1, Tab 2, Tab 3', + fileTree: 'Folder 1, Subfolder, File, Folder 2', link: 'Link', chip: 'Chip', timepickerinput: 'hh:mm', @@ -151,6 +154,7 @@ export const getShapeEditInlineType = ( case 'appBar': case 'tabsBar': case 'tooltip': + case 'fileTree': return 'textarea'; break; case 'image': diff --git a/src/pods/canvas/model/shape-size.mapper.ts b/src/pods/canvas/model/shape-size.mapper.ts index 397dddd5..3c4ea531 100644 --- a/src/pods/canvas/model/shape-size.mapper.ts +++ b/src/pods/canvas/model/shape-size.mapper.ts @@ -65,6 +65,7 @@ import { getVideoconferenceShapeSizeRestrictions, getGaugeShapeSizeRestrictions, getFabButtonShapeSizeRestrictions, + getFileTreeShapeSizeRestrictions, // other imports } from '@/common/components/mock-components/front-rich-components'; import { @@ -173,6 +174,7 @@ const shapeSizeMap: Record ShapeSizeRestrictions> = { textScribbled: getTextScribbledShapeRestrictions, paragraphScribbled: getParagraphScribbledShapeRestrictions, fabButton: getFabButtonShapeSizeRestrictions, + fileTree: getFileTreeShapeSizeRestrictions, }; export default shapeSizeMap; From 1aad3140242e9b9b22ae6631f391680edc1a947b Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Tue, 23 Sep 2025 17:53:18 +0200 Subject: [PATCH 02/34] feat: integrate FileTree into canvas rendering and gallery --- src/pods/canvas/shape-renderer/index.tsx | 3 ++ .../file-tree.renderer.tsx | 34 +++++++++++++++++++ .../simple-rich-components/index.ts | 1 + .../rich-components-gallery-data/index.ts | 4 +++ 4 files changed, 42 insertions(+) create mode 100644 src/pods/canvas/shape-renderer/simple-rich-components/file-tree.renderer.tsx diff --git a/src/pods/canvas/shape-renderer/index.tsx b/src/pods/canvas/shape-renderer/index.tsx index b591c9f9..37a59949 100644 --- a/src/pods/canvas/shape-renderer/index.tsx +++ b/src/pods/canvas/shape-renderer/index.tsx @@ -49,6 +49,7 @@ import { renderAppBar, renderLoadingIndicator, renderFabButton, + renderFileTree, } from './simple-rich-components'; import { renderDiamond, @@ -212,6 +213,8 @@ export const renderShapeComponent = ( return renderVideoconference(shape, shapeRenderedProps); case 'fabButton': return renderFabButton(shape, shapeRenderedProps); + case 'fileTree': + return renderFileTree(shape, shapeRenderedProps); case 'gauge': return renderGauge(shape, shapeRenderedProps); case 'imagePlaceholder': diff --git a/src/pods/canvas/shape-renderer/simple-rich-components/file-tree.renderer.tsx b/src/pods/canvas/shape-renderer/simple-rich-components/file-tree.renderer.tsx new file mode 100644 index 00000000..ad40072e --- /dev/null +++ b/src/pods/canvas/shape-renderer/simple-rich-components/file-tree.renderer.tsx @@ -0,0 +1,34 @@ +import { ShapeRendererProps } from '../model'; +import { ShapeModel } from '@/core/model'; +import { FileTreeShape } from '@/common/components/mock-components/front-rich-components/file-tree/file-tree'; + +export const renderFileTree = ( + shape: ShapeModel, + shapeRenderedProps: ShapeRendererProps +) => { + const { handleSelected, shapeRefs, handleDragEnd, handleTransform } = + shapeRenderedProps; + + return ( + + ); +}; diff --git a/src/pods/canvas/shape-renderer/simple-rich-components/index.ts b/src/pods/canvas/shape-renderer/simple-rich-components/index.ts index 440b7ba4..2a32d3d1 100644 --- a/src/pods/canvas/shape-renderer/simple-rich-components/index.ts +++ b/src/pods/canvas/shape-renderer/simple-rich-components/index.ts @@ -21,3 +21,4 @@ export * from './audio-player.renderer'; export * from './loading-indicator.renderer'; export * from './videoconference.renderer'; export * from './fab-button.renderer'; +export * from './file-tree.renderer'; diff --git a/src/pods/galleries/rich-components-gallery/rich-components-gallery-data/index.ts b/src/pods/galleries/rich-components-gallery/rich-components-gallery-data/index.ts index 439b4f92..b44eee90 100644 --- a/src/pods/galleries/rich-components-gallery/rich-components-gallery-data/index.ts +++ b/src/pods/galleries/rich-components-gallery/rich-components-gallery-data/index.ts @@ -40,4 +40,8 @@ export const mockRichComponentsCollection: ItemInfo[] = [ thumbnailSrc: '/rich-components/fab-button.svg', type: 'fabButton', }, + { + thumbnailSrc: '/rich-components/file-tree.svg', + type: 'fileTree', + }, ]; From 0ab052606601f37cfa01b188af5551a23964d8f5 Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Tue, 23 Sep 2025 17:53:46 +0200 Subject: [PATCH 03/34] feat: implement FileTree component --- .../file-tree/file-tree.business.ts | 3 + .../file-tree/file-tree.tsx | 129 ++++++++++++++++++ .../front-rich-components/index.ts | 1 + 3 files changed, 133 insertions(+) create mode 100644 src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts create mode 100644 src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts new file mode 100644 index 00000000..435bf3ff --- /dev/null +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts @@ -0,0 +1,3 @@ +export const joinTextContent = (text: string): string[] => { + return text.split(', '); +}; diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx new file mode 100644 index 00000000..adbabf2b --- /dev/null +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx @@ -0,0 +1,129 @@ +import { ShapeSizeRestrictions, ShapeType } from '@/core/model'; +import { forwardRef, useEffect, useState } from 'react'; +import { Group, Image, Rect, Text } from 'react-konva'; +import { ShapeProps } from '../../shape.model'; +import { fitSizeToShapeSizeRestrictions } from '@/common/utils/shapes'; +import { useGroupShapeProps } from '../../mock-components.utils'; +import { loadSvgWithFill } from '@/common/utils/svg.utils'; +import { useShapeProps } from '@/common/components/shapes/use-shape-props.hook'; +import { BASIC_SHAPE } from '../../front-components/shape.const'; +import { joinTextContent } from './file-tree.business'; + +const fileTreeShapeRestrictions: ShapeSizeRestrictions = { + minWidth: 280, + minHeight: 280, + maxWidth: -1, + maxHeight: -1, + defaultWidth: 180, + defaultHeight: 180, +}; + +interface FileTreeShapeProps extends ShapeProps { + text: string; +} + +type IconType = 'folder' | 'subfolder' | 'file'; + +interface IconState { + type: IconType; + value: HTMLImageElement | null; +} + +const shapeType: ShapeType = 'fileTree'; + +export const getFileTreeShapeSizeRestrictions = (): ShapeSizeRestrictions => + fileTreeShapeRestrictions; + +export const FileTreeShape = forwardRef( + (props, ref) => { + const { x, y, width, height, id, text, otherProps, ...shapeProps } = props; + + const treeTitles = joinTextContent(text); + + const [icons, setIcons] = useState([ + { type: 'folder', value: null }, + { type: 'subfolder', value: null }, + { type: 'file', value: null }, + { type: 'folder', value: null }, + ]); + + const restrictedSize = fitSizeToShapeSizeRestrictions( + fileTreeShapeRestrictions, + width, + height + ); + const { width: restrictedWidth, height: restrictedHeight } = restrictedSize; + + const iconWidth = 50; + const elementHeight = 60; + const fileX = 50; + + const { stroke, strokeStyle, fill, textColor, borderRadius } = + useShapeProps(otherProps, BASIC_SHAPE); + + const commonGroupProps = useGroupShapeProps( + props, + restrictedSize, + shapeType, + ref + ); + + useEffect(() => { + Promise.all([ + loadSvgWithFill('/icons/folder.svg', 'black'), + loadSvgWithFill('/icons/open.svg', 'black'), + loadSvgWithFill('/icons/new.svg', 'black'), + ]).then(([folder, subfolder, file]) => { + setIcons([ + { type: 'folder', value: folder }, + { type: 'subfolder', value: subfolder }, + { type: 'file', value: file }, + { type: 'folder', value: folder }, + ]); + }); + }, []); + + return ( + + {/* Container */} + + + {treeTitles.map((title, index) => ( + + {icons[index]?.value && ( + + )} + + + ))} + + ); + } +); diff --git a/src/common/components/mock-components/front-rich-components/index.ts b/src/common/components/mock-components/front-rich-components/index.ts index 525b3d37..b1e5f180 100644 --- a/src/common/components/mock-components/front-rich-components/index.ts +++ b/src/common/components/mock-components/front-rich-components/index.ts @@ -19,3 +19,4 @@ export * from './videoconference'; export * from './togglelightdark-shape'; export * from './gauge/gauge'; export * from './fab-button/fab-button'; +export * from './file-tree/file-tree'; From 997b8a053f2ac20cc57f1fedecff7a5b81b7c827 Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Wed, 24 Sep 2025 12:10:12 +0200 Subject: [PATCH 04/34] fix(file-tree): Fix selection box size by constraining text elements within bounds --- .../file-tree/file-tree.tsx | 49 ++++++++++++++----- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx index adbabf2b..d28638ba 100644 --- a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx @@ -10,12 +10,12 @@ import { BASIC_SHAPE } from '../../front-components/shape.const'; import { joinTextContent } from './file-tree.business'; const fileTreeShapeRestrictions: ShapeSizeRestrictions = { - minWidth: 280, + minWidth: 220, minHeight: 280, maxWidth: -1, maxHeight: -1, - defaultWidth: 180, - defaultHeight: 180, + defaultWidth: 280, + defaultHeight: 280, }; interface FileTreeShapeProps extends ShapeProps { @@ -36,7 +36,17 @@ export const getFileTreeShapeSizeRestrictions = (): ShapeSizeRestrictions => export const FileTreeShape = forwardRef( (props, ref) => { - const { x, y, width, height, id, text, otherProps, ...shapeProps } = props; + const { + x, + y, + width, + height, + id, + onSelected, + text, + otherProps, + ...shapeProps + } = props; const treeTitles = joinTextContent(text); @@ -56,7 +66,16 @@ export const FileTreeShape = forwardRef( const iconWidth = 50; const elementHeight = 60; - const fileX = 50; + const paddingLeft = 40; + const paddingTop = 30; + const fileX = 50 + paddingLeft; + const iconTextSpacing = 10; + + const folderTextX = iconWidth + iconTextSpacing + paddingLeft; + const fileTextX = fileX + iconWidth + iconTextSpacing; + + const folderAvailableWidth = restrictedWidth - folderTextX; + const fileAvailableWidth = restrictedWidth - fileTextX; const { stroke, strokeStyle, fill, textColor, borderRadius } = useShapeProps(otherProps, BASIC_SHAPE); @@ -87,9 +106,9 @@ export const FileTreeShape = forwardRef( {/* Container */} ( {icons[index]?.value && ( )} Date: Wed, 24 Sep 2025 12:33:36 +0200 Subject: [PATCH 05/34] fix(file-tree): Add right padding to prevent text overflow --- .../front-rich-components/file-tree/file-tree.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx index d28638ba..d710768e 100644 --- a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx @@ -66,16 +66,16 @@ export const FileTreeShape = forwardRef( const iconWidth = 50; const elementHeight = 60; - const paddingLeft = 40; + const paddingX = 40; const paddingTop = 30; - const fileX = 50 + paddingLeft; + const fileX = 50 + paddingX; const iconTextSpacing = 10; - const folderTextX = iconWidth + iconTextSpacing + paddingLeft; + const folderTextX = iconWidth + iconTextSpacing + paddingX; const fileTextX = fileX + iconWidth + iconTextSpacing; - const folderAvailableWidth = restrictedWidth - folderTextX; - const fileAvailableWidth = restrictedWidth - fileTextX; + const folderAvailableWidth = restrictedWidth - folderTextX - paddingX; + const fileAvailableWidth = restrictedWidth - fileTextX - paddingX; const { stroke, strokeStyle, fill, textColor, borderRadius } = useShapeProps(otherProps, BASIC_SHAPE); @@ -122,7 +122,7 @@ export const FileTreeShape = forwardRef( {icons[index]?.value && ( Date: Wed, 24 Sep 2025 14:30:45 +0200 Subject: [PATCH 06/34] feat(file-tree): Add file-tree component to shape other props configuration --- src/pods/canvas/model/shape-other-props.utils.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/pods/canvas/model/shape-other-props.utils.ts b/src/pods/canvas/model/shape-other-props.utils.ts index fe238aea..35fea654 100644 --- a/src/pods/canvas/model/shape-other-props.utils.ts +++ b/src/pods/canvas/model/shape-other-props.utils.ts @@ -75,6 +75,14 @@ export const generateDefaultOtherProps = ( stroke: '#808080', textColor: BASIC_SHAPE.DEFAULT_FILL_TEXT, }; + case 'fileTree': + return { + stroke: BASIC_SHAPE.DEFAULT_STROKE_COLOR, + backgroundColor: BASIC_SHAPE.DEFAULT_FILL_BACKGROUND, + textColor: BASIC_SHAPE.DEFAULT_FILL_TEXT, + strokeStyle: [], + borderRadius: `${BASIC_SHAPE.DEFAULT_CORNER_RADIUS}`, + }; case 'fabButton': return { icon: { From 9f44b4ce60d4dc4bb28d48ec61395ff9aac54ee5 Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Wed, 24 Sep 2025 14:31:14 +0200 Subject: [PATCH 07/34] feat(file-tree): Sync icon colors with stroke color for visual consistency --- .../front-rich-components/file-tree/file-tree.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx index d710768e..f44ba604 100644 --- a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx @@ -89,9 +89,9 @@ export const FileTreeShape = forwardRef( useEffect(() => { Promise.all([ - loadSvgWithFill('/icons/folder.svg', 'black'), - loadSvgWithFill('/icons/open.svg', 'black'), - loadSvgWithFill('/icons/new.svg', 'black'), + loadSvgWithFill('/icons/folder.svg', stroke), + loadSvgWithFill('/icons/open.svg', stroke), + loadSvgWithFill('/icons/new.svg', stroke), ]).then(([folder, subfolder, file]) => { setIcons([ { type: 'folder', value: folder }, @@ -100,7 +100,7 @@ export const FileTreeShape = forwardRef( { type: 'folder', value: folder }, ]); }); - }, []); + }, [stroke]); return ( From f5f3b80f51f6e43cc0f19d75ec4793b53d13ca44 Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Wed, 24 Sep 2025 15:07:40 +0200 Subject: [PATCH 08/34] feat(file-tree): Enable dynamic addition of file tree elements via symbol parsing --- .../file-tree/file-tree.business.ts | 45 ++++++++++++++++ .../file-tree/file-tree.tsx | 51 ++++++++----------- 2 files changed, 66 insertions(+), 30 deletions(-) diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts index 435bf3ff..a3fdd087 100644 --- a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts @@ -1,3 +1,48 @@ export const joinTextContent = (text: string): string[] => { return text.split(', '); }; + +// Symbol -> + Folder - Subfolder * File +export interface FileTreeItem { + type: 'folder' | 'subfolder' | 'file'; + text: string; +} + +export const parseFileTreeText = (text: string): FileTreeItem[] => { + return text + .split(',') + .map(line => { + const trimmed = line.trim(); + + if (trimmed === '') return null; + + // Detect symbol + if (trimmed.startsWith('+ ')) { + return { + type: 'folder', + text: trimmed.substring(2).trim(), + }; + } + + if (trimmed.startsWith('- ')) { + return { + type: 'subfolder', + text: trimmed.substring(2).trim(), + }; + } + + if (trimmed.startsWith('* ')) { + return { + type: 'file', + text: trimmed.substring(2).trim(), + }; + } + + // No symbol: will be treated as a folder + return { + type: 'folder', + text: trimmed, + }; + }) + .filter((item): item is FileTreeItem => item !== null); +}; diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx index f44ba604..82315376 100644 --- a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx @@ -7,7 +7,7 @@ import { useGroupShapeProps } from '../../mock-components.utils'; import { loadSvgWithFill } from '@/common/utils/svg.utils'; import { useShapeProps } from '@/common/components/shapes/use-shape-props.hook'; import { BASIC_SHAPE } from '../../front-components/shape.const'; -import { joinTextContent } from './file-tree.business'; +import { parseFileTreeText } from './file-tree.business'; const fileTreeShapeRestrictions: ShapeSizeRestrictions = { minWidth: 220, @@ -22,13 +22,6 @@ interface FileTreeShapeProps extends ShapeProps { text: string; } -type IconType = 'folder' | 'subfolder' | 'file'; - -interface IconState { - type: IconType; - value: HTMLImageElement | null; -} - const shapeType: ShapeType = 'fileTree'; export const getFileTreeShapeSizeRestrictions = (): ShapeSizeRestrictions => @@ -48,14 +41,15 @@ export const FileTreeShape = forwardRef( ...shapeProps } = props; - const treeTitles = joinTextContent(text); + const treeItems = parseFileTreeText(text); - const [icons, setIcons] = useState([ - { type: 'folder', value: null }, - { type: 'subfolder', value: null }, - { type: 'file', value: null }, - { type: 'folder', value: null }, - ]); + const [icons, setIcons] = useState>( + { + folder: null, + subfolder: null, + file: null, + } + ); const restrictedSize = fitSizeToShapeSizeRestrictions( fileTreeShapeRestrictions, @@ -93,12 +87,11 @@ export const FileTreeShape = forwardRef( loadSvgWithFill('/icons/open.svg', stroke), loadSvgWithFill('/icons/new.svg', stroke), ]).then(([folder, subfolder, file]) => { - setIcons([ - { type: 'folder', value: folder }, - { type: 'subfolder', value: subfolder }, - { type: 'file', value: file }, - { type: 'folder', value: folder }, - ]); + setIcons({ + folder, + subfolder, + file, + }); }); }, [stroke]); @@ -117,25 +110,23 @@ export const FileTreeShape = forwardRef( cornerRadius={borderRadius} /> - {treeTitles.map((title, index) => ( + {treeItems.map((item, index) => ( - {icons[index]?.value && ( + {icons[item.type] && ( )} Date: Wed, 24 Sep 2025 15:39:21 +0200 Subject: [PATCH 09/34] update(file-tree): update file tree default text to meet with dynamic symbol parsing --- src/pods/canvas/model/inline-editable.model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pods/canvas/model/inline-editable.model.ts b/src/pods/canvas/model/inline-editable.model.ts index d271de86..83413748 100644 --- a/src/pods/canvas/model/inline-editable.model.ts +++ b/src/pods/canvas/model/inline-editable.model.ts @@ -117,7 +117,7 @@ const defaultTextValueMap: Partial> = { gauge: '10%', buttonBar: 'Button 1, Button 2, Button 3', tabsBar: 'Tab 1, Tab 2, Tab 3', - fileTree: 'Folder 1, Subfolder, File, Folder 2', + fileTree: '+ Folder 1, - Subfolder, * File, + Folder 2', link: 'Link', chip: 'Chip', timepickerinput: 'hh:mm', From 4a020ade1c791275adb253b5b76972cd939d998e Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Wed, 24 Sep 2025 15:40:00 +0200 Subject: [PATCH 10/34] feat(file-tree): Implement dynamic height adaptation based on content --- .../file-tree/file-tree.business.ts | 40 +++++++++++++++++++ .../file-tree/file-tree.tsx | 31 ++++++++------ 2 files changed, 58 insertions(+), 13 deletions(-) diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts index a3fdd087..bf9b367c 100644 --- a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts @@ -1,3 +1,6 @@ +import { fitSizeToShapeSizeRestrictions } from '@/common/utils/shapes'; +import { ShapeSizeRestrictions, Size } from '@/core/model'; + export const joinTextContent = (text: string): string[] => { return text.split(', '); }; @@ -8,6 +11,14 @@ export interface FileTreeItem { text: string; } +interface FileTreeDynamicSizeParams { + width: number; + height: number; + elementHeight: number; + paddingY: number; + baseRestrictions: ShapeSizeRestrictions; +} + export const parseFileTreeText = (text: string): FileTreeItem[] => { return text .split(',') @@ -46,3 +57,32 @@ export const parseFileTreeText = (text: string): FileTreeItem[] => { }) .filter((item): item is FileTreeItem => item !== null); }; + +export const calculateFileTreeDynamicSize = ( + treeItems: FileTreeItem[], + params: FileTreeDynamicSizeParams +): Size => { + const { width, height, elementHeight, paddingY, baseRestrictions } = params; + + // Calculate minimum height required based on content + const minContentHeight = treeItems.length * elementHeight + paddingY * 2; + + // Create dynamic constraints with adaptive minimum height + const dynamicRestrictions: ShapeSizeRestrictions = { + ...baseRestrictions, + minHeight: minContentHeight, + defaultHeight: Math.max( + baseRestrictions.defaultHeight || 280, + minContentHeight + ), + }; + + // Use the user's current height, but ensure that it is not less than minContentHeight. + const finalHeight = Math.max(height, minContentHeight); + + return fitSizeToShapeSizeRestrictions( + dynamicRestrictions, + width, + finalHeight + ); +}; diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx index 82315376..d18d8387 100644 --- a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx @@ -2,12 +2,14 @@ import { ShapeSizeRestrictions, ShapeType } from '@/core/model'; import { forwardRef, useEffect, useState } from 'react'; import { Group, Image, Rect, Text } from 'react-konva'; import { ShapeProps } from '../../shape.model'; -import { fitSizeToShapeSizeRestrictions } from '@/common/utils/shapes'; import { useGroupShapeProps } from '../../mock-components.utils'; import { loadSvgWithFill } from '@/common/utils/svg.utils'; import { useShapeProps } from '@/common/components/shapes/use-shape-props.hook'; import { BASIC_SHAPE } from '../../front-components/shape.const'; -import { parseFileTreeText } from './file-tree.business'; +import { + calculateFileTreeDynamicSize, + parseFileTreeText, +} from './file-tree.business'; const fileTreeShapeRestrictions: ShapeSizeRestrictions = { minWidth: 220, @@ -51,20 +53,23 @@ export const FileTreeShape = forwardRef( } ); - const restrictedSize = fitSizeToShapeSizeRestrictions( - fileTreeShapeRestrictions, - width, - height - ); - const { width: restrictedWidth, height: restrictedHeight } = restrictedSize; - const iconWidth = 50; const elementHeight = 60; const paddingX = 40; - const paddingTop = 30; + const paddingY = 30; const fileX = 50 + paddingX; const iconTextSpacing = 10; + const restrictedSize = calculateFileTreeDynamicSize(treeItems, { + width, + height, + elementHeight, + paddingY, + baseRestrictions: fileTreeShapeRestrictions, + }); + + const { width: restrictedWidth, height: restrictedHeight } = restrictedSize; + const folderTextX = iconWidth + iconTextSpacing + paddingX; const fileTextX = fileX + iconWidth + iconTextSpacing; @@ -102,7 +107,7 @@ export const FileTreeShape = forwardRef( x={0} y={0} width={restrictedWidth} - height={restrictedHeight + 20} + height={restrictedHeight} stroke={stroke} strokeWidth={2} fill={fill} @@ -116,14 +121,14 @@ export const FileTreeShape = forwardRef( )} Date: Wed, 24 Sep 2025 16:37:08 +0200 Subject: [PATCH 11/34] create file tree svg --- public/rich-components/file-tree.svg | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 public/rich-components/file-tree.svg diff --git a/public/rich-components/file-tree.svg b/public/rich-components/file-tree.svg new file mode 100644 index 00000000..d1bd87d2 --- /dev/null +++ b/public/rich-components/file-tree.svg @@ -0,0 +1,28 @@ + + + + + + + + + Folder 1 + + + + + + Subfolder + + + + + + File + + + + + + Folder 2 + From 0c5abc01295b418df52a0485d90597f3fc24706d Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Fri, 26 Sep 2025 17:01:25 +0200 Subject: [PATCH 12/34] update file tree svg --- public/rich-components/file-tree.svg | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/public/rich-components/file-tree.svg b/public/rich-components/file-tree.svg index d1bd87d2..0104fcfa 100644 --- a/public/rich-components/file-tree.svg +++ b/public/rich-components/file-tree.svg @@ -6,23 +6,23 @@ - Folder 1 + Folder 1 - + - Subfolder + Subfolder - + - File + File - Folder 2 + Folder 2 From 933ff768e596ce7b7fed9207d43018d5fd4016a8 Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Fri, 26 Sep 2025 17:02:33 +0200 Subject: [PATCH 13/34] update default file tree text to multiline format with indentation --- src/pods/canvas/model/inline-editable.model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pods/canvas/model/inline-editable.model.ts b/src/pods/canvas/model/inline-editable.model.ts index 83413748..1ce46475 100644 --- a/src/pods/canvas/model/inline-editable.model.ts +++ b/src/pods/canvas/model/inline-editable.model.ts @@ -117,7 +117,7 @@ const defaultTextValueMap: Partial> = { gauge: '10%', buttonBar: 'Button 1, Button 2, Button 3', tabsBar: 'Tab 1, Tab 2, Tab 3', - fileTree: '+ Folder 1, - Subfolder, * File, + Folder 2', + fileTree: '+ Folder 1\n - Subfolder\n * File\n+ Folder 2\n', link: 'Link', chip: 'Chip', timepickerinput: 'hh:mm', From 2394d44284ad7e2499e5e2d1dc7b254dcb486f41 Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Fri, 26 Sep 2025 17:04:05 +0200 Subject: [PATCH 14/34] feat(file-tree): implement multiline parsing with indentation support, create helper functions for position calculation taking in acount indentation --- .../file-tree/file-tree.business.ts | 11 +++++- .../file-tree/file-tree.tsx | 36 ++++++++++++------- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts index bf9b367c..e5c5c13b 100644 --- a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts @@ -6,9 +6,11 @@ export const joinTextContent = (text: string): string[] => { }; // Symbol -> + Folder - Subfolder * File +// Level -> Level 0: no indentation in Folder / Level 1: 1 indentation (2 spaces) in Subfolder / Level 2: 2 (4 spaces) indentations in File export interface FileTreeItem { type: 'folder' | 'subfolder' | 'file'; text: string; + level: number; } interface FileTreeDynamicSizeParams { @@ -21,8 +23,11 @@ interface FileTreeDynamicSizeParams { export const parseFileTreeText = (text: string): FileTreeItem[] => { return text - .split(',') + .split('\n') .map(line => { + // First detect indentation + const indentMatch = line.match(/^(\s*)/); + const level = indentMatch ? Math.floor(indentMatch[1].length / 2) : 0; const trimmed = line.trim(); if (trimmed === '') return null; @@ -32,6 +37,7 @@ export const parseFileTreeText = (text: string): FileTreeItem[] => { return { type: 'folder', text: trimmed.substring(2).trim(), + level: level, }; } @@ -39,6 +45,7 @@ export const parseFileTreeText = (text: string): FileTreeItem[] => { return { type: 'subfolder', text: trimmed.substring(2).trim(), + level: level, }; } @@ -46,6 +53,7 @@ export const parseFileTreeText = (text: string): FileTreeItem[] => { return { type: 'file', text: trimmed.substring(2).trim(), + level: level, }; } @@ -53,6 +61,7 @@ export const parseFileTreeText = (text: string): FileTreeItem[] => { return { type: 'folder', text: trimmed, + level: level, }; }) .filter((item): item is FileTreeItem => item !== null); diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx index d18d8387..bcf2c397 100644 --- a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx @@ -8,12 +8,13 @@ import { useShapeProps } from '@/common/components/shapes/use-shape-props.hook'; import { BASIC_SHAPE } from '../../front-components/shape.const'; import { calculateFileTreeDynamicSize, + FileTreeItem, parseFileTreeText, } from './file-tree.business'; const fileTreeShapeRestrictions: ShapeSizeRestrictions = { minWidth: 220, - minHeight: 280, + minHeight: 180, maxWidth: -1, maxHeight: -1, defaultWidth: 280, @@ -57,8 +58,8 @@ export const FileTreeShape = forwardRef( const elementHeight = 60; const paddingX = 40; const paddingY = 30; - const fileX = 50 + paddingX; const iconTextSpacing = 10; + const indentationStep = 10; const restrictedSize = calculateFileTreeDynamicSize(treeItems, { width, @@ -70,12 +71,6 @@ export const FileTreeShape = forwardRef( const { width: restrictedWidth, height: restrictedHeight } = restrictedSize; - const folderTextX = iconWidth + iconTextSpacing + paddingX; - const fileTextX = fileX + iconWidth + iconTextSpacing; - - const folderAvailableWidth = restrictedWidth - folderTextX - paddingX; - const fileAvailableWidth = restrictedWidth - fileTextX - paddingX; - const { stroke, strokeStyle, fill, textColor, borderRadius } = useShapeProps(otherProps, BASIC_SHAPE); @@ -86,6 +81,23 @@ export const FileTreeShape = forwardRef( ref ); + // Helper functions for position calculations + const calculateIconX = (item: FileTreeItem) => { + return ( + paddingX + + item.level * indentationStep + + (item.type === 'file' ? 40 : 0) + ); + }; + + const calculateTextX = (item: FileTreeItem) => { + return calculateIconX(item) + iconWidth + iconTextSpacing; + }; + + const calculateAvailableWidth = (item: FileTreeItem) => { + return restrictedWidth - calculateTextX(item) - paddingX; + }; + useEffect(() => { Promise.all([ loadSvgWithFill('/icons/folder.svg', stroke), @@ -120,19 +132,17 @@ export const FileTreeShape = forwardRef( {icons[item.type] && ( )} Date: Mon, 29 Sep 2025 12:43:48 +0200 Subject: [PATCH 15/34] feat(file-tree): improve visual spacing in default text, 1 extra space for subfolder and file --- src/pods/canvas/model/inline-editable.model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pods/canvas/model/inline-editable.model.ts b/src/pods/canvas/model/inline-editable.model.ts index 1ce46475..bc99eecc 100644 --- a/src/pods/canvas/model/inline-editable.model.ts +++ b/src/pods/canvas/model/inline-editable.model.ts @@ -117,7 +117,7 @@ const defaultTextValueMap: Partial> = { gauge: '10%', buttonBar: 'Button 1, Button 2, Button 3', tabsBar: 'Tab 1, Tab 2, Tab 3', - fileTree: '+ Folder 1\n - Subfolder\n * File\n+ Folder 2\n', + fileTree: '+ Folder 1\n - Subfolder\n * File\n+ Folder 2\n', link: 'Link', chip: 'Chip', timepickerinput: 'hh:mm', From 4c24737c165e1f8c895de30a1382d85e22d4b666 Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Mon, 29 Sep 2025 16:09:59 +0200 Subject: [PATCH 16/34] feat(file-tree): update level calculation to match new spacing and adjust automatic height calculation to always fit content --- .../file-tree/file-tree.business.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts index e5c5c13b..9284cf53 100644 --- a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts @@ -6,7 +6,7 @@ export const joinTextContent = (text: string): string[] => { }; // Symbol -> + Folder - Subfolder * File -// Level -> Level 0: no indentation in Folder / Level 1: 1 indentation (2 spaces) in Subfolder / Level 2: 2 (4 spaces) indentations in File +// Level -> Level 0: no indentation in Folder / Level 1: 1 indentation (3 spaces) in Subfolder / Level 2: 2 indentations (6 spaces) in File export interface FileTreeItem { type: 'folder' | 'subfolder' | 'file'; text: string; @@ -15,7 +15,6 @@ export interface FileTreeItem { interface FileTreeDynamicSizeParams { width: number; - height: number; elementHeight: number; paddingY: number; baseRestrictions: ShapeSizeRestrictions; @@ -27,7 +26,7 @@ export const parseFileTreeText = (text: string): FileTreeItem[] => { .map(line => { // First detect indentation const indentMatch = line.match(/^(\s*)/); - const level = indentMatch ? Math.floor(indentMatch[1].length / 2) : 0; + const level = indentMatch ? Math.floor(indentMatch[1].length / 3) : 0; const trimmed = line.trim(); if (trimmed === '') return null; @@ -71,7 +70,7 @@ export const calculateFileTreeDynamicSize = ( treeItems: FileTreeItem[], params: FileTreeDynamicSizeParams ): Size => { - const { width, height, elementHeight, paddingY, baseRestrictions } = params; + const { width, elementHeight, paddingY, baseRestrictions } = params; // Calculate minimum height required based on content const minContentHeight = treeItems.length * elementHeight + paddingY * 2; @@ -81,13 +80,12 @@ export const calculateFileTreeDynamicSize = ( ...baseRestrictions, minHeight: minContentHeight, defaultHeight: Math.max( - baseRestrictions.defaultHeight || 280, + baseRestrictions.defaultHeight || 200, minContentHeight ), }; - // Use the user's current height, but ensure that it is not less than minContentHeight. - const finalHeight = Math.max(height, minContentHeight); + const finalHeight = minContentHeight; return fitSizeToShapeSizeRestrictions( dynamicRestrictions, From f7c6f0e878619c239e3fd0bae7cb7dcbf0bf628d Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Mon, 29 Sep 2025 21:52:57 +0200 Subject: [PATCH 17/34] feat: add generic size types and core model updates --- src/core/model/index.ts | 7 +++++++ src/pods/properties/properties.model.ts | 1 + 2 files changed, 8 insertions(+) diff --git a/src/core/model/index.ts b/src/core/model/index.ts index d0056053..bf189305 100644 --- a/src/core/model/index.ts +++ b/src/core/model/index.ts @@ -191,6 +191,12 @@ export interface IconInfo { export type IconSize = 'XS' | 'S' | 'M' | 'L' | 'XL'; +export type ElementSize = 'XS' | 'S' | 'M' | 'L' | 'XL'; + +export interface SizeConfig { + availableSizes: ElementSize[]; +} + export interface OtherProps { stroke?: string; strokeStyle?: number[]; @@ -204,6 +210,7 @@ export interface OtherProps { checked?: boolean; icon?: IconInfo; iconSize?: IconSize; + size?: ElementSize; imageSrc?: string; imageBlackAndWhite?: boolean; progress?: string; diff --git a/src/pods/properties/properties.model.ts b/src/pods/properties/properties.model.ts index 78f03746..2a91c25a 100644 --- a/src/pods/properties/properties.model.ts +++ b/src/pods/properties/properties.model.ts @@ -13,6 +13,7 @@ export const multiSelectEnabledProperties: (keyof OtherProps)[] = [ 'checked', 'icon', 'iconSize', + 'size', 'imageBlackAndWhite', 'progress', 'borderRadius', From 13ae1825cc44eafe19c0395738dc32a9faadb52d Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Mon, 29 Sep 2025 21:54:31 +0200 Subject: [PATCH 18/34] feat: add size configuration system for icon and fileTree shapes --- .../canvas/model/shape-other-props.utils.ts | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/pods/canvas/model/shape-other-props.utils.ts b/src/pods/canvas/model/shape-other-props.utils.ts index 35fea654..f2c1007f 100644 --- a/src/pods/canvas/model/shape-other-props.utils.ts +++ b/src/pods/canvas/model/shape-other-props.utils.ts @@ -5,7 +5,7 @@ import { LINK_SHAPE, LOW_WIREFRAME_SHAPE, } from '@/common/components/mock-components/front-components/shape.const'; -import { ShapeType, OtherProps } from '@/core/model'; +import { ShapeType, OtherProps, SizeConfig } from '@/core/model'; export const generateDefaultOtherProps = ( shapeType: ShapeType @@ -82,6 +82,7 @@ export const generateDefaultOtherProps = ( textColor: BASIC_SHAPE.DEFAULT_FILL_TEXT, strokeStyle: [], borderRadius: `${BASIC_SHAPE.DEFAULT_CORNER_RADIUS}`, + size: 'S', }; case 'fabButton': return { @@ -290,3 +291,20 @@ export const generateDefaultOtherProps = ( return undefined; } }; + +export const getSizeConfigForShape = ( + shapeType: ShapeType +): SizeConfig | undefined => { + switch (shapeType) { + case 'icon': + return { + availableSizes: ['XS', 'S', 'M', 'L', 'XL'], + }; + case 'fileTree': + return { + availableSizes: ['XS', 'S'], + }; + default: + return undefined; + } +}; From 47c3667f739ee6b93fe770243e4da1ac8b608561 Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Mon, 29 Sep 2025 21:56:58 +0200 Subject: [PATCH 19/34] feat: create generic SelectSizeV2 component that manages both icon and file tree shapes, add selectSizeV2 to properties pod --- .../components/select-size/index.ts | 1 + .../select-size/select-size-v2/index.ts | 1 + .../select-size-v2.component.module.css | 22 ++++++++++ .../select-size-v2.component.tsx | 40 +++++++++++++++++++ .../select-size-v2/select-size.utils.ts | 11 +++++ src/pods/properties/properties.pod.tsx | 25 +++++++++++- 6 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 src/pods/properties/components/select-size/select-size-v2/index.ts create mode 100644 src/pods/properties/components/select-size/select-size-v2/select-size-v2.component.module.css create mode 100644 src/pods/properties/components/select-size/select-size-v2/select-size-v2.component.tsx create mode 100644 src/pods/properties/components/select-size/select-size-v2/select-size.utils.ts diff --git a/src/pods/properties/components/select-size/index.ts b/src/pods/properties/components/select-size/index.ts index 7137538b..55e7c20a 100644 --- a/src/pods/properties/components/select-size/index.ts +++ b/src/pods/properties/components/select-size/index.ts @@ -1 +1,2 @@ export * from './select-size.component'; +export * from './select-size-v2'; diff --git a/src/pods/properties/components/select-size/select-size-v2/index.ts b/src/pods/properties/components/select-size/select-size-v2/index.ts new file mode 100644 index 00000000..83d8adc2 --- /dev/null +++ b/src/pods/properties/components/select-size/select-size-v2/index.ts @@ -0,0 +1 @@ +export * from './select-size-v2.component'; diff --git a/src/pods/properties/components/select-size/select-size-v2/select-size-v2.component.module.css b/src/pods/properties/components/select-size/select-size-v2/select-size-v2.component.module.css new file mode 100644 index 00000000..8954bc08 --- /dev/null +++ b/src/pods/properties/components/select-size/select-size-v2/select-size-v2.component.module.css @@ -0,0 +1,22 @@ +.container { + display: flex; + gap: 0.5em; + align-items: center; + padding: var(--space-xs) var(--space-md); + border-bottom: 1px solid var(--primary-300); +} + +.container :first-child { + flex: 1; +} + +.range { + cursor: pointer; + width: 60px; + height: var(--space-lg); +} + +.size { + width: 10px; + padding: 0 var(--space-md) 0 var(--space-s); +} diff --git a/src/pods/properties/components/select-size/select-size-v2/select-size-v2.component.tsx b/src/pods/properties/components/select-size/select-size-v2/select-size-v2.component.tsx new file mode 100644 index 00000000..ee9aaa0e --- /dev/null +++ b/src/pods/properties/components/select-size/select-size-v2/select-size-v2.component.tsx @@ -0,0 +1,40 @@ +import { ElementSize, ShapeType } from '@/core/model'; +import classes from './select-size-v2.component.module.css'; +import { sizeToStep, stepToSize } from './select-size.utils'; +import { getSizeConfigForShape } from '@/pods/canvas/model/shape-other-props.utils'; + +interface Props { + label: string; + shapeType?: ShapeType; + value: string; + onChange: (value: ElementSize) => void; +} + +export const SelectSizeV2: React.FC = ({ + label, + shapeType, + value, + onChange, +}) => { + if (!shapeType) return null; + + const config = getSizeConfigForShape(shapeType); + + if (!config) return null; + + return ( +
+

{label}

+ onChange(stepToSize(config, e.target.value))} + className={classes.range} + /> +

{value}

+
+ ); +}; diff --git a/src/pods/properties/components/select-size/select-size-v2/select-size.utils.ts b/src/pods/properties/components/select-size/select-size-v2/select-size.utils.ts new file mode 100644 index 00000000..adb16c3e --- /dev/null +++ b/src/pods/properties/components/select-size/select-size-v2/select-size.utils.ts @@ -0,0 +1,11 @@ +import { ElementSize, SizeConfig } from '@/core/model'; + +export const sizeToStep = (config: SizeConfig, size: string): string => { + const index = config.availableSizes.indexOf(size as ElementSize); + return index >= 0 ? (index + 1).toString() : '1'; +}; + +export const stepToSize = (config: SizeConfig, step: string): ElementSize => { + const index = parseInt(step) - 1; + return config.availableSizes[index] || config.availableSizes[0]; +}; diff --git a/src/pods/properties/properties.pod.tsx b/src/pods/properties/properties.pod.tsx index fa49293f..9cc41f5d 100644 --- a/src/pods/properties/properties.pod.tsx +++ b/src/pods/properties/properties.pod.tsx @@ -3,7 +3,13 @@ import classes from './properties.pod.module.css'; import { ZIndexOptions } from './components/zindex/zindex-option.component'; import { ColorPicker } from './components/color-picker/color-picker.component'; import { Checked } from './components/checked/checked.component'; -import { SelectSize, SelectIcon, BorderRadius, Disabled } from './components'; +import { + SelectSize, + SelectIcon, + BorderRadius, + Disabled, + SelectSizeV2, +} from './components'; import { StrokeStyle } from './components/stroke-style/stroke.style.component'; import { ImageSrc } from './components/image-src/image-selector.component'; import { ImageBlackAndWhite } from './components/image-black-and-white/image-black-and-white-selector.component'; @@ -145,6 +151,23 @@ export const PropertiesPod = () => { } /> + + + + updateOtherPropsOnSelected('size', size, isMultipleSelection) + } + /> + + Date: Mon, 29 Sep 2025 22:14:17 +0200 Subject: [PATCH 20/34] feat(file-tree): adjust shape restrictions, implement size property that manages font and icon dimensions --- .../file-tree/file-tree.business.ts | 29 ++++++++++++++++- .../file-tree/file-tree.tsx | 32 +++++++++---------- 2 files changed, 43 insertions(+), 18 deletions(-) diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts index 9284cf53..bfaaa0d8 100644 --- a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts @@ -1,5 +1,32 @@ import { fitSizeToShapeSizeRestrictions } from '@/common/utils/shapes'; -import { ShapeSizeRestrictions, Size } from '@/core/model'; +import { ElementSize, ShapeSizeRestrictions, Size } from '@/core/model'; +import { FONT_SIZE_VALUES } from '../../front-components/shape.const'; + +export const getFileTreeSizeValues = (size?: ElementSize) => { + switch (size) { + case 'XS': + return { + fontSize: FONT_SIZE_VALUES.SMALLTEXT, + iconWidth: 40, + elementHeight: 55, + size: 'XS', + }; + case 'S': + return { + fontSize: FONT_SIZE_VALUES.NORMALTEXT, + iconWidth: 50, + elementHeight: 60, + size: 'S', + }; + default: + return { + fontSize: FONT_SIZE_VALUES.NORMALTEXT, + iconWidth: 50, + elementHeight: 60, + size: 'S', + }; + } +}; export const joinTextContent = (text: string): string[] => { return text.split(', '); diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx index bcf2c397..72197bd1 100644 --- a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx @@ -9,16 +9,17 @@ import { BASIC_SHAPE } from '../../front-components/shape.const'; import { calculateFileTreeDynamicSize, FileTreeItem, + getFileTreeSizeValues, parseFileTreeText, } from './file-tree.business'; const fileTreeShapeRestrictions: ShapeSizeRestrictions = { - minWidth: 220, - minHeight: 180, + minWidth: 200, + minHeight: 120, maxWidth: -1, maxHeight: -1, - defaultWidth: 280, - defaultHeight: 280, + defaultWidth: 230, + defaultHeight: 180, }; interface FileTreeShapeProps extends ShapeProps { @@ -54,16 +55,17 @@ export const FileTreeShape = forwardRef( } ); - const iconWidth = 50; - const elementHeight = 60; - const paddingX = 40; - const paddingY = 30; + const { fontSize, iconWidth, elementHeight, size } = getFileTreeSizeValues( + otherProps?.size + ); + + const paddingX = 30; + const paddingY = 25; const iconTextSpacing = 10; - const indentationStep = 10; + const indentationStep = 25; const restrictedSize = calculateFileTreeDynamicSize(treeItems, { width, - height, elementHeight, paddingY, baseRestrictions: fileTreeShapeRestrictions, @@ -83,11 +85,7 @@ export const FileTreeShape = forwardRef( // Helper functions for position calculations const calculateIconX = (item: FileTreeItem) => { - return ( - paddingX + - item.level * indentationStep + - (item.type === 'file' ? 40 : 0) - ); + return paddingX + item.level * indentationStep; }; const calculateTextX = (item: FileTreeItem) => { @@ -133,7 +131,7 @@ export const FileTreeShape = forwardRef( @@ -145,7 +143,7 @@ export const FileTreeShape = forwardRef( width={calculateAvailableWidth(item)} height={elementHeight} fontFamily={BASIC_SHAPE.DEFAULT_FONT_FAMILY} - fontSize={15} + fontSize={fontSize} fill={textColor} wrap="none" ellipsis={true} From ac874188e35add11cbd01d351df1cabf5eb53385 Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Tue, 30 Sep 2025 18:00:38 +0200 Subject: [PATCH 21/34] feat(file-tree): refine sizing system for both available sizes --- .../file-tree/file-tree.business.ts | 43 +++++++++++++++---- .../file-tree/file-tree.tsx | 30 +++++++------ 2 files changed, 50 insertions(+), 23 deletions(-) diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts index bfaaa0d8..d7e30680 100644 --- a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts @@ -2,28 +2,53 @@ import { fitSizeToShapeSizeRestrictions } from '@/common/utils/shapes'; import { ElementSize, ShapeSizeRestrictions, Size } from '@/core/model'; import { FONT_SIZE_VALUES } from '../../front-components/shape.const'; -export const getFileTreeSizeValues = (size?: ElementSize) => { +interface FileTreeSizeValues { + fontSize: number; + iconDimension: number; + elementHeight: number; + paddingX: number; + paddingY: number; + extraTextTopPadding: number; + iconTextSpacing: number; + indentationStep: number; +} + +export const getFileTreeSizeValues = ( + size?: ElementSize +): FileTreeSizeValues => { switch (size) { case 'XS': return { - fontSize: FONT_SIZE_VALUES.SMALLTEXT, - iconWidth: 40, - elementHeight: 55, - size: 'XS', + fontSize: 12, + iconDimension: 25, + elementHeight: 30, + paddingX: 25, + paddingY: 15, + extraTextTopPadding: 9, + iconTextSpacing: 8, + indentationStep: 15, }; case 'S': return { fontSize: FONT_SIZE_VALUES.NORMALTEXT, - iconWidth: 50, + iconDimension: 50, elementHeight: 60, - size: 'S', + paddingX: 30, + paddingY: 20, + extraTextTopPadding: 20, + iconTextSpacing: 10, + indentationStep: 27, }; default: return { fontSize: FONT_SIZE_VALUES.NORMALTEXT, - iconWidth: 50, + iconDimension: 50, elementHeight: 60, - size: 'S', + paddingX: 30, + paddingY: 20, + extraTextTopPadding: 20, + iconTextSpacing: 10, + indentationStep: 25, }; } }; diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx index 72197bd1..f12101c9 100644 --- a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx @@ -14,7 +14,7 @@ import { } from './file-tree.business'; const fileTreeShapeRestrictions: ShapeSizeRestrictions = { - minWidth: 200, + minWidth: 150, minHeight: 120, maxWidth: -1, maxHeight: -1, @@ -55,14 +55,16 @@ export const FileTreeShape = forwardRef( } ); - const { fontSize, iconWidth, elementHeight, size } = getFileTreeSizeValues( - otherProps?.size - ); - - const paddingX = 30; - const paddingY = 25; - const iconTextSpacing = 10; - const indentationStep = 25; + const { + fontSize, + iconDimension, + elementHeight, + extraTextTopPadding, + paddingX, + paddingY, + iconTextSpacing, + indentationStep, + } = getFileTreeSizeValues(otherProps?.size); const restrictedSize = calculateFileTreeDynamicSize(treeItems, { width, @@ -89,7 +91,7 @@ export const FileTreeShape = forwardRef( }; const calculateTextX = (item: FileTreeItem) => { - return calculateIconX(item) + iconWidth + iconTextSpacing; + return calculateIconX(item) + iconDimension + iconTextSpacing; }; const calculateAvailableWidth = (item: FileTreeItem) => { @@ -131,14 +133,14 @@ export const FileTreeShape = forwardRef( )} Date: Tue, 30 Sep 2025 20:23:31 +0200 Subject: [PATCH 22/34] feat(file-tree): implement hook to get dynamic text editor height based on content --- .../file-tree/file-tree.business.ts | 38 +++++++++++++++++++ .../file-tree/file-tree.tsx | 10 +++++ 2 files changed, 48 insertions(+) diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts index d7e30680..b79844bf 100644 --- a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts @@ -1,6 +1,8 @@ import { fitSizeToShapeSizeRestrictions } from '@/common/utils/shapes'; import { ElementSize, ShapeSizeRestrictions, Size } from '@/core/model'; import { FONT_SIZE_VALUES } from '../../front-components/shape.const'; +import { useCanvasContext } from '@/core/providers'; +import { useEffect, useRef } from 'react'; interface FileTreeSizeValues { fontSize: number; @@ -145,3 +147,39 @@ export const calculateFileTreeDynamicSize = ( finalHeight ); }; + +// Hook to resize edition text area based on content +export const useFileTreeResizeOnContentChange = ( + id: string, + coords: { x: number; y: number }, + text: string, + currentSize: Size, + calculatedSize: Size, + minHeight: number +) => { + const previousText = useRef(text); + const { updateShapeSizeAndPosition } = useCanvasContext(); + + useEffect(() => { + // Only update if the text has changed AND the height is different + const textChanged = previousText.current !== text; + + const finalHeight = Math.max(calculatedSize.height, minHeight); + const finalSize = { ...calculatedSize, height: finalHeight }; + + const heightChanged = finalHeight !== currentSize.height; + + if (textChanged && heightChanged) { + previousText.current = text; + updateShapeSizeAndPosition(id, coords, finalSize, false); + } + }, [ + text, + calculatedSize.height, + currentSize.height, + id, + coords.x, + coords.y, + updateShapeSizeAndPosition, + ]); +}; diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx index f12101c9..0e8d25ac 100644 --- a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx @@ -11,6 +11,7 @@ import { FileTreeItem, getFileTreeSizeValues, parseFileTreeText, + useFileTreeResizeOnContentChange, } from './file-tree.business'; const fileTreeShapeRestrictions: ShapeSizeRestrictions = { @@ -73,6 +74,15 @@ export const FileTreeShape = forwardRef( baseRestrictions: fileTreeShapeRestrictions, }); + useFileTreeResizeOnContentChange( + id, + { x, y }, + text, + { width, height }, + restrictedSize, + fileTreeShapeRestrictions.minHeight + ); + const { width: restrictedWidth, height: restrictedHeight } = restrictedSize; const { stroke, strokeStyle, fill, textColor, borderRadius } = From 736f35a6164b55b78d4e5011366d21ce80f9e11c Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Tue, 30 Sep 2025 20:48:09 +0200 Subject: [PATCH 23/34] feat(file-tree): implement hook to get default width adjustment based on size --- .../file-tree/file-tree.business.ts | 28 +++++++++++++++++++ .../file-tree/file-tree.tsx | 3 ++ 2 files changed, 31 insertions(+) diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts index b79844bf..63a29659 100644 --- a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts @@ -183,3 +183,31 @@ export const useFileTreeResizeOnContentChange = ( updateShapeSizeAndPosition, ]); }; + +export const useFileTreeResizeOnSizeChange = ( + id: string, + coords: { x: number; y: number }, + currentWidth: number, + size?: ElementSize +) => { + const previousSize = useRef(size); + const { updateShapeSizeAndPosition } = useCanvasContext(); + + useEffect(() => { + // Only update if the size has changed + if (previousSize.current !== size) { + previousSize.current = size; + + const newWidth = size === 'XS' ? 150 : 230; + + if (currentWidth !== newWidth) { + updateShapeSizeAndPosition( + id, + coords, + { width: newWidth, height: currentWidth }, + false + ); + } + } + }, [size, currentWidth, id, coords.x, coords.y, updateShapeSizeAndPosition]); +}; diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx index 0e8d25ac..e7858290 100644 --- a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx @@ -12,6 +12,7 @@ import { getFileTreeSizeValues, parseFileTreeText, useFileTreeResizeOnContentChange, + useFileTreeResizeOnSizeChange, } from './file-tree.business'; const fileTreeShapeRestrictions: ShapeSizeRestrictions = { @@ -83,6 +84,8 @@ export const FileTreeShape = forwardRef( fileTreeShapeRestrictions.minHeight ); + useFileTreeResizeOnSizeChange(id, { x, y }, width, otherProps?.size); + const { width: restrictedWidth, height: restrictedHeight } = restrictedSize; const { stroke, strokeStyle, fill, textColor, borderRadius } = From f748eed89a55a759bf4127b91b1882998851b073 Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Tue, 30 Sep 2025 21:24:48 +0200 Subject: [PATCH 24/34] update file tree svg --- public/rich-components/file-tree.svg | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/public/rich-components/file-tree.svg b/public/rich-components/file-tree.svg index 0104fcfa..f51ce1a4 100644 --- a/public/rich-components/file-tree.svg +++ b/public/rich-components/file-tree.svg @@ -1,28 +1,28 @@ - + - + - + - Folder 1 + Folder 1 - + - Subfolder + Subfolder - + - File + File - + - Folder 2 + Folder 2 From feda3dc674af01f42820e8d317bb84763600e970 Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Thu, 2 Oct 2025 08:57:46 +0200 Subject: [PATCH 25/34] chore(docs): document why SelectSize v2 exists and link to migration issue --- .../select-size/select-size-v2/select-size-v2.component.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pods/properties/components/select-size/select-size-v2/select-size-v2.component.tsx b/src/pods/properties/components/select-size/select-size-v2/select-size-v2.component.tsx index ee9aaa0e..45dd2c77 100644 --- a/src/pods/properties/components/select-size/select-size-v2/select-size-v2.component.tsx +++ b/src/pods/properties/components/select-size/select-size-v2/select-size-v2.component.tsx @@ -3,6 +3,10 @@ import classes from './select-size-v2.component.module.css'; import { sizeToStep, stepToSize } from './select-size.utils'; import { getSizeConfigForShape } from '@/pods/canvas/model/shape-other-props.utils'; +// ⚠️ This is a temporary v2 component introduced to support shape-specific size customization. +// It will replace current SelectSize once migration is complete. +// For details, see issue: https://github.com/Lemoncode/quickmock/issues/791 + interface Props { label: string; shapeType?: ShapeType; From 31c8e40f940d35871cf7cf0a34aa493f72e7596a Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Thu, 2 Oct 2025 09:00:33 +0200 Subject: [PATCH 26/34] refactor(file-tree): extract file tree resize hooks into hook file, wrap both hooks with useFileTreeResize, export the wrapper function and update main component file --- .../file-tree/file-tree-resize.hook.ts | 88 +++++++++++++++++++ .../file-tree/file-tree.business.ts | 66 -------------- .../file-tree/file-tree.tsx | 10 +-- 3 files changed, 92 insertions(+), 72 deletions(-) create mode 100644 src/common/components/mock-components/front-rich-components/file-tree/file-tree-resize.hook.ts diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree-resize.hook.ts b/src/common/components/mock-components/front-rich-components/file-tree/file-tree-resize.hook.ts new file mode 100644 index 00000000..879a0485 --- /dev/null +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree-resize.hook.ts @@ -0,0 +1,88 @@ +import { ElementSize, Size } from '@/core/model'; +import { useCanvasContext } from '@/core/providers'; +import { useEffect, useRef } from 'react'; + +// Hook to resize edition text area based on content +const useFileTreeResizeOnContentChange = ( + id: string, + coords: { x: number; y: number }, + text: string, + currentSize: Size, + calculatedSize: Size, + minHeight: number +) => { + const previousText = useRef(text); + const { updateShapeSizeAndPosition } = useCanvasContext(); + + useEffect(() => { + // Only update if the text has changed AND the height is different + const textChanged = previousText.current !== text; + + const finalHeight = Math.max(calculatedSize.height, minHeight); + const finalSize = { ...calculatedSize, height: finalHeight }; + + const heightChanged = finalHeight !== currentSize.height; + + if (textChanged && heightChanged) { + previousText.current = text; + updateShapeSizeAndPosition(id, coords, finalSize, false); + } + }, [ + text, + calculatedSize.height, + currentSize.height, + id, + coords.x, + coords.y, + updateShapeSizeAndPosition, + ]); +}; + +const useFileTreeResizeOnSizeChange = ( + id: string, + coords: { x: number; y: number }, + currentWidth: number, + size?: ElementSize +) => { + const previousSize = useRef(size); + const { updateShapeSizeAndPosition } = useCanvasContext(); + + useEffect(() => { + // Only update if the size has changed + if (previousSize.current !== size) { + previousSize.current = size; + + const newWidth = size === 'XS' ? 150 : 230; + + if (currentWidth !== newWidth) { + updateShapeSizeAndPosition( + id, + coords, + { width: newWidth, height: currentWidth }, + false + ); + } + } + }, [size, currentWidth, id, coords.x, coords.y, updateShapeSizeAndPosition]); +}; + +export const useFileTreeResize = ( + id: string, + coords: { x: number; y: number }, + text: string, + currentSize: Size, + calculatedSize: Size, + minHeight: number, + size?: ElementSize +) => { + useFileTreeResizeOnContentChange( + id, + coords, + text, + currentSize, + calculatedSize, + minHeight + ); + + useFileTreeResizeOnSizeChange(id, coords, currentSize.width, size); +}; diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts index 63a29659..d7e30680 100644 --- a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts @@ -1,8 +1,6 @@ import { fitSizeToShapeSizeRestrictions } from '@/common/utils/shapes'; import { ElementSize, ShapeSizeRestrictions, Size } from '@/core/model'; import { FONT_SIZE_VALUES } from '../../front-components/shape.const'; -import { useCanvasContext } from '@/core/providers'; -import { useEffect, useRef } from 'react'; interface FileTreeSizeValues { fontSize: number; @@ -147,67 +145,3 @@ export const calculateFileTreeDynamicSize = ( finalHeight ); }; - -// Hook to resize edition text area based on content -export const useFileTreeResizeOnContentChange = ( - id: string, - coords: { x: number; y: number }, - text: string, - currentSize: Size, - calculatedSize: Size, - minHeight: number -) => { - const previousText = useRef(text); - const { updateShapeSizeAndPosition } = useCanvasContext(); - - useEffect(() => { - // Only update if the text has changed AND the height is different - const textChanged = previousText.current !== text; - - const finalHeight = Math.max(calculatedSize.height, minHeight); - const finalSize = { ...calculatedSize, height: finalHeight }; - - const heightChanged = finalHeight !== currentSize.height; - - if (textChanged && heightChanged) { - previousText.current = text; - updateShapeSizeAndPosition(id, coords, finalSize, false); - } - }, [ - text, - calculatedSize.height, - currentSize.height, - id, - coords.x, - coords.y, - updateShapeSizeAndPosition, - ]); -}; - -export const useFileTreeResizeOnSizeChange = ( - id: string, - coords: { x: number; y: number }, - currentWidth: number, - size?: ElementSize -) => { - const previousSize = useRef(size); - const { updateShapeSizeAndPosition } = useCanvasContext(); - - useEffect(() => { - // Only update if the size has changed - if (previousSize.current !== size) { - previousSize.current = size; - - const newWidth = size === 'XS' ? 150 : 230; - - if (currentWidth !== newWidth) { - updateShapeSizeAndPosition( - id, - coords, - { width: newWidth, height: currentWidth }, - false - ); - } - } - }, [size, currentWidth, id, coords.x, coords.y, updateShapeSizeAndPosition]); -}; diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx index e7858290..ac1788e9 100644 --- a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx @@ -11,9 +11,8 @@ import { FileTreeItem, getFileTreeSizeValues, parseFileTreeText, - useFileTreeResizeOnContentChange, - useFileTreeResizeOnSizeChange, } from './file-tree.business'; +import { useFileTreeResize } from './file-tree-resize.hook'; const fileTreeShapeRestrictions: ShapeSizeRestrictions = { minWidth: 150, @@ -75,17 +74,16 @@ export const FileTreeShape = forwardRef( baseRestrictions: fileTreeShapeRestrictions, }); - useFileTreeResizeOnContentChange( + useFileTreeResize( id, { x, y }, text, { width, height }, restrictedSize, - fileTreeShapeRestrictions.minHeight + fileTreeShapeRestrictions.minHeight, + otherProps?.size ); - useFileTreeResizeOnSizeChange(id, { x, y }, width, otherProps?.size); - const { width: restrictedWidth, height: restrictedHeight } = restrictedSize; const { stroke, strokeStyle, fill, textColor, borderRadius } = From f49e6bbc6f882be9b2bf2e766085bd6d9aa2b264 Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Thu, 2 Oct 2025 09:44:25 +0200 Subject: [PATCH 27/34] feat(file-tree): add dynamic width calculation to prevent icon overflow --- .../file-tree/file-tree.business.ts | 19 ++++++++++++++++++- .../file-tree/file-tree.tsx | 3 +++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts index d7e30680..08efa584 100644 --- a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts @@ -69,6 +69,9 @@ interface FileTreeDynamicSizeParams { width: number; elementHeight: number; paddingY: number; + paddingX: number; + iconDimension: number; + indentationStep: number; baseRestrictions: ShapeSizeRestrictions; } @@ -122,7 +125,20 @@ export const calculateFileTreeDynamicSize = ( treeItems: FileTreeItem[], params: FileTreeDynamicSizeParams ): Size => { - const { width, elementHeight, paddingY, baseRestrictions } = params; + const { + width, + elementHeight, + paddingY, + paddingX, + iconDimension, + indentationStep, + baseRestrictions, + } = params; + + const maxIconX = Math.max( + ...treeItems.map(item => paddingX + item.level * indentationStep) + ); + const requiredWidth = maxIconX + iconDimension + paddingX; // Calculate minimum height required based on content const minContentHeight = treeItems.length * elementHeight + paddingY * 2; @@ -130,6 +146,7 @@ export const calculateFileTreeDynamicSize = ( // Create dynamic constraints with adaptive minimum height const dynamicRestrictions: ShapeSizeRestrictions = { ...baseRestrictions, + minWidth: Math.max(baseRestrictions.minWidth, requiredWidth), minHeight: minContentHeight, defaultHeight: Math.max( baseRestrictions.defaultHeight || 200, diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx index ac1788e9..788a6328 100644 --- a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx @@ -71,6 +71,9 @@ export const FileTreeShape = forwardRef( width, elementHeight, paddingY, + paddingX, + iconDimension, + indentationStep, baseRestrictions: fileTreeShapeRestrictions, }); From 8a93fa99e98573cae722cb17580ef5f9fd5fecc7 Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Thu, 2 Oct 2025 10:49:42 +0200 Subject: [PATCH 28/34] feat(file-tree): improve resize behavior for content and ElementSize changes --- .../file-tree/file-tree-resize.hook.ts | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree-resize.hook.ts b/src/common/components/mock-components/front-rich-components/file-tree/file-tree-resize.hook.ts index 879a0485..afd8c781 100644 --- a/src/common/components/mock-components/front-rich-components/file-tree/file-tree-resize.hook.ts +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree-resize.hook.ts @@ -15,22 +15,28 @@ const useFileTreeResizeOnContentChange = ( const { updateShapeSizeAndPosition } = useCanvasContext(); useEffect(() => { - // Only update if the text has changed AND the height is different const textChanged = previousText.current !== text; const finalHeight = Math.max(calculatedSize.height, minHeight); const finalSize = { ...calculatedSize, height: finalHeight }; - const heightChanged = finalHeight !== currentSize.height; + const sizeChanged = + finalHeight !== currentSize.height || + calculatedSize.width !== currentSize.width; - if (textChanged && heightChanged) { + if (textChanged && sizeChanged) { previousText.current = text; updateShapeSizeAndPosition(id, coords, finalSize, false); + } else if (sizeChanged) { + // If only the size has changed, also resize + updateShapeSizeAndPosition(id, coords, finalSize, false); } }, [ text, calculatedSize.height, + calculatedSize.width, currentSize.height, + currentSize.width, id, coords.x, coords.y, @@ -38,10 +44,16 @@ const useFileTreeResizeOnContentChange = ( ]); }; +// Hook to force width change when ElementSize changes (XS ↔ S) +// This ensures that when dropping a component and changing from S to XS (or vice versa), +// the component doesn't maintain the previous width but forces the correct one: +// - XS: 150px width +// - S: 230px width + const useFileTreeResizeOnSizeChange = ( id: string, coords: { x: number; y: number }, - currentWidth: number, + currentSize: Size, size?: ElementSize ) => { const previousSize = useRef(size); @@ -54,16 +66,23 @@ const useFileTreeResizeOnSizeChange = ( const newWidth = size === 'XS' ? 150 : 230; - if (currentWidth !== newWidth) { + if (currentSize.width !== newWidth) { updateShapeSizeAndPosition( id, coords, - { width: newWidth, height: currentWidth }, + { width: newWidth, height: currentSize.height }, false ); } } - }, [size, currentWidth, id, coords.x, coords.y, updateShapeSizeAndPosition]); + }, [ + size, + currentSize.width, + id, + coords.x, + coords.y, + updateShapeSizeAndPosition, + ]); }; export const useFileTreeResize = ( @@ -84,5 +103,5 @@ export const useFileTreeResize = ( minHeight ); - useFileTreeResizeOnSizeChange(id, coords, currentSize.width, size); + useFileTreeResizeOnSizeChange(id, coords, currentSize, size); }; From efb9f200204ffe39ee809e7e3ba5e4c689788248 Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Thu, 2 Oct 2025 11:40:42 +0200 Subject: [PATCH 29/34] fix(file-tree): remove restrictive minHeight to enable width resizing and eliminate empty space, establish min height from 120 to 50 --- .../front-rich-components/file-tree/file-tree.business.ts | 8 ++------ .../front-rich-components/file-tree/file-tree.tsx | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts index 08efa584..70ac4ba6 100644 --- a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts @@ -143,15 +143,11 @@ export const calculateFileTreeDynamicSize = ( // Calculate minimum height required based on content const minContentHeight = treeItems.length * elementHeight + paddingY * 2; - // Create dynamic constraints with adaptive minimum height + // Create dynamic constraints for content-based sizing const dynamicRestrictions: ShapeSizeRestrictions = { ...baseRestrictions, minWidth: Math.max(baseRestrictions.minWidth, requiredWidth), - minHeight: minContentHeight, - defaultHeight: Math.max( - baseRestrictions.defaultHeight || 200, - minContentHeight - ), + defaultHeight: minContentHeight, }; const finalHeight = minContentHeight; diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx index 788a6328..9f76fcd5 100644 --- a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx @@ -16,7 +16,7 @@ import { useFileTreeResize } from './file-tree-resize.hook'; const fileTreeShapeRestrictions: ShapeSizeRestrictions = { minWidth: 150, - minHeight: 120, + minHeight: 50, maxWidth: -1, maxHeight: -1, defaultWidth: 230, From 5f0cbb9e69aa0b71e649dee1899c8ff8f5557c96 Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Fri, 3 Oct 2025 14:07:23 +0200 Subject: [PATCH 30/34] fix(file-tree): correctly parse symbols with space but no text content --- .../file-tree/file-tree.business.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts index 70ac4ba6..4c0acbe5 100644 --- a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts @@ -82,36 +82,37 @@ export const parseFileTreeText = (text: string): FileTreeItem[] => { // First detect indentation const indentMatch = line.match(/^(\s*)/); const level = indentMatch ? Math.floor(indentMatch[1].length / 3) : 0; - const trimmed = line.trim(); + const trimmedStart = line.trimStart(); - if (trimmed === '') return null; + if (trimmedStart === '') return null; // Detect symbol - if (trimmed.startsWith('+ ')) { + if (trimmedStart.startsWith('+ ')) { return { type: 'folder', - text: trimmed.substring(2).trim(), + text: trimmedStart.substring(2).trim(), level: level, }; } - if (trimmed.startsWith('- ')) { + if (trimmedStart.startsWith('- ')) { return { type: 'subfolder', - text: trimmed.substring(2).trim(), + text: trimmedStart.substring(2).trim(), level: level, }; } - if (trimmed.startsWith('* ')) { + if (trimmedStart.startsWith('* ')) { return { type: 'file', - text: trimmed.substring(2).trim(), + text: trimmedStart.substring(2).trim(), level: level, }; } // No symbol: will be treated as a folder + const trimmed = line.trim(); return { type: 'folder', text: trimmed, From 3b08933f572b450ed266e52dfcfb26b610508514 Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Fri, 3 Oct 2025 14:08:36 +0200 Subject: [PATCH 31/34] perf(file-tree): add memoization to optimize component re-renders --- .../front-rich-components/file-tree/file-tree.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx index 9f76fcd5..d7234643 100644 --- a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx @@ -1,5 +1,5 @@ import { ShapeSizeRestrictions, ShapeType } from '@/core/model'; -import { forwardRef, useEffect, useState } from 'react'; +import { forwardRef, useEffect, useMemo, useState } from 'react'; import { Group, Image, Rect, Text } from 'react-konva'; import { ShapeProps } from '../../shape.model'; import { useGroupShapeProps } from '../../mock-components.utils'; @@ -46,7 +46,9 @@ export const FileTreeShape = forwardRef( ...shapeProps } = props; - const treeItems = parseFileTreeText(text); + const treeItems = useMemo(() => { + return parseFileTreeText(text); + }, [text]); const [icons, setIcons] = useState>( { @@ -65,7 +67,10 @@ export const FileTreeShape = forwardRef( paddingY, iconTextSpacing, indentationStep, - } = getFileTreeSizeValues(otherProps?.size); + } = useMemo( + () => getFileTreeSizeValues(otherProps?.size), + [otherProps?.size] + ); const restrictedSize = calculateFileTreeDynamicSize(treeItems, { width, From 13430d74250c33822fe1e15a34e93a151b290d76 Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Fri, 3 Oct 2025 14:09:40 +0200 Subject: [PATCH 32/34] test(file-tree): add unit tests for parseFileTreeText --- .../file-tree/file-tree.business.spec.ts | 230 ++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.spec.ts diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.spec.ts b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.spec.ts new file mode 100644 index 00000000..b0b6aeca --- /dev/null +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.spec.ts @@ -0,0 +1,230 @@ +import { parseFileTreeText } from './file-tree.business'; + +describe('parseFileTreeText', () => { + describe('Basic functionality', () => { + it.each([ + ['+ Documents', 'folder', 'Documents'], + ['- Downloads', 'subfolder', 'Downloads'], + ['* README.md', 'file', 'README.md'], + ['Projects', 'folder', 'Projects'], + ])( + 'should parse %s as %s with text "%s"', + (text, expectedType, expectedText) => { + // Arrange + + // Act + const result = parseFileTreeText(text); + + // Assert + expect(result).toEqual([ + { + type: expectedType, + text: expectedText, + level: 0, + }, + ]); + } + ); + }); + + describe('Indentation levels', () => { + it.each<{ description: string; text: string; expectedLevel: number }>([ + { + description: 'no spaces create level 0', + text: '+ Root', + expectedLevel: 0, + }, + { + description: '3 spaces create level 1', + text: ' + Subfolder', + expectedLevel: 1, + }, + { + description: '6 spaces create level 2', + text: ' * File', + expectedLevel: 2, + }, + { + description: '9 spaces create level 3', + text: ' + Deep folder', + expectedLevel: 3, + }, + ])('$description', ({ text, expectedLevel }) => { + // Arrange + + // Act + const result = parseFileTreeText(text); + + // Assert + expect(result[0].level).toBe(expectedLevel); + }); + + it('should handle indentation with non-standard spacing', () => { + // Arrange + const text = ` + Two spaces (level 0) + + Four spaces (level 1) + + Five spaces (level 1) + + Seven spaces (level 2)`; + + // Act + const result = parseFileTreeText(text); + + // Assert + expect(result[0].level).toBe(0); // 2/3 = 0 + expect(result[1].level).toBe(1); // 4/3 = 1 + expect(result[2].level).toBe(1); // 5/3 = 1 + expect(result[3].level).toBe(2); // 7/3 = 2 + }); + + it('should handle complex nested structure', () => { + // Arrange + const text = `+ Root + - Subfolder 1 + * File 1 + + Subfolder 2 + - Deep subfolder + * Deep file + + Deep folder + - Deep subfolder 2 + * Deep file 2`; + + // Act + const result = parseFileTreeText(text); + + // Assert + expect(result).toEqual([ + { type: 'folder', text: 'Root', level: 0 }, + { type: 'subfolder', text: 'Subfolder 1', level: 1 }, + { type: 'file', text: 'File 1', level: 2 }, + { type: 'folder', text: 'Subfolder 2', level: 1 }, + { type: 'subfolder', text: 'Deep subfolder', level: 1 }, + { type: 'file', text: 'Deep file', level: 2 }, + { type: 'folder', text: 'Deep folder', level: 3 }, + { type: 'subfolder', text: 'Deep subfolder 2', level: 5 }, + { type: 'file', text: 'Deep file 2', level: 6 }, + ]); + }); + }); + + describe('Corner cases', () => { + it.each<{ description: string; input: string; expected: any[] }>([ + { + description: 'return empty array for empty string', + input: '', + expected: [], + }, + { + description: + 'filter out lines with only newlines between valid content', + input: ` + ++ Folder + +* File + +`, + expected: [ + { type: 'folder', text: 'Folder', level: 0 }, + { type: 'file', text: 'File', level: 0 }, + ], + }, + { + description: 'return empty array for text with only newlines', + input: '\n\n\n', + expected: [], + }, + ])('should $description', ({ input, expected }) => { + // Arrange + + // Act + const result = parseFileTreeText(input); + + // Assert + expect(result).toEqual(expected); + }); + }); + + describe('Edge cases with symbols', () => { + it.each<{ text: string; expected: any[]; description: string }>([ + { + description: + 'ignore extra spaces after the symbol and keep correct type', + text: `+ Documents +- Downloads +* README.md`, + expected: [ + { type: 'folder', text: 'Documents', level: 0 }, + { type: 'subfolder', text: 'Downloads', level: 0 }, + { type: 'file', text: 'README.md', level: 0 }, + ], + }, + { + description: 'handle symbols without space after as plain text', + text: `+ +- +*`, + expected: [ + { type: 'folder', text: '+', level: 0 }, + { type: 'folder', text: '-', level: 0 }, + { type: 'folder', text: '*', level: 0 }, + ], + }, + { + description: 'trim leading/trailing whitespace in text', + text: `+ Documents +- Downloads +* README.md `, + expected: [ + { type: 'folder', text: 'Documents', level: 0 }, + { type: 'subfolder', text: 'Downloads', level: 0 }, + { type: 'file', text: 'README.md', level: 0 }, + ], + }, + { + description: 'recognize symbols with space but no text', + text: `+ +- +* `, + expected: [ + { type: 'folder', text: '', level: 0 }, + { type: 'subfolder', text: '', level: 0 }, + { type: 'file', text: '', level: 0 }, + ], + }, + { + description: + 'handle lines starting with symbols but not followed by space, as folder and plain text', + text: `+Documents +-Downloads +*README.md`, + expected: [ + { type: 'folder', text: '+Documents', level: 0 }, + { type: 'folder', text: '-Downloads', level: 0 }, + { type: 'folder', text: '*README.md', level: 0 }, + ], + }, + ])('should $description', ({ text, expected }) => { + // Arrange + + // Act + const result = parseFileTreeText(text); + + // Assert + expect(result).toEqual(expected); + }); + }); + + describe('Large indentation values', () => { + it('should handle 27 spaces creating level 9 indentation', () => { + // Arrange + const spaces = ' '; // 27 spaces + + // Act + const text = `${spaces}+ Deep folder`; + const result = parseFileTreeText(text); + + // Assert + expect(result[0].level).toBe(9); + }); + }); +}); From 0f022958f35dee3082f3aa2a8138bc696179b55d Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Sat, 4 Oct 2025 09:24:12 +0200 Subject: [PATCH 33/34] refactor(file-tree): extract interfaces to dedicated model file --- .../file-tree/file-tree.model.ts | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/common/components/mock-components/front-rich-components/file-tree/file-tree.model.ts diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.model.ts b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.model.ts new file mode 100644 index 00000000..7415a168 --- /dev/null +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.model.ts @@ -0,0 +1,31 @@ +import { ShapeSizeRestrictions } from '@/core/model'; + +export interface FileTreeSizeValues { + fontSize: number; + iconDimension: number; + elementHeight: number; + paddingX: number; + paddingY: number; + extraTextTopPadding: number; + iconTextSpacing: number; + indentationStep: number; +} + +// Symbol -> + Folder - Subfolder * File +// Level -> Level 0: no indentation in Folder / Level 1: 1 indentation (3 spaces) in Subfolder / Level 2: 2 indentations (6 spaces) in File +export interface FileTreeItem { + type: 'folder' | 'subfolder' | 'file'; + text: string; + level: number; +} + +export interface FileTreeDynamicSizeParams { + width: number; + height: number; + elementHeight: number; + paddingY: number; + paddingX: number; + iconDimension: number; + indentationStep: number; + baseRestrictions: ShapeSizeRestrictions; +} From e32bb772c3435d3b5e754400e9c6ad0d543bafac Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Sat, 4 Oct 2025 09:27:23 +0200 Subject: [PATCH 34/34] feat(file-tree): enable manual height resizing while preserving content adaptation when switching between sizes --- .../file-tree/file-tree-resize.hook.ts | 31 ++++++++++++-- .../file-tree/file-tree.business.ts | 41 ++++--------------- .../file-tree/file-tree.tsx | 14 ++++++- 3 files changed, 48 insertions(+), 38 deletions(-) diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree-resize.hook.ts b/src/common/components/mock-components/front-rich-components/file-tree/file-tree-resize.hook.ts index afd8c781..fef5ba58 100644 --- a/src/common/components/mock-components/front-rich-components/file-tree/file-tree-resize.hook.ts +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree-resize.hook.ts @@ -1,6 +1,7 @@ import { ElementSize, Size } from '@/core/model'; import { useCanvasContext } from '@/core/providers'; import { useEffect, useRef } from 'react'; +import { FileTreeItem, FileTreeSizeValues } from './file-tree.model'; // Hook to resize edition text area based on content const useFileTreeResizeOnContentChange = ( @@ -54,6 +55,8 @@ const useFileTreeResizeOnSizeChange = ( id: string, coords: { x: number; y: number }, currentSize: Size, + treeItems: FileTreeItem[], + sizeValues: FileTreeSizeValues, size?: ElementSize ) => { const previousSize = useRef(size); @@ -66,11 +69,20 @@ const useFileTreeResizeOnSizeChange = ( const newWidth = size === 'XS' ? 150 : 230; - if (currentSize.width !== newWidth) { + const minContentHeight = + treeItems && sizeValues + ? treeItems.length * sizeValues.elementHeight + + sizeValues.paddingY * 2 + : currentSize.height; + + if ( + currentSize.width !== newWidth || + currentSize.height !== minContentHeight + ) { updateShapeSizeAndPosition( id, coords, - { width: newWidth, height: currentSize.height }, + { width: newWidth, height: minContentHeight }, false ); } @@ -78,10 +90,13 @@ const useFileTreeResizeOnSizeChange = ( }, [ size, currentSize.width, + currentSize.height, id, coords.x, coords.y, updateShapeSizeAndPosition, + treeItems, + sizeValues, ]); }; @@ -92,6 +107,8 @@ export const useFileTreeResize = ( currentSize: Size, calculatedSize: Size, minHeight: number, + treeItems: FileTreeItem[], + sizeValues: FileTreeSizeValues, size?: ElementSize ) => { useFileTreeResizeOnContentChange( @@ -103,5 +120,13 @@ export const useFileTreeResize = ( minHeight ); - useFileTreeResizeOnSizeChange(id, coords, currentSize, size); + useFileTreeResizeOnSizeChange( + id, + coords, + currentSize, + + treeItems, + sizeValues, + size + ); }; diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts index 4c0acbe5..5bddbaec 100644 --- a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.business.ts @@ -1,17 +1,11 @@ import { fitSizeToShapeSizeRestrictions } from '@/common/utils/shapes'; import { ElementSize, ShapeSizeRestrictions, Size } from '@/core/model'; import { FONT_SIZE_VALUES } from '../../front-components/shape.const'; - -interface FileTreeSizeValues { - fontSize: number; - iconDimension: number; - elementHeight: number; - paddingX: number; - paddingY: number; - extraTextTopPadding: number; - iconTextSpacing: number; - indentationStep: number; -} +import { + FileTreeDynamicSizeParams, + FileTreeItem, + FileTreeSizeValues, +} from './file-tree.model'; export const getFileTreeSizeValues = ( size?: ElementSize @@ -53,28 +47,6 @@ export const getFileTreeSizeValues = ( } }; -export const joinTextContent = (text: string): string[] => { - return text.split(', '); -}; - -// Symbol -> + Folder - Subfolder * File -// Level -> Level 0: no indentation in Folder / Level 1: 1 indentation (3 spaces) in Subfolder / Level 2: 2 indentations (6 spaces) in File -export interface FileTreeItem { - type: 'folder' | 'subfolder' | 'file'; - text: string; - level: number; -} - -interface FileTreeDynamicSizeParams { - width: number; - elementHeight: number; - paddingY: number; - paddingX: number; - iconDimension: number; - indentationStep: number; - baseRestrictions: ShapeSizeRestrictions; -} - export const parseFileTreeText = (text: string): FileTreeItem[] => { return text .split('\n') @@ -128,6 +100,7 @@ export const calculateFileTreeDynamicSize = ( ): Size => { const { width, + height, elementHeight, paddingY, paddingX, @@ -151,7 +124,7 @@ export const calculateFileTreeDynamicSize = ( defaultHeight: minContentHeight, }; - const finalHeight = minContentHeight; + const finalHeight = Math.max(height, minContentHeight); return fitSizeToShapeSizeRestrictions( dynamicRestrictions, diff --git a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx index d7234643..bb5c064d 100644 --- a/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx +++ b/src/common/components/mock-components/front-rich-components/file-tree/file-tree.tsx @@ -8,11 +8,11 @@ import { useShapeProps } from '@/common/components/shapes/use-shape-props.hook'; import { BASIC_SHAPE } from '../../front-components/shape.const'; import { calculateFileTreeDynamicSize, - FileTreeItem, getFileTreeSizeValues, parseFileTreeText, } from './file-tree.business'; import { useFileTreeResize } from './file-tree-resize.hook'; +import { FileTreeItem } from './file-tree.model'; const fileTreeShapeRestrictions: ShapeSizeRestrictions = { minWidth: 150, @@ -74,6 +74,7 @@ export const FileTreeShape = forwardRef( const restrictedSize = calculateFileTreeDynamicSize(treeItems, { width, + height, elementHeight, paddingY, paddingX, @@ -89,6 +90,17 @@ export const FileTreeShape = forwardRef( { width, height }, restrictedSize, fileTreeShapeRestrictions.minHeight, + treeItems, + { + fontSize, + iconDimension, + elementHeight, + extraTextTopPadding, + paddingX, + paddingY, + iconTextSpacing, + indentationStep, + }, otherProps?.size );