Commit d9a81a79 authored by Дмитрий Малюгин's avatar Дмитрий Малюгин 🕓
Browse files

feat: start to try to add correct animation if many components in 'Toast'

parent f672dd30
Loading
Loading
Loading
Loading
+20 −2
Original line number Diff line number Diff line
@@ -176,13 +176,31 @@ const selectOptions = [
const knob = ref(0);
const pbValue = ref(0);
const toast = ref(false);
const toast2 = ref(false);
const toast3 = ref(false);
const toast4 = ref(false);
const openDrawer = () => (visibleDrawer.value = true);
</script>

<template>
  <h2 class="title gradient-text">Playground</h2>
  <Button label="Open toast" @click="toast = true" />
  <Toast v-model="toast" type="info" position="bottomRight" width="500px" />
  <Button
    theme="black"
    label="Open all toasts"
    @click="
      () => {
        toast = true;
        toast2 = true;
        toast3 = true;
        toast4 = true;
      }
    "
  />
  <Button label="Open toast" @click="toast2 = true" />
  <Toast v-model="toast" type="success" position="top" width="500px" />
  <Toast v-model="toast4" type="info" position="top" width="500px" />
  <Toast v-model="toast3" type="warn" position="top" width="500px" />
  <Toast v-model="toast2" type="error" position="top" width="500px" />
  <Rating theme="red">
    <template #offIcon>
      <CrossIcon color="red" />
+1 −1
Original line number Diff line number Diff line
@@ -298,7 +298,7 @@ export interface IToastProps {
  text?: string;
  header?: string;
  icon?: TIcon;
  position?: TExpandedPosition;
  position?: Exclude<TExpandedPosition, 'left' | 'right'>;
  width?: string;
  static?: boolean;
}
+3 −1
Original line number Diff line number Diff line
@@ -20,7 +20,7 @@ const meta: Meta = {
    size: { control: 'select', options: ['small', 'normal', 'large', 'huge'] },
    position: {
      control: 'select',
      options: ['topRight', 'bottomRight', 'bottomLeft', 'topLeft', 'top', 'right', 'bottom', 'left'],
      options: ['topRight', 'bottomRight', 'bottomLeft', 'topLeft', 'top', 'bottom'],
    },
    active: { control: 'boolean' },
    static: { control: 'boolean' },
@@ -103,6 +103,8 @@ export const Small: Story = {
    active: true,
    size: 'small',
    static: true,
    theme: 'black',
    width: '400px',
  },
};

+77 −14
Original line number Diff line number Diff line
<script setup lang="ts">
import type { IToastProps } from '@interfaces/componentsProps';
import { computed, watch } from 'vue';
import { computed, onBeforeUnmount, onMounted, ref, type Ref, watch } from 'vue';
import { convertThemeToColor, getValueFromSize } from '@helpers/common';
import type { TToastType } from '@interfaces/componentsProp';
import { iconsSet } from '@/common/constants/icons';
@@ -36,20 +36,61 @@ const typeToIcon: Record<TToastType, string> = {
  error: 'CrossRound',
};

const active = defineModel<boolean>();
const active = defineModel() as Ref<boolean>;

let toastsContainer = document.querySelector(`.toasts-container.${props.position}`);
if (!toastsContainer) {
  toastsContainer = document.createElement('div');
  toastsContainer.classList.add('toasts-container');
  toastsContainer.classList.add(`${props.position}`);
  document.body.appendChild(toastsContainer);
}

const toast = ref();
watch(toast, () => {
  if (toast.value) {
    toastsContainer?.appendChild(toast.value);
  }
});
const activeToastsCount = ref(0);
let observer = null;

const updateCount = () => {
  activeToastsCount.value = document.querySelectorAll(`.toast-container.${props.position}.active`).length;
};
const initObserver = () => {
  const config = { attributeOldValue: true, attributes: true, characterData: true, childList: true, subtree: true };
  observer = new MutationObserver(() => {
    console.log('update type: ', props.type);
    updateCount();
  });
  observer.observe(toastsContainer, config);
};

onMounted(() => {
  updateCount();
  initObserver();
});
onBeforeUnmount(() => {
  if (observer) {
    observer.disconnect();
  }
});

const themeColor = computed<TThemeColor>(() => props.theme ?? typeToTheme[props.type]);
const header = computed<string>(() => props.header ?? typeToHeader[props.type]);
const icon = computed(() => props.icon ?? typeToIcon[props.type]);

const color = computed(() => convertThemeToColor(themeColor.value, '400'));
const whiteOrBlack = computed(() => (themeColor.value === 'white' ? 'black' : 'white'));
const backgroundColor = computed(() =>
const color = computed(() =>
  convertThemeToColor(
    themeColor.value === 'white' ? 'black' : themeColor.value === 'black' ? 'white' : themeColor.value,
    '800',
    '400',
  ),
);
const whiteOrBlack = computed(() => (themeColor.value === 'white' ? 'black' : 'white'));
const backgroundColor = computed(() =>
  convertThemeToColor(themeColor.value, themeColor.value === 'white' || themeColor.value === 'black' ? '500' : '800'),
);
const borderColor = computed(() => convertThemeToColor(themeColor.value, '500'));
const fontSize = computed(() => getValueFromSize(props.size, ['12px', '16px', '20px', '24px']));
const padding = computed(
@@ -84,32 +125,54 @@ const styles = computed(() => {
  }
  return `${positionParts.value[0]}: -100%; ${positionParts.value[1]}: 20px`;
});
const activeStyles = computed(() => {
  if (positionParts.value.length === 1) return `${positionParts.value[0]}: 20px`;
  return `${positionParts.value[0]}: 20px; ${positionParts.value[1]}: 20px`;
});

const calcActiveStyles = () => {
  const activeToasts = document.querySelectorAll(`.toast-container.${props.position}.active`);
  let activeToastsHeight = 0;
  for (const toast of activeToasts) {
    activeToastsHeight += toast.offsetHeight;
  }

  const offset = activeToastsHeight + 20 * activeToasts.length + 20 + 'px';
  console.log('activeToasts: ', `${positionParts.value[0]}: ${offset}`);

  if (positionParts.value.length === 1) return `${positionParts.value[0]}: ${offset}`;
  return `${positionParts.value[0]}: ${offset}; ${positionParts.value[1]}: 20px`;
};
const closeToast = () => (active.value = false);

let timeout: number;
const key = Math.random();

if (props.duration) {
  watch(active, () => {
    if (active.value) {
      timeout = setTimeout(() => (active.value = false), (props.duration as number) * 1000);
      toastsContainer?.setAttribute('key', String(key));
    } else {
      clearTimeout(timeout);
      toastsContainer?.removeAttribute('key');
    }
  });
}
watch(activeToastsCount, () => {
  console.log(activeToastsCount.value);
  calcActiveStyles();
});
</script>

<template>
  <section
    class="toast-container"
    ref="toast"
    :class="[
      `toast-container ${position}`,
      {
        active,
      },
    ]"
    :style="`position: ${static ? 'relative' : 'fixed'};
    ${styles};
    ${active ? activeStyles : null}`"
    ${active ? calcActiveStyles() : null}`"
  >
    <h3 class="toast-header" :style="`font-size: calc(${fontSize} + 4px)`">
      <component :is="iconsSet[icon]" :color="color" :size="iconSize" />
@@ -131,7 +194,7 @@ if (props.duration) {
  z-index: 9999;
  padding: v-bind(padding);
  border: 1px solid v-bind(borderColor);
  border-radius: 5px;
  border-radius: 7px;
  width: v-bind(width);
  transition: all 0.4s ease-in-out;
  ::before {
@@ -144,7 +207,7 @@ if (props.duration) {
    width: 100%;
    height: 100%;
    opacity: 0.55;
    border-radius: 4px;
    border-radius: 6px;
  }
}
.toast-header {