/ Gists / 15 Alert , Tailwind + CVA
On gists

15 Alert , Tailwind + CVA

cdruc vue

Alert.vue Raw #

// https://tallpad.com/series/vuejs-misc/lessons/build-an-alert-component-using-vue-tailwindcss-and-cva


<script setup>
import {InformationCircleIcon, XMarkIcon, CheckCircleIcon, ExclamationTriangleIcon, XCircleIcon} from "@heroicons/vue/20/solid/index.js";
import {computed} from "vue";
import {cva} from "class-variance-authority";

const props = defineProps({
  intent: {
    type: String,
    validator(value) {
      return ["info", "success", "danger", "warning"].includes(value);
    },
    default: "info",
  },
  title: String,
  show: {
    type: Boolean,
    default: true,
  },
  onDismiss: Function,
});

const containerClass = computed(() => {
  return cva("flex p-4 rounded-md space-x-3", {
    variants: {
      intent: {
        info: "bg-blue-100",
        success: "bg-green-100",
        warning: "bg-orange-100",
        danger: "bg-red-100",
      },
    },
  })({
    intent: props.intent,
  });
});

const iconClass = computed(() => {
  return cva("w-6 h-6", {
    variants: {
      intent: {
        info: "text-blue-700",
        success: "text-green-600",
        warning: "text-orange-400",
        danger: "text-red-500",
      },
    },
  })({
    intent: props.intent,
  });
});

const titleClass = computed(() => {
  return cva("font-medium", {
    variants: {
      intent: {
        info: "text-blue-900",
        success: "text-green-900",
        warning: "text-orange-900",
        danger: "text-red-900"
      },
    },
  })({
    intent: props.intent,
  });
});

const contentClass = computed(() => {
  return cva("text-sm", {
    variants: {
      intent: {
        info: "text-blue-800",
        success: "text-green-800",
        warning: "text-orange-800",
        danger: "text-red-800",
      },
    },
  })({
    intent: props.intent,
  });
});

const closeButtonClass = computed(() => {
  return cva("p-0.5 rounded-md -m-1", {
    variants: {
      intent: {
        info: "text-blue-900/70 hover:text-blue-900 hover:bg-blue-200",
        success: "text-green-900/70 hover:text-green-900 hover:bg-green-200",
        warning: "text-orange-900/70 hover:text-orange-900 hover:bg-orange-200",
        danger: "text-red-900/70 hover:text-red-900 hover:bg-red-200",
      },
    },
  })({
    intent: props.intent,
  });
});

const iconComponent = computed(() => {
  const icons = {
    success: CheckCircleIcon,
    warning: ExclamationTriangleIcon,
    danger: XCircleIcon,
    info: InformationCircleIcon,
  };

  return icons[props.intent];
});

function dismiss() {
  if (props.onDismiss) {
    props.onDismiss();
  }
}
</script>

<template>
  <div v-if="props.show" :class="containerClass">
    <div class="shrink-0">
      <component :is="iconComponent" :class="iconClass" />
    </div>
    <div class="flex-1 space-y-2">
      <h2 :class="titleClass">
        {{ props.title }}
      </h2>
      <div :class="contentClass"><slot /></div>
    </div>
    <div class="shrink-0" v-if="props.onDismiss">
      <button @click="dismiss()" :class="closeButtonClass">
        <XMarkIcon class="w-6 h-6" />
      </button>
    </div>
  </div>
</template>