diff --git a/public/low-wireframes/paragraphScribbled.svg b/public/low-wireframes/paragraphScribbled.svg
new file mode 100644
index 00000000..a3f11e0c
--- /dev/null
+++ b/public/low-wireframes/paragraphScribbled.svg
@@ -0,0 +1,27 @@
+
\ No newline at end of file
diff --git a/src/common/components/mock-components/front-low-wireframes-components/paragraph-scribbled-shape/index.ts b/src/common/components/mock-components/front-low-wireframes-components/paragraph-scribbled-shape/index.ts
new file mode 100644
index 00000000..7ab23d52
--- /dev/null
+++ b/src/common/components/mock-components/front-low-wireframes-components/paragraph-scribbled-shape/index.ts
@@ -0,0 +1 @@
+export * from './paragraph-scribbled-shape';
diff --git a/src/common/components/mock-components/front-low-wireframes-components/paragraph-scribbled-shape/paragraph-scribbled-shape.tsx b/src/common/components/mock-components/front-low-wireframes-components/paragraph-scribbled-shape/paragraph-scribbled-shape.tsx
new file mode 100644
index 00000000..ff6da889
--- /dev/null
+++ b/src/common/components/mock-components/front-low-wireframes-components/paragraph-scribbled-shape/paragraph-scribbled-shape.tsx
@@ -0,0 +1,69 @@
+import { forwardRef, useMemo } from 'react';
+import { Group, Path, Rect } from 'react-konva';
+import { ShapeSizeRestrictions, ShapeType } from '@/core/model';
+import { ShapeProps } from '../../shape.model';
+import { useShapeProps } from '../../../shapes/use-shape-props.hook';
+import { BASIC_SHAPE } from '../../front-components/shape.const';
+import { useGroupShapeProps } from '../../mock-components.utils';
+import { fitSizeToShapeSizeRestrictions } from '@/common/utils/shapes';
+import { MIN_LINE_HEIGHT } from './paragraph-scribbled.const';
+import { calculateParagraphPaths } from './paragraph-scribbled.business';
+
+const paragraphScribbledShapeRestrictions: ShapeSizeRestrictions = {
+ minWidth: 100,
+ minHeight: MIN_LINE_HEIGHT,
+ maxWidth: -1,
+ maxHeight: -1,
+ defaultWidth: 300,
+ defaultHeight: 150,
+};
+
+export const getParagraphScribbledShapeRestrictions =
+ (): ShapeSizeRestrictions => paragraphScribbledShapeRestrictions;
+
+const shapeType: ShapeType = 'paragraphScribbled';
+
+export const ParagraphScribbled = forwardRef((props, ref) => {
+ const { width, height, id, otherProps, ...shapeProps } = props;
+
+ const { stroke } = useShapeProps(otherProps, BASIC_SHAPE);
+ const commonGroupProps = useGroupShapeProps(
+ props,
+ { width, height },
+ shapeType,
+ ref
+ );
+
+ const restrictedSize = fitSizeToShapeSizeRestrictions(
+ paragraphScribbledShapeRestrictions,
+ width,
+ height
+ );
+
+ const { width: restrictedWidth, height: restrictedHeight } = restrictedSize;
+
+ const paths = useMemo(() => {
+ return calculateParagraphPaths(restrictedWidth, restrictedHeight, id);
+ }, [restrictedWidth, restrictedHeight, id]);
+
+ return (
+
+ {paths.map((path, idx) => (
+
+ ))}
+
+
+ );
+});
diff --git a/src/common/components/mock-components/front-low-wireframes-components/paragraph-scribbled-shape/paragraph-scribbled.business.ts b/src/common/components/mock-components/front-low-wireframes-components/paragraph-scribbled-shape/paragraph-scribbled.business.ts
new file mode 100644
index 00000000..7256ebb4
--- /dev/null
+++ b/src/common/components/mock-components/front-low-wireframes-components/paragraph-scribbled-shape/paragraph-scribbled.business.ts
@@ -0,0 +1,36 @@
+import { calculatePath } from '../text-scribbled-shape/text-scribbled.business';
+import { MIN_LINE_HEIGHT } from './paragraph-scribbled.const';
+
+export const calculateParagraphPaths = (
+ restrictedWidth: number,
+ restrictedHeight: number,
+ id: string
+): string[] => {
+ // Calculate how many lines fit based on the height
+ const numLines = Math.max(1, Math.trunc(restrictedHeight / MIN_LINE_HEIGHT));
+
+ return Array.from({ length: numLines }).map((_, i) => {
+ const lineY = i * MIN_LINE_HEIGHT;
+ const lineId = `${id}-${i}`;
+ const rawPath = calculatePath(restrictedWidth, MIN_LINE_HEIGHT, lineId);
+
+ // Adjust the path to shift Y coordinate for each line
+ // 🔍 Step by step:
+ // The path assumes the text is vertically centered in a block of given height (e.g., 25px).
+ // If you just drew this path multiple times, all lines would overlap.
+ // To fix that, we shift the Y coordinate for each point in the path.
+ //
+ // Regular expression: /\d+,\d+/g
+ // Finds all x,y coordinates in the path string (e.g., "10,12", "15,11").
+ // We split each coordinate, convert y to number, add vertical offset (lineY),
+ // then reassemble the coordinate string.
+ const shiftedPath = rawPath.replace(/\d+,\d+/g, match => {
+ const [xStr, yStr] = match.split(',');
+ const x = parseFloat(xStr);
+ const y = parseFloat(yStr) + lineY;
+ return `${x},${y}`;
+ });
+
+ return shiftedPath;
+ });
+};
diff --git a/src/common/components/mock-components/front-low-wireframes-components/paragraph-scribbled-shape/paragraph-scribbled.const.ts b/src/common/components/mock-components/front-low-wireframes-components/paragraph-scribbled-shape/paragraph-scribbled.const.ts
new file mode 100644
index 00000000..9667baf5
--- /dev/null
+++ b/src/common/components/mock-components/front-low-wireframes-components/paragraph-scribbled-shape/paragraph-scribbled.const.ts
@@ -0,0 +1 @@
+export const MIN_LINE_HEIGHT = 25;
diff --git a/src/core/model/index.ts b/src/core/model/index.ts
index db8001e4..97dd6e3f 100644
--- a/src/core/model/index.ts
+++ b/src/core/model/index.ts
@@ -83,7 +83,8 @@ export type ShapeType =
| 'ellipseLow'
| 'rectangleLow'
| 'circleLow'
- | 'textScribbled';
+ | 'textScribbled'
+ | 'paragraphScribbled';
export const ShapeDisplayName: Record = {
multiple: 'multiple',
@@ -156,6 +157,7 @@ export const ShapeDisplayName: Record = {
rectangleLow: 'Rectangle Placeholder',
circleLow: 'Circle',
textScribbled: 'Text Scribbled',
+ paragraphScribbled: 'Paragraph Scribbled',
};
export type EditType = 'input' | 'textarea' | 'imageupload';
diff --git a/src/pods/canvas/model/shape-size.mapper.ts b/src/pods/canvas/model/shape-size.mapper.ts
index 1c3f0052..e4f8fd74 100644
--- a/src/pods/canvas/model/shape-size.mapper.ts
+++ b/src/pods/canvas/model/shape-size.mapper.ts
@@ -87,6 +87,7 @@ import {
getCircleLowShapeSizeRestrictions,
getTextScribbledShapeRestrictions,
} from '@/common/components/mock-components/front-low-wireframes-components';
+import { getParagraphScribbledShapeRestrictions } from '@/common/components/mock-components/front-low-wireframes-components/paragraph-scribbled-shape';
const getMultipleNodeSizeRestrictions = (): ShapeSizeRestrictions => ({
minWidth: 0,
@@ -169,6 +170,7 @@ const shapeSizeMap: Record ShapeSizeRestrictions> = {
rectangleLow: getRectangleLowShapeRestrictions,
circleLow: getCircleLowShapeSizeRestrictions,
textScribbled: getTextScribbledShapeRestrictions,
+ paragraphScribbled: getParagraphScribbledShapeRestrictions,
};
export default shapeSizeMap;
diff --git a/src/pods/canvas/shape-renderer/index.tsx b/src/pods/canvas/shape-renderer/index.tsx
index 1ca9a691..7b06e31c 100644
--- a/src/pods/canvas/shape-renderer/index.tsx
+++ b/src/pods/canvas/shape-renderer/index.tsx
@@ -81,6 +81,7 @@ import {
renderEllipseLow,
renderRectangleLow,
renderTextScribbled,
+ renderParagraphScribbled,
} from './simple-low-wireframes-components';
export const renderShapeComponent = (
@@ -226,6 +227,8 @@ export const renderShapeComponent = (
return renderCircleLow(shape, shapeRenderedProps);
case 'textScribbled':
return renderTextScribbled(shape, shapeRenderedProps);
+ case 'paragraphScribbled':
+ return renderParagraphScribbled(shape, shapeRenderedProps);
default:
return renderNotFound(shape, shapeRenderedProps);
}
diff --git a/src/pods/canvas/shape-renderer/simple-low-wireframes-components/index.ts b/src/pods/canvas/shape-renderer/simple-low-wireframes-components/index.ts
index 714172a3..83c76451 100644
--- a/src/pods/canvas/shape-renderer/simple-low-wireframes-components/index.ts
+++ b/src/pods/canvas/shape-renderer/simple-low-wireframes-components/index.ts
@@ -5,3 +5,4 @@ export * from './low-vertical-line.renderer';
export * from './rectangle-low.renderer';
export * from './circle-low.renderer';
export * from './text-scribbled.renderer';
+export * from './paragraph-scribbled.renderer';
diff --git a/src/pods/canvas/shape-renderer/simple-low-wireframes-components/paragraph-scribbled.renderer.tsx b/src/pods/canvas/shape-renderer/simple-low-wireframes-components/paragraph-scribbled.renderer.tsx
new file mode 100644
index 00000000..85ca8d04
--- /dev/null
+++ b/src/pods/canvas/shape-renderer/simple-low-wireframes-components/paragraph-scribbled.renderer.tsx
@@ -0,0 +1,32 @@
+import { ShapeRendererProps } from '../model';
+import { ShapeModel } from '@/core/model';
+import { ParagraphScribbled } from '@/common/components/mock-components/front-low-wireframes-components/paragraph-scribbled-shape';
+
+export const renderParagraphScribbled = (
+ shape: ShapeModel,
+ shapeRenderedProps: ShapeRendererProps
+) => {
+ const { handleSelected, shapeRefs, handleDragEnd, handleTransform } =
+ shapeRenderedProps;
+
+ return (
+
+ );
+};
diff --git a/src/pods/galleries/low-wireframe-gallery/low-wireframe-gallery-data/index.ts b/src/pods/galleries/low-wireframe-gallery/low-wireframe-gallery-data/index.ts
index f309ac20..ce07b4a5 100644
--- a/src/pods/galleries/low-wireframe-gallery/low-wireframe-gallery-data/index.ts
+++ b/src/pods/galleries/low-wireframe-gallery/low-wireframe-gallery-data/index.ts
@@ -29,4 +29,8 @@ export const mockLowWireframeCollection: ItemInfo[] = [
thumbnailSrc: '/low-wireframes/textScribbled.svg',
type: 'textScribbled',
},
+ {
+ thumbnailSrc: '/low-wireframes/paragraphScribbled.svg',
+ type: 'paragraphScribbled',
+ },
];