JSFiddle - React, Tailwind, and code Playground

by kurotanshi

HTML

<script src="https://unpkg.com/vue@next"></script>
<div id="app">
  <menu-component :title="menuData.name" :child="menuData.childNodes"></menu-component>
</div>

SCSS

#app {
  display: block;
  padding: 1rem;
  font-size: 1rem;
}

#app > ul {
  > li {
    margin-left: 0;
  }
}

ul > li {
  display: block;
  margin-left: 1rem;  
  margin-bottom: 0.5rem;
  padding: 5px;
  background-color: #eee;
}

h2.has-child {
  cursor: pointer;
  
  &::before {  
    content: '+ ';
  }
}

h2.has-child.is-open::before {
  content: '- ';
}

JavaScript

const menuData = {
  name: '好書推薦',
  childNodes: [{
      name: 'Git',
      url: null,
      childNodes: [{
        name: '為你自己學 Git',
        url: 'https://www.tenlong.com.tw/products/9789864342662'
      }]
    },
    {
      name: '前端開發',
      url: null,
      childNodes: [{
          name: '金魚都能懂的 CSS 選取器',
          url: 'https://www.tenlong.com.tw/products/9789864344994'
        },
        {
          name: '0 陷阱!0 誤解!8 天重新認識 JavaScript!',
          url: 'https://www.tenlong.com.tw/products/9789864344130'
        },
        {
          name: '讓 TypeScript 成為你全端開發的 ACE!',
          url: 'https://www.tenlong.com.tw/products/9789864344895'
        },
      ]
    },
    {
      name: 'IoT',
      url: null,
      childNodes: [{
        name: 'IoT沒那麼難!新手用 JavaScript 入門做自己的玩具!',
        url: 'https://www.tenlong.com.tw/products/9789864345328'
      }]
    },
    {
      name: 'Chatbot',
      url: null,
      childNodes: [{
        name: '人人可作卡米狗:從零打造自己的 LINE 聊天機器人',
        url: 'https://www.tenlong.com.tw/products/9789864342938'
      }]
    }
  ]
};

const app = Vue.createApp({
  data() {
    return {
      menuData
    }
  }
});

app.component('menu-component', {
  name: `menu-component`,
  props: {
    title: String,
    url: String,
    child: {
      type: Array,
      default: []
    }
  },
  data() {
    return {
      isOpen: false
    }
  },
  template: `
    <ul>
      <li>
        <template v-if="child.length > 0">
          <h2 class="has-child"
            :class="{ 'is-open': isOpen }"
            @click="isOpen = !isOpen">{{ title }}</h2>
          <menu-component 
            v-show="isOpen"
            v-for="c in child"
            :title="c.name"
            :child="c.childNodes"
            :url="c.url"
            />
        </template>
        <a :href="url" target="_blank" v-else>{{ title }}</a>
      </li>
    </ul>`
});

app.mount('#app');