Material Design for Bootstrap 4 (Vue version) 소개

Angular5에서 사용한 MDBootstrap의 Vue 버전이 있었으므로 넣어 보았다.
내비게이션이 MDBootstrap 그대로라면 vue-router의 router-link를 사용할 수 없어,
구성 요소를 약간 로컬로 변경했습니다.

완성된 화면


  • vue-cli의 초기 화면에 위로 네비게이션 붙였다



  • 필요한 패키지 설치


    yarn add mdbootstrap bootstrap-vue classnames
    yarn add @types/classnames --dev
    

    템플릿의 구성 요소를 git clone으로 떨어뜨립니다.


  • Angular, React는 패키지 추가로 갈 수 있지만, Vue는 왜 템플릿을 git clone하여 프로젝트를 만들거나, vue-cli에서 템플릿을 사용하여 새로 만들게 되어 기존 프로젝트에 도입 불편했다
  • git clone https://github.com/mdbootstrap/Vue-Bootstrap-with-Material-Design.git mdb
    

    템플릿에서 vue-cli로 만든 기존 프로젝트로 소스 복사


  • 구성 요소
  • mkdir ~/works/todo/src/conponents/mdbootstrap
    cp ~/works/mdb/src/components/* ~/works/todo/src/mdbootstrap
    
  • mixins
  • mkdir ~/works/todo/src/mixins
    cp ~/works/mdb/src/mixins/* ~/works/todo/src/mixins
    

    main.ts에 리플 효과의 CSS를 import 추가



    src/main.ts
    import Vue from 'vue';
    import App from './App.vue';
    import router from './router';
    import store from './store';
    import './registerServiceWorker';
    
    import 'bootstrap/dist/css/bootstrap.css';
    import 'mdbootstrap/css/mdb.css';
    + import './components/mdbootstrap/Waves.css';
    
    Vue.config.productionTip = false;
    
    new Vue({
      router,
      store,
      render: (h) => h(App),
    }).$mount('#app');
    

    App.vue 네비게이션을 MDBootstrap 네비게이션으로 변경



    src/App.vue
    <template>
      <div id="app">
        <!--Navbar-->
        <navbar position="top" class="indigo navbar-dark" name="ToDo" to="/" scrolling>
          <navbar-collapse>
            <navbar-nav>
              <navbar-item to="/" active exact waves-fixed>Home</navbar-item>
              <navbar-item to="/about" waves-fixed>About</navbar-item>
              <navbar-item to="/tasks" waves-fixed>Tasks</navbar-item>
              <!-- Dropdown -->
              <dropdown tag="li" class="nav-item">
                <dropdown-toggle tag="a" navLink color="indigo" waves-fixed>Dropdown</dropdown-toggle>
                <dropdown-menu>
                  <dropdown-item>Action</dropdown-item>
                  <dropdown-item>Another action</dropdown-item>
                  <dropdown-item>Something else here</dropdown-item>
                </dropdown-menu>
              </dropdown>
            </navbar-nav>
              <!-- Search form -->
            <form class="form-inline">
              <mdinput type="text" placeholder="Search" aria-label="Search" label navInput waves waves-fixed/>
            </form>
          </navbar-collapse>
        </navbar>
    
        <div class="container">
          <router-view/>
        </div>
      </div>
    </template>
    
    <script>
    import Navbar from "@/components/mdbootstrap/Navbar.vue";
    import NavbarItem from "@/components/mdbootstrap/NavbarItem.vue";
    import NavbarNav from "@/components/mdbootstrap/NavbarNav.vue";
    import NavbarCollapse from "@/components/mdbootstrap/NavbarCollapse.vue";
    import Container from "@/components/mdbootstrap/Container.vue";
    import Dropdown from "@/components/mdbootstrap/Dropdown.vue";
    import DropdownItem from "@/components/mdbootstrap/DropdownItem.vue";
    import DropdownMenu from "@/components/mdbootstrap/DropdownMenu.vue";
    import DropdownToggle from "@/components/mdbootstrap/DropdownToggle.vue";
    import drop from "@/mixins/drop";
    import Mdinput from "@/components/mdbootstrap/MdInput.vue";
    
    export default {
      components: {
        Navbar,
        NavbarItem,
        NavbarNav,
        NavbarCollapse,
        Container,
        Dropdown,
        DropdownItem,
        DropdownMenu,
        DropdownToggle,
        Mdinput
      },
      mixins: [drop]
    };
    </script>
    
    <style lang="scss">
    #app {
      font-family: "Avenir", Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
    }
    
    .navbar .dropdown-menu a:hover {
      color: inherit !important;
    }
    .fixed-top {
      position: sticky;
    }
    </style>
    

    Navbar의 구성 요소를 router-link로 전환하도록 변경


  • active 메뉴의 배경색을 바꾸는 부분도 움직이지 않았기 때문에 변경했다. App.vue측의/의 라우팅에는 exact 속성을 붙이고 있다.

  • src/components/mdbootstrap/Navbar.vue
    <template>
      <nav :class="className" :is="tag">
    -    <a :href="href" class="navbar-brand">{{name}}
    +    <router-link :to="to" class="navbar-brand">{{name}}
          <img v-if="src" :src="src" :alt="alt"/>
    -    </a>
    +    </router-link>
        <button class="navbar-toggler" type="button" data-toggle="collapse" :data-target="target" aria-controls="navbarSupportedContent"
            aria-expanded="false" aria-label="Toggle navigation" v-on:click="toggle">
          <span class="navbar-toggler-icon"></span>
        </button>
        <slot></slot>
      </nav>
    </template>
    
    
    <script>
    //
    //
    //
    //
    //
    //
    //
    //
    //
    //
    //
    //
    //
    //
    
    import classNames from "classnames";
    
    export default {
      props: {
        tag: {
          type: String,
          default: "nav"
        },
        expand: {
          type: String,
          default: "large"
        },
        position: {
          type: String
        },
    -    href: {
    +    to: {
          type: String
        },
        src: {
          type: String
        },
        alt: {
          type: String
        },
        name: {
          type: String
        },
        target: {
          type: String,
          default: "#navbarSupportedContent"
        },
        scrolling: {
          type: Boolean,
          default: false
        }
      },
      data() {
        return {
          className: classNames(
            "navbar",
            "navbar-dark",
            this.expand === "small"
              ? "navbar-expand-sm"
              : this.expand === "medium"
                ? "navbar-expand-md"
                : this.expand === "large" ? "navbar-expand-lg" : "navbar-expand-lx",
            this.position === "top"
              ? "fixed-top"
              : this.position === "bottom" ? "fixed-bottom" : "",
            this.scrolling ? "scrolling-navbar" : ""
          ),
          scrolled: false,
          toggleClicked: true
        };
      },
      methods: {
        toggle(e) {
          if (this.toggleClicked) {
            this.collapse.classList.toggle("show-navbar");
            this.collapse.classList.remove("hide-navbar");
            this.collapse.classList.toggle("collapse");
            this.collapse.style.overflow = "hidden";
            this.collapseOverflow = setTimeout(() => {
              this.collapse.style.overflow = "initial";
            }, 300);
            this.toggleClicked = false;
          } else {
            this.collapse.classList.add("hide-navbar");
            this.collapse.classList.toggle("show-navbar");
            this.collapse.style.overflow = "hidden";
            this.collapseOverflow = setTimeout(() => {
              this.collapse.classList.toggle("collapse");
              this.collapse.style.overflow = "initial";
            }, 300);
            this.toggleClicked = true;
          }
        },
        handleScroll() {
          if (window.scrollY > 100 && this.scrolled === false) {
            this.$el.style.paddingTop = 5 + "px";
            this.$el.style.paddingBottom = 5 + "px";
            this.scrolled = true;
          } else if (window.scrollY < 100 && this.scrolled === true) {
            this.$el.style.paddingTop = 12 + "px";
            this.$el.style.paddingBottom = 12 + "px";
            this.scrolled = false;
          }
        }
      },
      mounted() {
        this.collapse = this.$el.children.navbarSupportedContent;
        this.collapse.classList.add("collapse");
      },
      created() {
        window.addEventListener("scroll", this.handleScroll);
      },
      destroyed() {
        window.removeEventListener("scroll", this.handleScroll);
      }
    };
    </script>
    
    <style scoped>
    .scrolling-navbar {
      transition: padding 0.5s;
    }
    .nav-item {
      position: relative;
    }
    </style>
    

    NavbarItem의 구성 요소를 router-link에서 전환하도록 변경



    src/components/mdbootstrap/NavbarItem.vue
    <template>
      <li :is="tag" :class="[className, {'ripple-parent': waves}]" @click="wave">
    -    <a :href="href" class="nav-link"><slot></slot></a>
    +    <router-link :to="to" class="nav-link" :exact="exact"><slot></slot></router-link>
      </li>
    </template>
    
    <script>
    //
    //
    //
    //
    //
    //
    
    import classNames from "classnames";
    import waves from "../../mixins/waves";
    
    export default {
      props: {
        tag: {
          type: String,
          default: "li"
        },
        active: {
          type: Boolean,
          default: false
        },
    -    href: {
    +    to: {
          type: String,
          default: "#"
        },
        waves: {
          type: Boolean,
          default: true
        },
        wavesFixed: {
          type: Boolean,
          default: false
        },
    +    exact: {
    +      type: Boolean,
    +      default: false
    +    }
      },
      data() {
        return {
          className: classNames("nav-item", this.active ? "active" : "")
        };
      },
      mixins: [waves]
    };
    </script>
    
    <style scoped>
    + .navbar.navbar-dark .breadcrumb .nav-item > .nav-link.router-link-active,
    + .navbar.navbar-dark .navbar-nav .nav-item > .nav-link.router-link-active {
    +   background-color: rgba(255, 255, 255, 0.1);
    }
    </style>
    

    이것으로 완성.

    감상


  • Angular5에서 MDBootstrap 사용했을 때는 패키지 추가해 소스 수정으로 했지만, Vue.js에서는 귀찮았다.
  • App.vue를 TypeScript로 만들면 오류가 발생하지 않았습니다. 형 정의는 준비되어 있지 않은 것 같기 때문에 Js로 할 수밖에 없다.
  • 
    ERROR in /Users/uwettie/works/todo-client/src/App.vue
    43:18 Could not find a declaration file for module '@/mixins/drop'. '/Users/uwettie/works/todo-client/src/mixins/drop.js' implicitly has an 'any' type.
        41 | import DropdownMenu from "@/components/mdbootstrap/DropdownMenu.vue";
        42 | import DropdownToggle from "@/components/mdbootstrap/DropdownToggle.vue";
      > 43 | import drop from "@/mixins/drop";
           |                  ^
        44 | import Mdinput from "@/components/mdbootstrap/MdInput.vue";
        45 |
        46 | export default {
    Version: typescript 2.8.3, tslint 5.9.1
    

    Vuetify



    Vue.js의 머티리얼 디자인의 컴포넌트 프레임워크는 Vuetify라는 것이 유명하다.
    그곳에서 하는 것이 좋을까.
    Vue.js 전용이기 때문에 버려지면 싫다고 생각해 버리지만 괜찮을까.

    만든 소스

    좋은 웹페이지 즐겨찾기