diff --git a/public/rich-components/fab-button.svg b/public/rich-components/fab-button.svg
new file mode 100644
index 00000000..728ecd63
--- /dev/null
+++ b/public/rich-components/fab-button.svg
@@ -0,0 +1,10 @@
+
\ No newline at end of file
diff --git a/src/common/components/mock-components/front-components/icon/icon-shape.business.ts b/src/common/components/mock-components/front-components/icon/icon-shape.business.ts
index 9378b3e3..71b7c376 100644
--- a/src/common/components/mock-components/front-components/icon/icon-shape.business.ts
+++ b/src/common/components/mock-components/front-components/icon/icon-shape.business.ts
@@ -1,20 +1,5 @@
import { IconSize } from '@/core/model';
-export const loadSvgWithFill = async (url: string, fillColor: string) => {
- const response = await fetch(url);
- const svgText = await response.text();
-
- const modifiedSvg = svgText.replace(/fill="[^"]*"/g, `fill="${fillColor}"`);
-
- const svgBlob = new Blob([modifiedSvg], { type: 'image/svg+xml' });
- const objectURL = URL.createObjectURL(svgBlob);
-
- const img = new window.Image();
- img.src = objectURL;
-
- return img;
-};
-
export const returnIconSize = (iconSize: IconSize): number[] => {
switch (iconSize) {
case 'XS':
diff --git a/src/common/components/mock-components/front-components/icon/icon-shape.tsx b/src/common/components/mock-components/front-components/icon/icon-shape.tsx
index f268a9ae..86af4394 100644
--- a/src/common/components/mock-components/front-components/icon/icon-shape.tsx
+++ b/src/common/components/mock-components/front-components/icon/icon-shape.tsx
@@ -7,7 +7,8 @@ import { useModalDialogContext } from '@/core/providers/model-dialog-providers/m
import { IconModal } from '@/pods/properties/components/icon-selector/modal';
import { useCanvasContext } from '@/core/providers';
import { useGroupShapeProps } from '../../mock-components.utils';
-import { loadSvgWithFill, returnIconSize } from './icon-shape.business';
+import { returnIconSize } from './icon-shape.business';
+import { loadSvgWithFill } from '@/common/utils/svg.utils';
const iconShapeRestrictions: ShapeSizeRestrictions = {
minWidth: 25,
diff --git a/src/common/components/mock-components/front-rich-components/fab-button/fab-button.tsx b/src/common/components/mock-components/front-rich-components/fab-button/fab-button.tsx
new file mode 100644
index 00000000..29c11261
--- /dev/null
+++ b/src/common/components/mock-components/front-rich-components/fab-button/fab-button.tsx
@@ -0,0 +1,96 @@
+import { BASE_ICONS_URL, ShapeSizeRestrictions, ShapeType } from '@/core/model';
+import { forwardRef, useEffect, useState } from 'react';
+import { Circle, Group, Image } from 'react-konva';
+import { ShapeProps } from '../../shape.model';
+import { fitSizeToShapeSizeRestrictions } from '@/common/utils/shapes';
+import { useShapeProps } from '@/common/components/shapes/use-shape-props.hook';
+import { useGroupShapeProps } from '../../mock-components.utils';
+import { BASIC_SHAPE } from '../../front-components/shape.const';
+import { IconModal } from '@/pods/properties/components/icon-selector/modal';
+import { useModalDialogContext } from '@/core/providers/model-dialog-providers/model-dialog.provider';
+import { useCanvasContext } from '@/core/providers';
+import { loadSvgWithFill } from '@/common/utils/svg.utils';
+
+const fabButtonShapeRestrictions: ShapeSizeRestrictions = {
+ minWidth: 25,
+ minHeight: 25,
+ maxWidth: -1,
+ maxHeight: -1,
+ defaultWidth: 85,
+ defaultHeight: 85,
+};
+
+const shapeType: ShapeType = 'fabButton';
+
+export const getFabButtonShapeSizeRestrictions = (): ShapeSizeRestrictions =>
+ fabButtonShapeRestrictions;
+
+export const FabButtonShape = forwardRef((props, ref) => {
+ const { x, y, width, height, id, onSelected, otherProps, ...shapeProps } =
+ props;
+
+ const [iconImage, setIconImage] = useState(null);
+
+ const { openModal } = useModalDialogContext();
+ const { selectionInfo } = useCanvasContext();
+ const { updateOtherPropsOnSelected } = selectionInfo;
+
+ const restrictedSize = fitSizeToShapeSizeRestrictions(
+ fabButtonShapeRestrictions,
+ width,
+ height
+ );
+ const { width: restrictedWidth, height: restrictedHeight } = restrictedSize;
+
+ const radius = Math.min(restrictedWidth, restrictedHeight) / 2;
+ const center = radius;
+
+ const iconInfo = otherProps?.icon;
+ const iconSize = radius * 1.2;
+ const iconStroke = otherProps?.stroke || '#ffffff';
+
+ const { fill } = useShapeProps(otherProps, BASIC_SHAPE);
+ const commonGroupProps = useGroupShapeProps(
+ props,
+ restrictedSize,
+ shapeType,
+ ref
+ );
+
+ const handleDoubleClick = () => {
+ if (iconInfo) {
+ openModal(
+ updateOtherPropsOnSelected('icon', icon)}
+ />,
+ 'Choose Icon'
+ );
+ }
+ };
+
+ useEffect(() => {
+ if (iconInfo?.filename) {
+ loadSvgWithFill(`${BASE_ICONS_URL}${iconInfo.filename}`, iconStroke).then(
+ img => setIconImage(img)
+ );
+ }
+ }, [iconInfo?.filename, iconStroke]);
+
+ return (
+
+ {/* Background Circle */}
+
+ {/* Icon */}
+ {iconImage && (
+
+ )}
+
+ );
+});
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 6664c0ae..525b3d37 100644
--- a/src/common/components/mock-components/front-rich-components/index.ts
+++ b/src/common/components/mock-components/front-rich-components/index.ts
@@ -18,3 +18,4 @@ export * from './loading-indicator';
export * from './videoconference';
export * from './togglelightdark-shape';
export * from './gauge/gauge';
+export * from './fab-button/fab-button';
diff --git a/src/common/utils/svg.utils.ts b/src/common/utils/svg.utils.ts
new file mode 100644
index 00000000..573c60b5
--- /dev/null
+++ b/src/common/utils/svg.utils.ts
@@ -0,0 +1,14 @@
+export const loadSvgWithFill = async (url: string, fillColor: string) => {
+ const response = await fetch(url);
+ const svgText = await response.text();
+
+ const modifiedSvg = svgText.replace(/fill="[^"]*"/g, `fill="${fillColor}"`);
+
+ const svgBlob = new Blob([modifiedSvg], { type: 'image/svg+xml' });
+ const objectURL = URL.createObjectURL(svgBlob);
+
+ const img = new window.Image();
+ img.src = objectURL;
+
+ return img;
+};
diff --git a/src/core/model/index.ts b/src/core/model/index.ts
index 97dd6e3f..6885155c 100644
--- a/src/core/model/index.ts
+++ b/src/core/model/index.ts
@@ -84,7 +84,8 @@ export type ShapeType =
| 'rectangleLow'
| 'circleLow'
| 'textScribbled'
- | 'paragraphScribbled';
+ | 'paragraphScribbled'
+ | 'fabButton';
export const ShapeDisplayName: Record = {
multiple: 'multiple',
@@ -158,6 +159,7 @@ export const ShapeDisplayName: Record = {
circleLow: 'Circle',
textScribbled: 'Text Scribbled',
paragraphScribbled: 'Paragraph Scribbled',
+ fabButton: 'Fab Button',
};
export type EditType = 'input' | 'textarea' | 'imageupload';
diff --git a/src/pods/canvas/model/shape-other-props.utils.ts b/src/pods/canvas/model/shape-other-props.utils.ts
index d10ff84b..fe238aea 100644
--- a/src/pods/canvas/model/shape-other-props.utils.ts
+++ b/src/pods/canvas/model/shape-other-props.utils.ts
@@ -75,6 +75,17 @@ export const generateDefaultOtherProps = (
stroke: '#808080',
textColor: BASIC_SHAPE.DEFAULT_FILL_TEXT,
};
+ case 'fabButton':
+ return {
+ icon: {
+ name: 'chat',
+ filename: 'chat.svg',
+ searchTerms: ['chat', 'message', 'conversation', 'chatting'],
+ categories: ['IT'],
+ },
+ stroke: '#ffffff',
+ backgroundColor: '#A9A9A9',
+ };
case 'buttonBar':
return {
stroke: BASIC_SHAPE.DEFAULT_STROKE_COLOR,
diff --git a/src/pods/canvas/model/shape-size.mapper.ts b/src/pods/canvas/model/shape-size.mapper.ts
index e4f8fd74..397dddd5 100644
--- a/src/pods/canvas/model/shape-size.mapper.ts
+++ b/src/pods/canvas/model/shape-size.mapper.ts
@@ -64,6 +64,7 @@ import {
getVideoPlayerShapeSizeRestrictions,
getVideoconferenceShapeSizeRestrictions,
getGaugeShapeSizeRestrictions,
+ getFabButtonShapeSizeRestrictions,
// other imports
} from '@/common/components/mock-components/front-rich-components';
import {
@@ -171,6 +172,7 @@ const shapeSizeMap: Record ShapeSizeRestrictions> = {
circleLow: getCircleLowShapeSizeRestrictions,
textScribbled: getTextScribbledShapeRestrictions,
paragraphScribbled: getParagraphScribbledShapeRestrictions,
+ fabButton: getFabButtonShapeSizeRestrictions,
};
export default shapeSizeMap;
diff --git a/src/pods/canvas/shape-renderer/index.tsx b/src/pods/canvas/shape-renderer/index.tsx
index 7b06e31c..b591c9f9 100644
--- a/src/pods/canvas/shape-renderer/index.tsx
+++ b/src/pods/canvas/shape-renderer/index.tsx
@@ -48,6 +48,7 @@ import {
renderCalendar,
renderAppBar,
renderLoadingIndicator,
+ renderFabButton,
} from './simple-rich-components';
import {
renderDiamond,
@@ -209,6 +210,8 @@ export const renderShapeComponent = (
return renderLoadingIndicator(shape, shapeRenderedProps);
case 'videoconference':
return renderVideoconference(shape, shapeRenderedProps);
+ case 'fabButton':
+ return renderFabButton(shape, shapeRenderedProps);
case 'gauge':
return renderGauge(shape, shapeRenderedProps);
case 'imagePlaceholder':
diff --git a/src/pods/canvas/shape-renderer/simple-rich-components/fab-button.renderer.tsx b/src/pods/canvas/shape-renderer/simple-rich-components/fab-button.renderer.tsx
new file mode 100644
index 00000000..32c88469
--- /dev/null
+++ b/src/pods/canvas/shape-renderer/simple-rich-components/fab-button.renderer.tsx
@@ -0,0 +1,32 @@
+import { ShapeRendererProps } from '../model';
+import { ShapeModel } from '@/core/model';
+import { FabButtonShape } from '@/common/components/mock-components/front-rich-components/fab-button/fab-button';
+
+export const renderFabButton = (
+ 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 81078779..440b7ba4 100644
--- a/src/pods/canvas/shape-renderer/simple-rich-components/index.ts
+++ b/src/pods/canvas/shape-renderer/simple-rich-components/index.ts
@@ -20,3 +20,4 @@ export * from './video-player.renderer';
export * from './audio-player.renderer';
export * from './loading-indicator.renderer';
export * from './videoconference.renderer';
+export * from './fab-button.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 c8ceb040..439b4f92 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
@@ -36,4 +36,8 @@ export const mockRichComponentsCollection: ItemInfo[] = [
thumbnailSrc: '/rich-components/videoconference.svg',
type: 'videoconference',
},
+ {
+ thumbnailSrc: '/rich-components/fab-button.svg',
+ type: 'fabButton',
+ },
];