Logo

dev-resources.site

for different kinds of informations.

Building Message Component in Vue3

Published at
6/1/2024
Categories
vue
vue3
message
component
Author
markliu2013
Categories
4 categories in total
vue
open
vue3
open
message
open
component
open
Author
11 person written this
markliu2013
open
Building Message Component in Vue3

In most web products, global message components are widely used. They often appear in scenarios for user feedback, information prompts, and dialogues with the system. If you use the traditional component writing method, you need to import the component and register it in components, then call it in the template in the form of a tag, pass in custom props attributes, and trigger events through emit.

What we want the component is easy to use, just call a method.

Message.text("This is default message");
Message.error({
    content: "error message",
    duration: 3000,
    close: true
});
Enter fullscreen mode Exit fullscreen mode

Component API Design

// Message type:
["text", "success", "error"]

// Message option
[String]: message content
[Object]: message config


text [String] "" message content
duration [Number] 0 milliseconds to close, 0 means it will not close automatically.
close [Boolean] false show close icon or not

// call api
Message[type](option);
Enter fullscreen mode Exit fullscreen mode

Component File Structure

|--- message
    |--- src
    |   |--- style
    |   |   |--- index.less
    |   |--- message-item.vue // component template of one message
    |   |--- message.js // render the component instance
    |   |--- Instance.js // component instance
    |---index.js // export component

Enter fullscreen mode Exit fullscreen mode

Template

The template is simple. The outer layer is wrapped by an animation component, and the message display and closing are controlled by v-show. The content part includes an icon, message text, and a configurable manual close button.

<template>
  <!-- message list -->
  <Transition name="slide-fade">
    <div class="message-container" v-show="visible">
      <!-- content -->
      <div class="message-content">
        <!-- Icon is determined by the message type. No icon is configured for text type -->
        <div class="message-icon" v-if="config.icon">
          <i :class="config.icon"></i>
        </div>

        <!-- message text -->
        <span v-text="config.content"></span>

        <!-- click to close -->
        <div class="option" v-if="config.close">
          <i class="ri-close-fill" @click="onClose"></i>
        </div>
      </div>
    </div>
  </Transition>
</template>
Enter fullscreen mode Exit fullscreen mode

message icon

The icon is determined by the type in the API call. The icon type is determined when creating the instance. Here, the open-source icon library Remix Icon is referenced. You can visit: remixicon.cn

Style

Create styles and animations.

@radius: 4px;
@normalHeight: 34px;

.message {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  text-align: center;
  box-sizing: border-box;
  z-index: 9999;
  transform: translateZ(9999px);
  padding-top: 28px;
  transition: top 0.4s ease;

  .message-container {
    margin-bottom: 14px;

    .message-content {
      display: inline-block;
      padding: 0 18px;
      height: @normalHeight;
      text-align: left;
      line-height: @normalHeight;
      font-size: 14px;
      font-weight: 400;
      border-radius: @radius;
      color: #fff;
      background: #000;
      .option {
        display: inline-block;
        pointer-events: all;
        margin-left: 18px;

        i {
          font-size: 18px;
          font-weight: 400;
          margin-top: -3px;
          display: inline-block;
          box-sizing: border-box;
          vertical-align: middle;
          cursor: pointer;
          color: #d9d9d9;
          transition: color 0.2s ease;

          &:hover {
            color: #ff7c75;
            transition: color 0.2s ease;
          }
        }
      }
    }

    .message-icon {
      display: inline-block;

      i {
        font-size: 18px;
        font-weight: 400;
        margin-top: -3px;
        margin-right: 6px;
        display: inline-block;
        box-sizing: border-box;
        vertical-align: middle;
      }

      .ri-checkbox-circle-fill {
        color: #58c05b;
      }

      .ri-close-circle-fill {
        color: #fd4f4d;
        width: 20px;
        height: 20px;
        background-color: red;
        -webkit-mask-image: url('close-circle-fill.svg');
        mask-image: url('close-circle-fill.svg');
      }
    }
  }
}
.slide-fade-enter-active {
  transition: all 0.2s ease-out;
}

.slide-fade-leave-active {
  transition: all 0.2s ease;
}

.slide-fade-enter-from,
.slide-fade-leave-to {
  transform: translateY(-50px);
  opacity: 0;
}
Enter fullscreen mode Exit fullscreen mode

template script

In the component, rendering and unmounting are implemented by obtaining the passed-in config configuration and remove. The onOpen and onClose methods are used to control message opening and manual closing. The specific code is as follows.

<script setup>
import { ref } from 'vue';
import './style/index.less';

const props = defineProps({
  config: { type: Object, default: () => undefined }, // message config
  remove: { type: Function, default: () => undefined } // callback of remove message
});

const visible = ref(false);

const onClose = () => {
  visible.value = false;
  setTimeout(() => {
    props.remove();
  }, 200);
};

const onOpen = (config) => {
  setTimeout(() => {
    visible.value = true;
  }, 10);

  // remove message in duration
  if (config.duration !== 0) {
    setTimeout(() => {
      onClose();
    }, config.duration);
  }
};

onOpen(props.config);
</script>
Enter fullscreen mode Exit fullscreen mode

Create Component Instance

Next, in Instance.js, we will write APIs for creating, mounting, and destroying components when the component is called. At the top, import the method for creating a Vue instance and the component template we just wrote:

import { createApp } from 'vue';
import MessageItem from './message-item.vue';
Enter fullscreen mode Exit fullscreen mode

Declare instance operation methods, accepting a message configuration parameter, cfg.

/**
 * Message 
 * @param {Object} cfg
 */
const createInstance = cfg => {
    const config = cfg || {}
        // 1、Create a wrapper container, and set the outer Class attribute and message count.

    // 2、Create an instance and mount it to the body.

    // 3、unmount method, and recount after unmounting.
}
export default createInstance
Enter fullscreen mode Exit fullscreen mode

1. Create a wrapper container and set the outer Class attribute

Create a DIV as an outer container to wrap the component, and set the corresponding class attribute.

let messageNode = document.createElement('div')
let attr = document.createAttribute("class")
attr.value = "message"
messageNode.setAttributeNode(attr)
Enter fullscreen mode Exit fullscreen mode

Message count, we define the height of a message pop-up as 54 px. When multiple messages are queued to open, we make each component staggered by setting the top value.

const height = 54 // Height of a single message box

const messageList = document.getElementsByClassName('message')
messageNode.style.top = `${messageList.length * height}px`
Enter fullscreen mode Exit fullscreen mode

2. Create an instance and mount it to the body.

const app = createApp(MessageItem, {
  config,
  remove() {
    handleRemove()// Remove the element. After the message is closed, unmount and remove it from the DOM.
  }
})

// Mount the instance and append it to the end of the body
app.vm = app.mount(messageNode)
document.body.appendChild(messageNode)

app.close = () => {
  handleRemove()
}

return app

Enter fullscreen mode Exit fullscreen mode

3. Define methods for unmounting and resetting the top value.

const handleRemove = ()=>{
  app.unmount(messageNode)
  document.body.removeChild(messageNode)
  resetMsgTop()
 }

const resetMsgTop = () => {
  for (let i = 0; i < messageList.length; i++) {
    messageList[i].style.top = `${i * height}px`
  }
}
Enter fullscreen mode Exit fullscreen mode

API for rendering instances

Read the configuration and render in message.js.

import createInstance from './instance.js';

/**
 * Read configuration and render Message
 * @param {Object} typeCfg message type
 * @param {Object/String} cfg config
 */
function renderMsg(typeCfg = {}, cfg = '') {
  // Allow direct input of message content,
  // therefore, it is necessary to determine the type of the passed-in cfg
  const isContent = typeof cfg === 'string';

  // Integrate custom configurations
  cfg = isContent
    ? {
        content: cfg
      }
    : cfg;

  const config = Object.assign({}, typeCfg, cfg); // 合并配置

  const {
    type = 'text', // message type
    content = '', // message content
    icon = '', // message icon
    duration = 3000, // Automatic closing delay time
    close = false // Whether to display the close button
  } = config;

  // create instance
  return createInstance({
    type,
    content,
    duration,
    icon,
    close
  });
}
Enter fullscreen mode Exit fullscreen mode

Expose APIs such as text, success, error, etc.

export default {
  // text type
  text(cfg = '') {
    const textCfg = {
      type: 'text',
      icon: ''
    };

    return renderMsg(textCfg, cfg);
  },
  // success type
  success(cfg = '') {
    const successCfg = {
      type: 'success',
      icon: 'ri-checkbox-circle-fill'
    };

    return renderMsg(successCfg, cfg);
  },
  // error type
  error(cfg = '') {
    const errorCfg = {
      type: 'error',
      icon: 'ri-close-circle-fill'
    };
    return renderMsg(errorCfg, cfg);
  }
};
Enter fullscreen mode Exit fullscreen mode

Finally, expose this component in the index.js.

import Message from './src/Message.js';

export default Message;
Enter fullscreen mode Exit fullscreen mode

Checkout out full code: https://github.com/markliu2013/StellarNovaUI/tree/main/packages/components/src/message

After understanding the code, You can style and change icon base on your project.

component Article's
30 articles in total
Favicon
Build a note app with JavaScript component.
Favicon
Key characteristic of Component-Based Architecture
Favicon
React Component Libraries: Overview of 19 Top Libs
Favicon
Styling Components in React 🧢
Favicon
Building a Seamless OTP Input Field in React: A Step-by-Step Guide
Favicon
MithrilJS component with state management
Favicon
[Off Topic] Nano introdução do framework Angular para Devs do back
Favicon
Comparing Vue Component Documentation tools
Favicon
Laravel 11 Custom Component File Structure
Favicon
Building Message Component in Vue3
Favicon
Aplicando paginação em um componente Select
Favicon
How much does it cost to repair an outdoor LED display?
Favicon
Global toast in Vue3
Favicon
Unveiling the Hidden Gem of React: The Power of Compound Components
Favicon
Controlled vs Uncontrolled Components in React
Favicon
React components -(Class component v/s function component)
Favicon
3 Ways to Create React Components with Bit
Favicon
Client or Server component ?
Favicon
Desenvolvimento de Componentes Assíncronos com Vue.js
Favicon
NAND Flash vs NOR Flash: Differences between them
Favicon
Link Component in React Router
Favicon
Guia de Components - para quem tem pressa!
Favicon
How to exchange data between Angular components
Favicon
Component Testing with Cypress and Reactjs
Favicon
React - Higher Order Components (HOC)
Favicon
How to Create Component Library Like Material UI or Mantine UI?
Favicon
Looking forward to adding Algolia's DOCSEARCH to Mantine DataTable
Favicon
Cypress Component Testing vs React Testing Library - the complete comparison
Favicon
Creating Custom Component for NPS Feedback
Favicon
Maximize your Angular code reusability using <NgTemplateOutlet>

Featured ones: