From 8ef9ed9f288f74b967b33a1a26a3a42266f33d2d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9C=D0=B0?=
 =?UTF-8?q?=D0=BB=D1=8E=D0=B3=D0=B8=D0=BD?= <d.malygin@iqdev.digital>
Date: Fri, 20 Sep 2024 18:11:46 +0500
Subject: [PATCH] image settings in progress

---
 src/app/components.d.ts                       |   5 +
 src/app/helpers/index.ts                      |   3 +-
 src/app/interfaces/ui.ts                      |  15 +
 .../entities/settings/ImageSettings.vue       | 318 ++++++++++++++----
 .../entities/settings/ParagraphSettings.vue   | 103 ++++--
 .../settings/lists/ImageSettingsList.vue      | 145 ++++++++
 .../settings/lists/ParagraphSettingsList.vue  |  88 ++---
 src/modules/entities/ImageItem.vue            |  36 +-
 src/modules/entities/ParagraphItem.vue        |   2 +-
 src/output.css                                |  48 +--
 src/shared/icons/AlignCenterIcon.vue          |  27 ++
 src/shared/icons/AlignLeftIcon.vue            |  27 ++
 src/shared/icons/AlignRightIcon.vue           |  27 ++
 src/shared/ui/Button.vue                      |  22 +-
 src/shared/ui/Slider.vue                      |  11 +-
 src/shared/ui/ToggleButton.vue                | 202 +++++++++++
 16 files changed, 887 insertions(+), 192 deletions(-)
 create mode 100644 src/app/interfaces/ui.ts
 create mode 100644 src/components/entities/settings/lists/ImageSettingsList.vue
 create mode 100644 src/shared/icons/AlignCenterIcon.vue
 create mode 100644 src/shared/icons/AlignLeftIcon.vue
 create mode 100644 src/shared/icons/AlignRightIcon.vue
 create mode 100644 src/shared/ui/ToggleButton.vue

diff --git a/src/app/components.d.ts b/src/app/components.d.ts
index 2210e21..be12a63 100644
--- a/src/app/components.d.ts
+++ b/src/app/components.d.ts
@@ -7,6 +7,9 @@ export {}
 /* prettier-ignore */
 declare module 'vue' {
   export interface GlobalComponents {
+    AlignCenterIcon: typeof import('./../shared/icons/AlignCenterIcon.vue')['default']
+    AlignLeftIcon: typeof import('./../shared/icons/AlignLeftIcon.vue')['default']
+    AlignRightIcon: typeof import('./../shared/icons/AlignRightIcon.vue')['default']
     App: typeof import('./App.vue')['default']
     AuthorizationForm: typeof import('./../modules/authorization/AuthorizationForm.vue')['default']
     BaseLoader: typeof import('./../shared/BaseLoader.vue')['default']
@@ -35,6 +38,7 @@ declare module 'vue' {
     ImageMenu: typeof import('./../components/entities/settings/ImageMenu.vue')['default']
     ImagePositionMenu: typeof import('./../components/entities/image/ImagePositionMenu.vue')['default']
     ImageSettings: typeof import('./../components/entities/settings/ImageSettings.vue')['default']
+    ImageSettingsList: typeof import('./../components/entities/settings/lists/ImageSettingsList.vue')['default']
     ImageSizeMenu: typeof import('./../components/entities/image/ImageSizeMenu.vue')['default']
     ImageStateMenu: typeof import('./../components/entities/image/ImageStateMenu.vue')['default']
     LogoAndLabel: typeof import('./../components/LogoAndLabel.vue')['default']
@@ -68,6 +72,7 @@ declare module 'vue' {
     TextPositionMenu: typeof import('./../components/entities/text/TextPositionMenu.vue')['default']
     TextSettings: typeof import('./../components/entities/settings/TextSettings.vue')['default']
     TextStateMenu: typeof import('./../components/entities/text/TextStateMenu.vue')['default']
+    ToggleButton: typeof import('./../shared/ui/ToggleButton.vue')['default']
     ToggleSwitch: typeof import('./../shared/ui/ToggleSwitch.vue')['default']
     TrashIcon: typeof import('./../shared/icons/TrashIcon.vue')['default']
     Tree: typeof import('./../shared/ui/Tree.vue')['default']
diff --git a/src/app/helpers/index.ts b/src/app/helpers/index.ts
index ecfbbd7..af4a3b0 100644
--- a/src/app/helpers/index.ts
+++ b/src/app/helpers/index.ts
@@ -54,11 +54,12 @@ export const editEntity = (newState: IEntity) => {
 export const deleteEntity = (entityUuid: string) => {
   const dataStore = useDataStore();
   const websocketStore = useWebsocketStore();
+  const page_uuid = cookies.get('current_page_uuid');
   const entities = dataStore.entities;
   const entityToDelete = entities.find((entity) => entity.entity_uuid === entityUuid);
   const data = {
     event: 'deleteEntity',
-    body: { ...entityToDelete }
+    body: { ...entityToDelete, page_uuid }
   };
   websocketStore.sendData(data);
 };
diff --git a/src/app/interfaces/ui.ts b/src/app/interfaces/ui.ts
new file mode 100644
index 0000000..cf6b6fe
--- /dev/null
+++ b/src/app/interfaces/ui.ts
@@ -0,0 +1,15 @@
+import type { TTheme } from '@/app/interfaces/environment';
+
+export interface IToggleButtonItem {
+  label: string;
+  textColor?: TTheme;
+  backgroundColor?: TTheme;
+  isLabelHidden?: boolean;
+  iconPos?: string;
+  textStyle?: 'bold' | 'italic';
+}
+export interface ISliderOption {
+  label: string;
+  value: number;
+  color?: string;
+}
diff --git a/src/components/entities/settings/ImageSettings.vue b/src/components/entities/settings/ImageSettings.vue
index ce7ba0d..e14ccf8 100644
--- a/src/components/entities/settings/ImageSettings.vue
+++ b/src/components/entities/settings/ImageSettings.vue
@@ -1,34 +1,20 @@
 <script setup lang="ts">
-import { useVModel } from '@vueuse/core';
-import { useDataStore } from '@/app/stores/data';
 import type { IImage } from '@/app/interfaces/entities';
-import { deleteEntity, editEntity } from '@/app/helpers';
+import { convertThemeToColorWhiteDefault, deleteEntity, editEntity } from '@/app/helpers';
+import type { TTheme } from '@/app/interfaces/environment';
+import cookies from '@/app/plugins/Cookie';
+import type { IToggleButtonItem } from '@/app/interfaces/ui';
 
 interface Props {
   entityData: IImage;
 }
 const props = defineProps<Props>();
-const emit = defineEmits(['update:entityData', 'openCropImageModal']);
-const entityData = useVModel(props, 'entityData', emit);
+const emit = defineEmits(['saveChanges']);
+const entityData = computed(() => props.entityData);
+const newEntityData = ref({ ...entityData.value });
+watch(entityData, () => (newEntityData.value = entityData.value));
+const isModal = ref<boolean>(false);
 
-const dataStore = useDataStore();
-const entities = computed(() => dataStore.entities);
-
-const addTitle = () => {
-  editEntity({
-    ...entityData.value,
-    title: 'Title',
-    entity_title_position: 'center',
-    font_size: entityData.value.font_size ?? '24'
-  });
-  entityData.value = { ...entityData.value, title: 'Title', entity_title_position: 'center' };
-};
-const removeTitle = () => {
-  const newState = { ...entityData.value };
-  newState.title = null;
-  editEntity({ ...newState });
-  entityData.value = newState;
-};
 const addText = () => {
   editEntity({
     ...entityData.value,
@@ -37,7 +23,7 @@ const addText = () => {
     font_size: entityData.value.font_size ?? '24',
     paragraph_size: 'full'
   });
-  entityData.value = { ...entityData.value, text: 'Text' };
+  newEntityData.value = { ...entityData.value, text: 'Text' };
 };
 const removeText = () => {
   const newState = { ...entityData.value };
@@ -45,55 +31,259 @@ const removeText = () => {
     newState[item] = null;
   });
   editEntity({ ...newState });
-  entityData.value = newState;
-};
-const editPosition = (position: 'left' | 'center' | 'right') => {
-  entityData.value.entity_position = position;
-  editEntity({ ...entityData.value, entity_position: position });
-};
-const editTitlePosition = (position: 'left' | 'center' | 'right') => {
-  entityData.value.entity_title_position = position;
-  editEntity({ ...entityData.value, entity_title_position: position });
-};
-const editTextPosition = (position: 'left' | 'right') => {
-  entityData.value.text_position = position;
-  editEntity({ ...entityData.value, text_position: position });
-};
-const editParagraphWidth = (widthMode: 'full' | 'half') => {
-  entityData.value.paragraph_size = widthMode;
-  editEntity({ ...entityData.value, paragraph_size: widthMode });
+  newEntityData.value = newState;
 };
 const changeFontSize = (newSize: '16' | '20' | '24' | '40' | '64') => {
   entityData.value.font_size = newSize;
   editEntity({ ...entityData.value, font_size: newSize });
 };
+const themeColor: TTheme = cookies.get('favorite_color');
+const themeColorConverted = convertThemeToColorWhiteDefault(themeColor);
+const isTitle = ref(!!entityData.value.title);
+const isText = ref(!!entityData.value.text);
+const isEntityWidthFull = ref(entityData.value.paragraph_size === 'full');
+
+const maxLines = computed(() => {
+  if (isTitle.value) {
+    return Math.floor(168 / 24);
+  } else {
+    return Math.floor(240 / 24);
+  }
+});
+const entityIsTitleOptions = ref([
+  {
+    label: 'Off',
+    value: false,
+    textStyle: 'bold'
+  },
+  {
+    label: 'On',
+    value: true,
+    textStyle: 'bold'
+  }
+]);
+const entityIsTextOptions = ref([
+  {
+    label: 'Off',
+    value: false,
+    textStyle: 'bold'
+  },
+  {
+    label: 'On',
+    value: true,
+    textStyle: 'bold'
+  }
+]);
+const entityPositionOptions = ref([
+  {
+    label: 'left',
+    isLabelHidden: true
+  },
+  {
+    label: 'center',
+    isLabelHidden: true
+  },
+  {
+    label: 'right',
+    isLabelHidden: true
+  }
+]);
+const entityTitlePositionOptions = ref([
+  {
+    label: 'left',
+    isLabelHidden: true
+  },
+  {
+    label: 'center',
+    isLabelHidden: true
+  },
+  {
+    label: 'right',
+    isLabelHidden: true
+  }
+]);
+const entityTextPositionOptions = ref([
+  {
+    label: 'Left',
+    value: 'left',
+    textStyle: 'bold'
+  },
+  {
+    label: 'Right',
+    value: 'right',
+    textStyle: 'bold'
+  }
+]);
+const entityParagraphSizeOptions = ref([
+  {
+    label: 'Half',
+    value: 'half',
+    textStyle: 'bold'
+  },
+  {
+    label: 'Full',
+    value: 'full',
+    textStyle: 'bold'
+  }
+]);
+const imageScaleOptions = ref([
+  {
+    label: 'x0.25',
+    value: 0,
+    color: 'var(--purple-700)'
+  },
+  {
+    label: 'x0.5',
+    value: 1,
+    color: 'var(--indigo-500)'
+  },
+  {
+    label: 'x0.75',
+    value: 2,
+    color: 'var(--sky-500)'
+  },
+  {
+    label: 'x1',
+    value: 3,
+    color: 'var(--green-500)'
+  },
+  {
+    label: 'x1.25',
+    value: 4,
+    color: 'var(--yellow-500)'
+  },
+  {
+    label: 'x1.5',
+    value: 5,
+    color: 'var(--orange-500)'
+  },
+  {
+    label: 'x1.75',
+    value: 6,
+    color: 'var(--red-500)'
+  },
+  {
+    label: 'x2',
+    value: 7,
+    color: 'var(--red-800)'
+  }
+]);
+const saveChanges = () => {
+  const entityPosition = isEntityWidthFull.value ? 'full' : 'half';
+  if (entityPosition !== entityData.value.entity_position) {
+    newEntityData.value.paragraph_size = entityPosition;
+  }
+  if (isTitle.value !== !!entityData.value.title) {
+    if (isTitle.value) {
+      newEntityData.value.title = 'Title';
+    } else {
+      newEntityData.value.title = null;
+    }
+  }
+  if (isText.value !== !!entityData.value.title) {
+    if (isText.value) {
+      newEntityData.value.title = 'Text';
+    } else {
+      newEntityData.value.title = null;
+    }
+  }
+  if (JSON.stringify(entityData) !== JSON.stringify(newEntityData.value)) {
+    emit('saveChanges', newEntityData.value);
+  }
+  isModal.value = false;
+};
 </script>
 
 <template>
-  <section style="height: 146px" class="speedDial absolute left-2 top-0 transition-all select-none">
-    <ImageStateMenu
-      :entityData="entityData"
-      class="h-12"
-      @deleteEntity="deleteEntity"
-      @addTitle="addTitle"
-      @removeTitle="removeTitle"
-      @addText="addText"
-      @removeText="removeText"
-      @openCropImageModal="$emit('openCropImageModal')"
-    />
-    <div v-if="entityData?.text || entityData?.title">
-      <TextFontMenu :entityData="entityData" class="h-12" @changeFontSize="changeFontSize" />
-    </div>
-    <div v-if="entities.length > 1">
-      <ImagePositionMenu
-        :entityData="entityData"
-        @editPosition="editPosition"
-        @editTitlePosition="editTitlePosition"
-        @editTextPosition="editTextPosition"
-        @editParagraphWidth="editParagraphWidth"
+  <button
+    :style="`background-color: ${themeColorConverted}`"
+    class="settings absolute left-2 top-0 select-none size-10 hover:brightness-75 transition-all cursor-pointer"
+    @click.prevent="isModal = true"
+  >
+    <SettingsIcon color="white" size="25" />
+  </button>
+  <Modal v-model:isVisible="isModal" theme="black" width="90%"
+    ><template #header><h3 class="w-max mx-auto">Edit paragraph</h3></template>
+    <div class="p-10 flex gap-16 items-center">
+      <ImageSettingsList
+        v-model:newEntityData="newEntityData"
+        v-model:isTitle="isTitle"
+        v-model:isText="isText"
+        v-model:isEntityWidthFull="isEntityWidthFull"
+        :themeColor="themeColor"
+        :entityIsTitleOptions="entityIsTitleOptions"
+        :entityIsTextOptions="entityIsTextOptions"
+        :entityPositionOptions="entityPositionOptions"
+        :entityTitlePositionOptions="entityTitlePositionOptions"
+        :entityTextPositionOptions="entityTextPositionOptions"
+        :entityParagraphSizeOptions="entityParagraphSizeOptions"
+        :imageScaleOptions="imageScaleOptions"
       />
+      <section
+        :style="`border-color: var(--${themeColor}-200); height: 450px`"
+        class="grow flex flex-col gap-4 p-4 min-h-full border-2 border-slate-100 border-dashed rounded-2xl"
+      >
+        <div :style="`justify-content: ${newEntityData.entity_position};`" class="flex">
+          <div
+            v-show="isTitle"
+            :style="`border-color: var(--${themeColor}-800);
+            justify-content: ${newEntityData.entity_title_position}; width: ${isEntityWidthFull ? '100%' : '50%'}`"
+            class="flex text-2xl font-bold text-center px-2 py-4 border-2 border-dashed rounded-2xl"
+          >
+            <h3 class="w-2/3 overflow-ellipsis overflow-hidden whitespace-nowrap">
+              {{ newEntityData.title ?? 'Title' }}
+            </h3>
+          </div>
+        </div>
+        <div class="flex">
+          <div :style="`justify-content: ${newEntityData.entity_position}`" class="flex">
+            <div
+              v-show="isText"
+              :style="`border-color: var(--${themeColor}-400); width: ${isEntityWidthFull ? '100%' : '50%'};`"
+              class="h-full p-4 pb-2 border-2 border-dashed rounded-2xl overflow-hidden"
+            >
+              <p class="pb-0 overflow-hidden contain-inline-size text">
+                {{ newEntityData.text ?? 'Text' }}
+              </p>
+            </div>
+          </div>
+          <img class="h-max" :src="newEntityData.imageUrl" alt="" />
+        </div>
+      </section>
+      <div class="absolute top-4 right-16 z-10 hover:brightness-80 transition-all">
+        <Button label="Delete" textColor="white" theme="red" textStyle="bold" size="medium">
+          <template #icon>
+            <TrashIcon color="white" size="25" />
+          </template>
+        </Button>
+      </div>
+      <div
+        class="absolute top-4 left-4 z-10 hover:brightness-80 transition-all"
+        @click.prevent="saveChanges"
+      >
+        <Button label="Save" textColor="white" :theme="themeColor" textStyle="bold" size="medium">
+          <template #icon>
+            <SaveIcon color="white" size="25" />
+          </template>
+        </Button>
+      </div>
     </div>
-  </section>
+  </Modal>
 </template>
 
-<style scoped></style>
+<style scoped>
+.text {
+  --max-lines: v-bind(maxLines);
+  overflow: hidden;
+
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: var(--max-lines);
+}
+.settings {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  border-radius: 50%;
+}
+</style>
diff --git a/src/components/entities/settings/ParagraphSettings.vue b/src/components/entities/settings/ParagraphSettings.vue
index d9f1dd1..8f434d8 100644
--- a/src/components/entities/settings/ParagraphSettings.vue
+++ b/src/components/entities/settings/ParagraphSettings.vue
@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { convertThemeToColorWhiteDefault, editEntity } from '@/app/helpers';
+import { convertThemeToColorWhiteDefault, deleteEntity, editEntity } from '@/app/helpers';
 import type { IParagraph } from '@/app/interfaces/entities';
 import type { TTheme } from '@/app/interfaces/environment';
 import cookies from '@/app/plugins/Cookie';
@@ -9,18 +9,19 @@ interface Props {
 }
 const props = defineProps<Props>();
 const emit = defineEmits(['saveChanges']);
-const entityData = props.entityData;
-let newEntityData = ref({ ...entityData });
+const entityData = computed(() => props.entityData);
+const newEntityData = ref({ ...entityData.value });
+watch(entityData, () => (newEntityData.value = entityData.value));
 const isModal = ref<boolean>(false);
-
+const isModalToDeleteParagraph = ref<boolean>(false);
 const changeFontSize = (newSize: '16' | '20' | '24' | '40' | '64') => {
-  entityData.font_size = newSize;
-  editEntity({ ...entityData, font_size: newSize });
+  entityData.value.font_size = newSize;
+  editEntity({ ...entityData.value, font_size: newSize });
 };
 const themeColor: TTheme = cookies.get('favorite_color');
 const themeColorConverted = convertThemeToColorWhiteDefault(themeColor);
-const isTitle = ref(!!entityData.title);
-const isEntityWidthFull = ref(entityData.paragraph_size === 'full');
+const isTitle = ref(!!entityData.value.title);
+const isEntityWidthFull = ref(entityData.value.paragraph_size === 'full');
 
 const maxLines = computed(() => {
   if (isTitle.value) {
@@ -29,51 +30,81 @@ const maxLines = computed(() => {
     return Math.floor(240 / 24);
   }
 });
-const entityPositionOptions = [
+const entityIsTitleOptions = ref([
+  {
+    label: 'Off',
+    value: false,
+    textStyle: 'bold'
+  },
+  {
+    label: 'On',
+    value: true,
+    textStyle: 'bold'
+  }
+]);
+const isEntityWidthFullOptions = ref([
+  {
+    label: 'Half',
+    value: false,
+    textStyle: 'bold'
+  },
+  {
+    label: 'Full',
+    value: true,
+    textStyle: 'bold'
+  }
+]);
+const entityPositionOptions = ref([
   {
     label: 'left',
-    value: 0
+    isLabelHidden: true
   },
   {
     label: 'center',
-    value: 1
+    isLabelHidden: true
   },
   {
     label: 'right',
-    value: 2
+    isLabelHidden: true
   }
-];
-const entityTitlePositionOptions = [
+]);
+const entityTitlePositionOptions = ref([
   {
     label: 'left',
-    value: 0
+    isLabelHidden: true
   },
   {
     label: 'center',
-    value: 1
+    isLabelHidden: true
   },
   {
     label: 'right',
-    value: 2
+    isLabelHidden: true
   }
-];
+]);
 const saveChanges = () => {
   const entityPosition = isEntityWidthFull.value ? 'full' : 'half';
-  if (entityPosition !== entityData.entity_position) {
+  if (entityPosition !== entityData.value.entity_position) {
     newEntityData.value.paragraph_size = entityPosition;
   }
-  if (isTitle.value !== !!entityData.title) {
+  if (isTitle.value !== !!entityData.value.title) {
     if (isTitle.value) {
       newEntityData.value.title = 'Title';
     } else {
       newEntityData.value.title = null;
     }
   }
-  if (JSON.stringify(entityData) !== JSON.stringify(newEntityData.value)) {
-    console.log('they are different, I will save it. New data:', newEntityData.value);
+  if (JSON.stringify(entityData.value) !== JSON.stringify(newEntityData.value)) {
     emit('saveChanges', newEntityData.value);
   }
-  console.log('newEntityData.value :', newEntityData.value);
+  isModal.value = false;
+};
+const toggleConfirmToDeleteParagraph = () => {
+  isModalToDeleteParagraph.value = !isModalToDeleteParagraph.value;
+};
+const deleteParagraph = () => {
+  deleteEntity(entityData.value.entity_uuid);
+  isModalToDeleteParagraph.value = false;
   isModal.value = false;
 };
 </script>
@@ -86,14 +117,33 @@ const saveChanges = () => {
   >
     <SettingsIcon color="white" size="25" />
   </button>
-  <Modal v-model:isVisible="isModal" theme="black" width="70%"
+  <Modal v-model:isVisible="isModal" theme="black" width="90%"
     ><template #header><h3 class="w-max mx-auto">Edit paragraph</h3></template>
+    <Modal v-model:isVisible="isModalToDeleteParagraph" theme="black" width="30%"
+      ><p class="font-bold pt-4 mb-4 text-center">Are you sure you want to delete this element?</p>
+      <div class="flex justify-between">
+        <Button
+          label="Yes, delete"
+          theme="red"
+          textColor="white"
+          textStyle="bold"
+          @click.prevent="deleteParagraph"
+        />
+        <Button
+          label="Cancel"
+          theme="white"
+          textColor="black"
+          @click.prevent="toggleConfirmToDeleteParagraph"
+        /></div
+    ></Modal>
     <div class="p-10 flex gap-16 items-center">
       <ParagraphSettingsList
         v-model:newEntityData="newEntityData"
         v-model:isTitle="isTitle"
         v-model:isEntityWidthFull="isEntityWidthFull"
         :themeColor="themeColor"
+        :entityIsTitleOptions="entityIsTitleOptions"
+        :isEntityWidthFullOptions="isEntityWidthFullOptions"
         :entityPositionOptions="entityPositionOptions"
         :entityTitlePositionOptions="entityTitlePositionOptions"
       />
@@ -121,7 +171,10 @@ const saveChanges = () => {
           </div>
         </div>
       </section>
-      <div class="absolute top-4 right-16 z-10 hover:brightness-80 transition-all">
+      <div
+        class="absolute top-4 right-16 z-10 hover:brightness-80 transition-all"
+        @click.prevent="toggleConfirmToDeleteParagraph"
+      >
         <Button label="Delete" textColor="white" theme="red" textStyle="bold" size="medium">
           <template #icon>
             <TrashIcon color="white" size="25" />
diff --git a/src/components/entities/settings/lists/ImageSettingsList.vue b/src/components/entities/settings/lists/ImageSettingsList.vue
new file mode 100644
index 0000000..535f2a6
--- /dev/null
+++ b/src/components/entities/settings/lists/ImageSettingsList.vue
@@ -0,0 +1,145 @@
+<script setup lang="ts">
+import type { TTheme } from '@/app/interfaces/environment';
+import { useVModels } from '@vueuse/core';
+import type { IImage } from '@/app/interfaces/entities';
+import ToggleButton from '@/shared/ui/ToggleButton.vue';
+import type { ISliderOption, IToggleButtonItem } from '@/app/interfaces/ui';
+
+interface Props {
+  newEntityData: IImage;
+  isTitle: boolean;
+  isText: boolean;
+  isEntityWidthFull: boolean;
+  themeColor: TTheme;
+  entityIsTitleOptions: IToggleButtonItem[];
+  entityIsTextOptions: IToggleButtonItem[];
+  entityPositionOptions: IToggleButtonItem[];
+  entityTitlePositionOptions: IToggleButtonItem[];
+  entityParagraphSizeOptions: IToggleButtonItem[];
+  entityTextPositionOptions: IToggleButtonItem[];
+  imageScaleOptions: ISliderOption[];
+}
+const props = defineProps<Props>();
+const emit = defineEmits([
+  'update:newEntityData',
+  'update:isTitle',
+  'update:isText',
+  'update:isEntityWidthFull'
+]);
+const { newEntityData, isTitle, isText, isEntityWidthFull } = useVModels(props, emit);
+</script>
+
+<template>
+  <div>
+    <div class="flex flex-col items-center" style="min-width: 30%; min-height: 100px">
+      <p class="py-2">Image size</p>
+      <Slider
+        v-model:value="newEntityData.image_scale"
+        width="300px"
+        size="small"
+        max="7"
+        :options="imageScaleOptions"
+        :isSmooth="true"
+        backgroundColor="white"
+        :theme="themeColor"
+      />
+    </div>
+    <ul class="flex gap-2 h-full" style="min-width: 35%">
+      <li class="flex flex-col items-center justify-between gap-4" style="min-width: 150px">
+        <div>
+          <p class="py-2 text-center">Title</p>
+          <ToggleButton
+            v-model:value="isTitle"
+            :theme="themeColor"
+            :options="entityIsTitleOptions"
+            rounded="true"
+            :border="themeColor"
+            :activeBGColor="themeColor"
+          />
+        </div>
+        <div style="height: 108px" class="flex gap-8 items-center justify-between col-span-2">
+          <Transition name="fading">
+            <div v-show="isTitle" class="flex flex-col items-center">
+              <p class="py-2 text-center">Title position</p>
+              <ToggleButton
+                v-model:value="newEntityData.entity_title_position"
+                :theme="themeColor"
+                :options="entityTitlePositionOptions"
+                rounded="true"
+                :border="themeColor"
+                :activeBGColor="themeColor"
+              >
+                <template #1Icon><AlignLeftIcon /></template>
+                <template #2Icon><AlignCenterIcon /></template>
+                <template #3Icon><AlignRightIcon /></template
+              ></ToggleButton>
+            </div>
+          </Transition>
+        </div>
+        <div style="height: 108px" class="flex gap-8 items-center justify-between col-span-2">
+          <Transition name="fading">
+            <div v-show="isText" class="flex flex-col items-center">
+              <p class="py-2">Text position</p>
+              <ToggleButton
+                v-model:value="newEntityData.text_position"
+                :theme="themeColor"
+                :options="entityTextPositionOptions"
+                rounded="true"
+                :border="themeColor"
+                :activeBGColor="themeColor"
+              />
+            </div>
+          </Transition>
+        </div>
+      </li>
+      <li class="flex flex-col items-center justify-between gap-4" style="min-width: 150px">
+        <div>
+          <p class="py-2 text-center">Text</p>
+          <div class="flex items-center">
+            <ToggleButton
+              v-model:value="isText"
+              :theme="themeColor"
+              :options="entityIsTextOptions"
+              rounded="true"
+              :border="themeColor"
+              :activeBGColor="themeColor"
+            />
+          </div>
+        </div>
+        <div style="height: 108px" class="flex gap-8 items-center justify-between">
+          <div class="flex flex-col items-center">
+            <p class="py-2 text-center">Block position</p>
+            <ToggleButton
+              v-model:value="newEntityData.entity_position"
+              :theme="themeColor"
+              :options="entityPositionOptions"
+              rounded="true"
+              :border="themeColor"
+              :activeBGColor="themeColor"
+              ><template #1Icon><AlignLeftIcon /></template>
+              <template #2Icon><AlignCenterIcon /></template>
+              <template #3Icon><AlignRightIcon /></template
+            ></ToggleButton>
+          </div>
+        </div>
+        <div style="height: 108px" class="flex gap-8 items-center justify-between">
+          <Transition name="fading">
+            <div v-show="isText" class="flex flex-col items-center">
+              <p class="py-2">Text width</p>
+              <ToggleButton
+                v-model:value="newEntityData.paragraph_size"
+                :theme="themeColor"
+                :options="entityParagraphSizeOptions"
+                rounded="true"
+                :border="themeColor"
+                :activeBGColor="themeColor"
+              />
+            </div>
+          </Transition>
+        </div>
+      </li>
+    </ul>
+  </div>
+</template>
+
+<style scoped></style>
diff --git a/src/components/entities/settings/lists/ParagraphSettingsList.vue b/src/components/entities/settings/lists/ParagraphSettingsList.vue
index 236e324..ce2277a 100644
--- a/src/components/entities/settings/lists/ParagraphSettingsList.vue
+++ b/src/components/entities/settings/lists/ParagraphSettingsList.vue
@@ -2,20 +2,18 @@
 import type { TTheme } from '@/app/interfaces/environment';
 import { useVModels } from '@vueuse/core';
 import type { IParagraph } from '@/app/interfaces/entities';
+import ToggleButton from '@/shared/ui/ToggleButton.vue';
+import type { IToggleButtonItem } from '@/app/interfaces/ui';
 
 interface Props {
   newEntityData: IParagraph;
   isTitle: boolean;
   isEntityWidthFull: boolean;
   themeColor: TTheme;
-  entityPositionOptions: {
-    label: string;
-    value: number;
-  }[];
-  entityTitlePositionOptions: {
-    label: string;
-    value: number;
-  }[];
+  entityIsTitleOptions: IToggleButtonItem[];
+  isEntityWidthFullOptions: IToggleButtonItem[];
+  entityPositionOptions: IToggleButtonItem[];
+  entityTitlePositionOptions: IToggleButtonItem[];
 }
 const props = defineProps<Props>();
 const emit = defineEmits(['update:newEntityData', 'update:isTitle', 'update:isEntityWidthFull']);
@@ -23,66 +21,72 @@ const { newEntityData, isTitle, isEntityWidthFull } = useVModels(props, emit);
 </script>
 
 <template>
-  <ul class="flex gap-8 h-full" style="min-width: 35%">
-    <li class="flex flex-col items-center gap-4" style="min-width: 150px">
+  <ul class="flex gap-2 h-full" style="min-width: 35%">
+    <li class="flex flex-col items-center gap-4" style="min-width: 150px; min-height: 108px">
       <div>
         <p class="py-2 text-center">Title</p>
         <div class="flex items-center">
-          Off
-          <ToggleSwitch v-model:isActive="isTitle" class="mx-2" :theme="themeColor" />
-          On
+          <ToggleButton
+            v-model:value="isTitle"
+            :theme="themeColor"
+            :options="entityIsTitleOptions"
+            rounded="true"
+            :border="themeColor"
+            :activeBGColor="themeColor"
+          />
         </div>
       </div>
-      <div style="height: 108px" class="flex gap-8 items-center justify-between col-span-2">
+      <div class="flex items-center justify-between">
         <Transition name="fading">
           <div v-show="isTitle" class="flex flex-col items-center">
             <p class="py-2 text-center">Title position</p>
-            <Slider
+            <ToggleButton
               v-model:value="newEntityData.entity_title_position"
               :theme="themeColor"
-              width="150"
-              size="small"
-              isSmooth="true"
-              backgroundColor="white"
-              min="0"
-              max="2"
-              step="1"
               :options="entityTitlePositionOptions"
-            />
+              rounded="true"
+              size="small"
+              :border="themeColor"
+              :activeBGColor="themeColor"
+            >
+              <template #1Icon><AlignLeftIcon /></template>
+              <template #2Icon><AlignCenterIcon /></template>
+              <template #3Icon><AlignRightIcon /></template>
+            </ToggleButton>
           </div>
         </Transition>
       </div>
     </li>
-    <li class="flex flex-col items-center gap-4" style="min-width: 150px">
+    <li class="flex flex-col items-center gap-4" style="min-width: 150px; min-height: 108px">
       <div>
-        <p class="py-2 text-center">Paragraph width</p>
-        <div class="flex items-center">
-          Half
-          <ToggleSwitch
-            v-model:isActive="isEntityWidthFull"
-            class="mx-2"
+        <div class="flex flex-col items-center">
+          <p class="py-2 text-center">Paragraph width</p>
+          <ToggleButton
+            v-model:value="isEntityWidthFull"
             :theme="themeColor"
-            :negativeTheme="themeColor"
+            :options="isEntityWidthFullOptions"
+            rounded="true"
+            :border="themeColor"
+            :activeBGColor="themeColor"
           />
-          Full
         </div>
       </div>
-      <div style="height: 108px" class="flex gap-8 items-center justify-between col-span-2">
+      <div class="flex gap-8 items-center justify-between">
         <Transition name="fading">
           <div v-show="!isEntityWidthFull" class="flex flex-col items-center">
             <p class="py-2">Paragraph position</p>
-            <Slider
+            <ToggleButton
               v-model:value="newEntityData.entity_position"
               :theme="themeColor"
-              width="150"
-              size="small"
-              isSmooth="true"
-              backgroundColor="white"
-              min="0"
-              max="2"
-              step="1"
               :options="entityPositionOptions"
-            />
+              rounded="true"
+              size="small"
+              :border="themeColor"
+              :activeBGColor="themeColor"
+              ><template #1Icon><AlignLeftIcon /></template>
+              <template #2Icon><AlignCenterIcon /></template>
+              <template #3Icon><AlignRightIcon /></template>
+            </ToggleButton>
           </div>
         </Transition>
       </div>
diff --git a/src/modules/entities/ImageItem.vue b/src/modules/entities/ImageItem.vue
index 34303df..a674a22 100644
--- a/src/modules/entities/ImageItem.vue
+++ b/src/modules/entities/ImageItem.vue
@@ -1,16 +1,24 @@
 <script setup lang="ts">
-import { useVModel, useWindowSize } from '@vueuse/core';
+import { useWindowSize } from '@vueuse/core';
 import type { IImage } from '@/app/interfaces/entities';
 import { editEntity } from '@/app/helpers';
 import { cropImage } from '@/app/helpers/images';
+import type { IEntity } from '@/app/interfaces/environment';
+import { useDataStore } from '@/app/stores/data';
 
 interface Props {
   entityData: IImage;
   isEditMode: boolean;
 }
 const props = defineProps<Props>();
-const emit = defineEmits(['update:entityData']);
-const entityData = useVModel(props, 'entityData', emit);
+const entityData = ref(props.entityData);
+
+const dataStore = useDataStore();
+const entities = computed(() => dataStore.entities);
+const entityIndex = computed(() =>
+  entities.value.findIndex((entity: IEntity) => entity.entity_uuid === props.entityData.entity_uuid)
+);
+const entitiesLength = computed(() => entities.value.length);
 
 const isModalCropImage = ref<boolean>(false);
 const { width: windowWidth } = useWindowSize();
@@ -27,6 +35,10 @@ const editTitle = () => {
 const editText = () => {
   editEntity({ ...entityData.value, text: entityData.value.text });
 };
+const saveChanges = (newState: IImage) => {
+  editEntity(newState);
+  entityData.value = newState;
+};
 const saveImage = async (newUrl: string, newWidth: number, newHeight: number) => {
   entityData.value.imageUrl = newUrl;
   entityData.value.image_width = newWidth;
@@ -47,7 +59,6 @@ const openCropImageModal = () => (isModalCropImage.value = true);
 
 <template>
   <section
-    ref="container"
     :class="[
       'entityContainer relative flex px-16 transition-all',
       {
@@ -87,9 +98,9 @@ const openCropImageModal = () => (isModalCropImage.value = true);
             style="min-height: 100px; max-height: 700px"
             class="object-contain order-1"
           />
-          <div class="speedDialSize absolute left-0 top-0 transition-all select-none">
-            <ImageSizeMenu v-if="isEditMode" :entityData="entityData" @scaleImage="scaleImage" />
-          </div>
+          <!--          <div class="speedDialSize absolute left-0 top-0 transition-all select-none">-->
+          <!--            <ImageSizeMenu v-if="isEditMode" :entityData="entityData" @scaleImage="scaleImage" />-->
+          <!--          </div>-->
         </div>
         <div
           v-if="entityData.text_position"
@@ -113,12 +124,13 @@ const openCropImageModal = () => (isModalCropImage.value = true);
           />
         </div>
       </div>
-      <ImageMenu
-        v-if="isEditMode"
-        v-model:entityData="entityData"
-        @openCropImageModal="openCropImageModal"
+      <ImageSettings v-if="isEditMode" :entityData="entityData" @saveChanges="saveChanges" />
+      <EntityPositionSettings
+        v-if="isEditMode && entitiesLength > 1"
+        :entityUuid="entityData.entity_uuid"
+        :entityIndex="entityIndex"
+        :entitiesLength="entitiesLength"
       />
-      <EntityPositionSettings :entityUuid="entityData.entity_uuid" />
     </div>
   </section>
 </template>
diff --git a/src/modules/entities/ParagraphItem.vue b/src/modules/entities/ParagraphItem.vue
index a925c20..fc61f3a 100644
--- a/src/modules/entities/ParagraphItem.vue
+++ b/src/modules/entities/ParagraphItem.vue
@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { useElementSize, useTextareaAutosize } from '@vueuse/core';
+import { useTextareaAutosize } from '@vueuse/core';
 import type { IParagraph } from '@/app/interfaces/entities';
 import { editEntity } from '@/app/helpers';
 import { useDataStore } from '@/app/stores/data';
diff --git a/src/output.css b/src/output.css
index f527fbd..54856cd 100644
--- a/src/output.css
+++ b/src/output.css
@@ -554,40 +554,6 @@ video {
   --tw-contain-style:  ;
 }
 
-.container {
-  width: 100%;
-}
-
-@media (min-width: 640px) {
-  .container {
-    max-width: 640px;
-  }
-}
-
-@media (min-width: 768px) {
-  .container {
-    max-width: 768px;
-  }
-}
-
-@media (min-width: 1024px) {
-  .container {
-    max-width: 1024px;
-  }
-}
-
-@media (min-width: 1280px) {
-  .container {
-    max-width: 1280px;
-  }
-}
-
-@media (min-width: 1536px) {
-  .container {
-    max-width: 1536px;
-  }
-}
-
 .pointer-events-none {
   pointer-events: none;
 }
@@ -716,6 +682,11 @@ video {
   margin-bottom: 1rem;
 }
 
+.mx-4 {
+  margin-left: 1rem;
+  margin-right: 1rem;
+}
+
 .-mb-2 {
   margin-bottom: -0.5rem;
 }
@@ -825,6 +796,11 @@ video {
   height: 100%;
 }
 
+.h-max {
+  height: -moz-max-content;
+  height: max-content;
+}
+
 .min-h-full {
   min-height: 100%;
 }
@@ -985,6 +961,10 @@ video {
   gap: 2rem;
 }
 
+.gap-0 {
+  gap: 0px;
+}
+
 .overflow-auto {
   overflow: auto;
 }
diff --git a/src/shared/icons/AlignCenterIcon.vue b/src/shared/icons/AlignCenterIcon.vue
new file mode 100644
index 0000000..e7edd7a
--- /dev/null
+++ b/src/shared/icons/AlignCenterIcon.vue
@@ -0,0 +1,27 @@
+<script setup lang="ts">
+interface Props {
+  color?: string;
+  size?: string | number;
+}
+defineProps<Props>();
+</script>
+
+<template>
+  <svg
+    :width="size ?? '25px'"
+    :height="size ?? '25px'"
+    viewBox="0 0 24 24"
+    fill="none"
+    xmlns="http://www.w3.org/2000/svg"
+  >
+    <path
+      d="M3 6H21M3 14H21M17 10H7M17 18H7"
+      :stroke="color ?? '#000000'"
+      stroke-width="2"
+      stroke-linecap="round"
+      stroke-linejoin="round"
+    />
+  </svg>
+</template>
+
+<style scoped></style>
diff --git a/src/shared/icons/AlignLeftIcon.vue b/src/shared/icons/AlignLeftIcon.vue
new file mode 100644
index 0000000..52a46b6
--- /dev/null
+++ b/src/shared/icons/AlignLeftIcon.vue
@@ -0,0 +1,27 @@
+<script setup lang="ts">
+interface Props {
+  color?: string;
+  size?: string | number;
+}
+defineProps<Props>();
+</script>
+
+<template>
+  <svg
+    :width="size ?? '25px'"
+    :height="size ?? '25px'"
+    viewBox="0 0 24 24"
+    fill="none"
+    xmlns="http://www.w3.org/2000/svg"
+  >
+    <path
+      d="M3 10H16M3 14H21M3 18H16M3 6H21"
+      :stroke="color ?? '#000000'"
+      stroke-width="2"
+      stroke-linecap="round"
+      stroke-linejoin="round"
+    />
+  </svg>
+</template>
+
+<style scoped></style>
diff --git a/src/shared/icons/AlignRightIcon.vue b/src/shared/icons/AlignRightIcon.vue
new file mode 100644
index 0000000..b9d49d8
--- /dev/null
+++ b/src/shared/icons/AlignRightIcon.vue
@@ -0,0 +1,27 @@
+<script setup lang="ts">
+interface Props {
+  color?: string;
+  size?: string | number;
+}
+defineProps<Props>();
+</script>
+
+<template>
+  <svg
+    :width="size ?? '25px'"
+    :height="size ?? '25px'"
+    viewBox="0 0 24 24"
+    fill="none"
+    xmlns="http://www.w3.org/2000/svg"
+  >
+    <path
+      d="M8 10H21M3 14H21M8 18H21M3 6H21"
+      :stroke="color ?? '#000000'"
+      stroke-width="2"
+      stroke-linecap="round"
+      stroke-linejoin="round"
+    />
+  </svg>
+</template>
+
+<style scoped></style>
diff --git a/src/shared/ui/Button.vue b/src/shared/ui/Button.vue
index e5be3f0..161a8fd 100644
--- a/src/shared/ui/Button.vue
+++ b/src/shared/ui/Button.vue
@@ -59,14 +59,14 @@ interface Props {
 const props = defineProps<Props>();
 const themeColor = computed(() => convertThemeToColorWhiteDefault(props.theme));
 const textColor = computed(() => convertThemeToColorBlackDefault(props.textColor));
-const borderColor = computed(() => convertThemeToColorBlackDefault(props.border));
+const borderColor = computed(() =>
+  props.border ? convertThemeToColorBlackDefault(props.border) : ''
+);
 const textSize = computed(() => {
-  if (!props?.size) return '16px';
+  if (!props?.size || props.size === 'medium') return '16px';
   switch (props.size) {
     case 'small':
       return '12px';
-    case 'medium':
-      return '16px';
     case 'large':
       return '20px';
     case 'extraLarge':
@@ -74,12 +74,10 @@ const textSize = computed(() => {
   }
 });
 const buttonPadding = computed(() => {
-  if (!props?.size) return '0.75rem 0.5rem';
+  if (!props?.size || props.size === 'medium') return '0.75rem 0.5rem';
   switch (props.size) {
     case 'small':
       return '0.5rem 0.375rem';
-    case 'medium':
-      return '0.75rem 0.5rem';
     case 'large':
       return '1.2rem 0.8rem';
     case 'extraLarge':
@@ -93,10 +91,11 @@ const buttonPadding = computed(() => {
     :class="[
       'button',
       {
-        'flex-column': iconPos === 'top' || iconPos === 'bottom'
+        'flex-column': iconPos === 'top' || iconPos === 'bottom',
+        border: borderColor
       }
     ]"
-    :style="`padding: ${buttonPadding}; border: 1px solid ${borderColor}`"
+    :style="`padding: ${buttonPadding}`"
   >
     <span :style="`background-color: ${themeColor}`" class="background"></span>
     <span
@@ -154,7 +153,7 @@ const buttonPadding = computed(() => {
   flex-direction: column;
 }
 .order-1 {
-  order: 1;
+  order: -1;
 }
 .bold {
   font-weight: bold;
@@ -162,4 +161,7 @@ const buttonPadding = computed(() => {
 .italic {
   font-style: italic;
 }
+.border {
+  border: 2px solid v-bind(borderColor);
+}
 </style>
diff --git a/src/shared/ui/Slider.vue b/src/shared/ui/Slider.vue
index 59fff7f..0a4fcf6 100644
--- a/src/shared/ui/Slider.vue
+++ b/src/shared/ui/Slider.vue
@@ -17,6 +17,7 @@ interface Props {
   options?: {
     label: string;
     value: number;
+    color?: string;
   }[];
 }
 const props = defineProps<Props>();
@@ -75,7 +76,7 @@ const themeBackground = computed(() => convertThemeToColorBlackDefault(props.bac
     <div v-if="options?.length">
       <ul
         class="marksList"
-        :style="`width: ${width ?? 200}px; margin-bottom: 5px; font-size: 10px`"
+        :style="`width: ${width ?? 200}px; margin-bottom: 5px; font-size: 10px; padding: 0 15px`"
       >
         <li v-for="option of options" :key="option">|</li>
       </ul>
@@ -89,7 +90,11 @@ const themeBackground = computed(() => convertThemeToColorBlackDefault(props.bac
         ]"
       >
         <template v-for="option of options" :key="option.value">
-          <option :value="option.value" :label="option.label"></option>
+          <option
+            :value="option.value"
+            :label="option.label"
+            :style="`color: ${option.color ?? 'white'}`"
+          ></option>
         </template>
       </datalist>
     </div>
@@ -98,7 +103,7 @@ const themeBackground = computed(() => convertThemeToColorBlackDefault(props.bac
 
 <style scoped>
 .slideContainer {
-  width: 100%;
+  width: v-bind(width);
 }
 .slider {
   -webkit-appearance: none;
diff --git a/src/shared/ui/ToggleButton.vue b/src/shared/ui/ToggleButton.vue
new file mode 100644
index 0000000..d47fd27
--- /dev/null
+++ b/src/shared/ui/ToggleButton.vue
@@ -0,0 +1,202 @@
+<script setup lang="ts">
+import { computed } from 'vue';
+import { convertThemeToColorBlackDefault, convertThemeToColorWhiteDefault } from '@/app/helpers';
+import { useVModel } from '@vueuse/core';
+type TTheme =
+  | 'white'
+  | 'slate'
+  | 'blue'
+  | 'sky'
+  | 'teal'
+  | 'lime'
+  | 'green'
+  | 'yellow'
+  | 'orange'
+  | 'pink'
+  | 'fuchsia'
+  | 'purple'
+  | 'indigo'
+  | 'rose'
+  | 'red'
+  | 'black';
+interface Props {
+  value: any;
+  options: {
+    label: string;
+    value?: any;
+    textColor?: TTheme;
+    backgroundColor?: TTheme;
+    isLabelHidden?: boolean;
+    iconPos?: string;
+    textStyle?: 'bold' | 'italic';
+  }[];
+  border?: TTheme;
+  size?: 'small' | 'medium' | 'large' | 'extraLarge';
+  theme?: TTheme;
+  rounded?: any;
+  activeBGColor?: TTheme;
+}
+const props = defineProps<Props>();
+const emit = defineEmits(['update:value']);
+const value = useVModel(props, 'value', emit);
+const activeBGColor = computed(() =>
+  props.activeBGColor ? convertThemeToColorBlackDefault(props.activeBGColor) : ''
+);
+const borderColor = computed(() =>
+  props.border ? convertThemeToColorBlackDefault(props.border) : ''
+);
+const textSize = computed(() => {
+  if (!props?.size || props.size === 'medium') return '16px';
+  switch (props.size) {
+    case 'small':
+      return '12px';
+    case 'large':
+      return '20px';
+    case 'extraLarge':
+      return '24px';
+  }
+});
+const buttonPadding = computed(() => {
+  if (!props?.size || props.size === 'medium') return '0.75rem 0.5rem';
+  switch (props.size) {
+    case 'small':
+      return '0.5rem 0.375rem';
+    case 'large':
+      return '1.2rem 0.8rem';
+    case 'extraLarge':
+      return '1.8rem 1.2rem';
+  }
+});
+const buttonHeight = computed(() => {
+  if (!props?.size || props.size === 'medium') return '40px';
+  switch (props.size) {
+    case 'small':
+      return '24px';
+    case 'large':
+      return '68px';
+    case 'extraLarge':
+      return '114px';
+  }
+});
+</script>
+
+<template>
+  <div
+    :class="[
+      'buttonGroup',
+      {
+        'rounded-full': props.rounded,
+        border: borderColor
+      }
+    ]"
+  >
+    <button
+      v-for="(item, index) of options"
+      :key="item.label"
+      :class="[
+        'button',
+        {
+          'flex-column': item.iconPos === 'top' || item.iconPos === 'bottom'
+        }
+      ]"
+      :style="`padding: ${buttonPadding}`"
+      @click.prevent="value = item?.value ?? item.label"
+    >
+      <span
+        :style="`background-color: ${activeBGColor && (value === item.value || value === item.label) ? activeBGColor : convertThemeToColorWhiteDefault(item.backgroundColor)}`"
+        :class="[
+          'background',
+          {
+            'rounded-left': index === 0,
+            'rounded-left-full': index === 0 && props.rounded,
+            'rounded-right': index === options.length - 1,
+            'rounded-right-full': index === options.length - 1 && props.rounded
+          }
+        ]"
+      ></span>
+      <span
+        v-if="!item.isLabelHidden"
+        :style="`color: ${convertThemeToColorBlackDefault(item.textColor)}; font-size: ${textSize}`"
+        :class="[
+          'text',
+          {
+            bold: item.textStyle === 'bold',
+            italic: item.textStyle === 'italic'
+          }
+        ]"
+        >{{ item.label ?? 'Button' }}</span
+      >
+      <slot
+        :class="[
+          'icon',
+          {
+            'order-1': item.iconPos === 'left' || item.iconPos === 'top'
+          }
+        ]"
+        :name="`${index + 1}Icon`"
+      />
+    </button>
+  </div>
+</template>
+
+<style scoped>
+.buttonGroup {
+  display: flex;
+  border-radius: 0.5rem;
+  position: relative;
+}
+.button {
+  position: relative;
+  display: flex;
+  gap: 8px;
+  align-items: center;
+  user-select: none;
+}
+.button:hover .background {
+  filter: brightness(90%);
+}
+.background {
+  width: 100%;
+  height: 100%;
+  position: absolute;
+  z-index: -2;
+  top: 0;
+  left: 0;
+  transition: filter 0.2s ease-in-out;
+}
+.icon {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.flex-column {
+  flex-direction: column;
+}
+.order-1 {
+  order: -1;
+}
+.bold {
+  font-weight: bold;
+}
+.italic {
+  font-style: italic;
+}
+.border {
+  border: 2px solid v-bind(borderColor);
+}
+.rounded-left {
+  border-radius: 0.5rem 0 0 0.5rem;
+}
+.rounded-left-full {
+  border-radius: v-bind(buttonHeight) 0 0 v-bind(buttonHeight);
+}
+.rounded-right {
+  border-radius: 0 0.5rem 0.5rem 0;
+}
+.rounded-right-full {
+  border-radius: 0 v-bind(buttonHeight) v-bind(buttonHeight) 0;
+}
+.rounded-full {
+  border-radius: v-bind(buttonHeight);
+}
+</style>
-- 
GitLab