├── .browserslistrc ├── .gitignore ├── README.md ├── babel.config.js ├── jest.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── favicon.ico └── index.html ├── src ├── App.vue ├── DemoWithRouter.ts ├── assets │ ├── fonts │ │ └── Inter.ttf │ └── logo.png ├── components │ ├── Vue3RouterTree │ │ ├── CaretRight.vue │ │ ├── index.tsx │ │ └── style.scss │ └── icons │ │ └── Icon.vue ├── devComponents │ ├── Prism.ts │ ├── icons │ │ ├── Icon.vue │ │ └── IconLogoGithub.vue │ └── navigation │ │ └── TabView.vue ├── layouts │ └── DefaultLayout │ │ └── DefaultLayout.vue ├── main.ts ├── router │ └── index.ts ├── shims-vue.d.ts ├── styles │ ├── index.scss │ └── prism-nocture-birds.css ├── utils │ └── snippets.ts └── views │ ├── About.vue │ ├── Home.vue │ └── guide │ ├── BasicExample.vue │ ├── DefaultOpenExample.vue │ ├── Index.vue │ ├── OpenAll.vue │ ├── WithIcons.vue │ └── WithMetaInfo.vue ├── tailwind.config.js ├── tests └── unit │ └── example.spec.ts ├── tsconfig.json └── vue.config.js /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist 4 | build 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | # Editor directories and files 16 | .idea 17 | .vscode 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 router tree 2 | 3 | This component is based on **Vue.js 3**, it represents your routes or items as a tree view, by default takes it takes the routes configuration as items, but you could provide your custom items that respects the following format : 4 | 5 | ```js 6 | [ 7 | { 8 | path:'/somePath',//optional 9 | name:'someName',//required 10 | component:SomeComponent //optional but if it's provided the tree node will be a link that redirects to this component 11 | children:[ 12 | 13 | 14 | ] 15 | } 16 | 17 | ] 18 | 19 | ``` 20 | 21 | You could also add any other field that you need it when you want to customize the items rendering 22 | 23 | ## Demo 24 | 25 | [LIVE DEMO](https://boussadjra.github.io/vue3-router-tree/) or you could check this [boilerplate](https://codesandbox.io/s/vue-3-router-tree-demo-wzxr1?file=/src/App.vue) on codesandbox 26 | 27 | ## Installation 28 | 29 | npm install vue3-router-tree --save 30 | 31 | ## Usage 32 | 33 | #### With router : 34 | 35 | ```html 36 | 37 | ``` 38 | 39 | #### with custom items : 40 | 41 | ```html 42 | 47 | 48 | 104 | ``` 105 | 106 | ### props : 107 | 108 | | Name | default | description | 109 | | ------------- | --------- | ----------------------------------------------------------------------------------------------- | 110 | | items | [] | the tree items or if not provided the component renders the current available routes | 111 | | active-color | "#5d1df1" | the color of the active sub node | 112 | | default-open | '' | specify the default opened path | 113 | | exclude-field | '' | In your route config you could specify a field inside meta option which will be used to exclude | 114 | | open-all | '' | Expand all items that have children | 115 | 116 | ## slots : 117 | 118 | | Name | description | 119 | | ---- | ----------------------------------- | 120 | | item | override the default item rendering | 121 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: '@vue/cli-plugin-unit-jest/presets/typescript-and-babel', 3 | transform: { 4 | '^.+\\.vue$': 'vue-jest' 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue3-router-tree", 3 | "version": "0.1.6", 4 | "author": "BOUSSADJRA BRAHIM", 5 | "private": false, 6 | "main": "./build/vue3-router-tree.common.js", 7 | "scripts": { 8 | "serve": "vue-cli-service serve", 9 | "build": "vue-cli-service build", 10 | "bundle": "vue-cli-service build --dest build --target lib --name vue3-router-tree ./src/components/Vue3RouterTree/index.tsx", 11 | "test:unit": "vue-cli-service test:unit" 12 | }, 13 | "dependencies": { 14 | "core-js": "^3.6.5", 15 | "prismjs": "^1.23.0", 16 | "vue": "^3.0.0-0", 17 | "vue-router": "^4.0.2" 18 | }, 19 | "devDependencies": { 20 | "@types/jest": "^24.0.19", 21 | "@types/prismjs": "^1.16.2", 22 | "@vue/cli-plugin-babel": "~4.5.0", 23 | "@vue/cli-plugin-router": "~4.5.0", 24 | "@vue/cli-plugin-typescript": "~4.5.0", 25 | "@vue/cli-plugin-unit-jest": "~4.5.0", 26 | "@vue/cli-plugin-vuex": "~4.5.0", 27 | "@vue/cli-service": "~4.5.0", 28 | "@vue/compiler-sfc": "^3.0.0-0", 29 | "@vue/test-utils": "^2.0.0-0", 30 | "autoprefixer": "^9.8.6", 31 | "node-sass": "^4.12.0", 32 | "postcss": "^7.0.35", 33 | "sass-loader": "^8.0.2", 34 | "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.0.2", 35 | "typescript": "~3.9.3", 36 | "vue-jest": "^5.0.0-0" 37 | }, 38 | "repository": { 39 | "type": "git", 40 | "url": "git+https://github.com/boussadjra/vue3-router-tree.git" 41 | }, 42 | "license": "MIT", 43 | "homepage": "https://boussadjra.github.io/vue3-router-tree/", 44 | "files": [ 45 | "build/*", 46 | "src/*", 47 | "*.json" 48 | ], 49 | "keywords": [ 50 | "vue.js 3", 51 | "vuejs 3", 52 | "tree view", 53 | "vue tree view", 54 | "vue component", 55 | "web component" 56 | ] 57 | } 58 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | } 6 | } -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boussadjra/vue3-router-tree/53c4b24587b7e8e6e75ce7d2ff65f4f93de28d3b/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Vue 3 router tree 11 | 12 | 13 | 14 | 15 | 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 18 | 19 | 21 | -------------------------------------------------------------------------------- /src/DemoWithRouter.ts: -------------------------------------------------------------------------------- 1 | import { defineComponent, h } from 'vue'; 2 | import Vue3RouterTree from './components/Vue3RouterTree'; 3 | 4 | import {RouterView} from 'vue-router' 5 | export default defineComponent({ 6 | 7 | name:"DemoWithRouter", 8 | 9 | render(){ 10 | return h("div",{}, [h(Vue3RouterTree),h(RouterView)]) 11 | } 12 | }) -------------------------------------------------------------------------------- /src/assets/fonts/Inter.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boussadjra/vue3-router-tree/53c4b24587b7e8e6e75ce7d2ff65f4f93de28d3b/src/assets/fonts/Inter.ttf -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boussadjra/vue3-router-tree/53c4b24587b7e8e6e75ce7d2ff65f4f93de28d3b/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/Vue3RouterTree/CaretRight.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | -------------------------------------------------------------------------------- /src/components/Vue3RouterTree/index.tsx: -------------------------------------------------------------------------------- 1 | import { computed, defineComponent, h, onMounted, ref, Transition, VNode, watch, watchEffect } from 'vue'; 2 | import './style.scss' 3 | 4 | import CaretRight from './CaretRight.vue' 5 | import { RouteRecordRaw, RouterLink, useRouter } from 'vue-router'; 6 | 7 | interface TreeNode { 8 | name: string; 9 | path: string; 10 | component: VNode; 11 | children: Array; 12 | id: number; 13 | info?: string | number | boolean 14 | } 15 | 16 | 17 | export default defineComponent({ 18 | props: { 19 | 20 | items: { 21 | type: Array as () => Array 22 | }, 23 | activeColor: { 24 | type: String, 25 | default: "#5d1df1" 26 | }, 27 | defaultOpen: { 28 | type: String, 29 | default: '' 30 | }, 31 | excludeField: { 32 | type: String, 33 | default: '' 34 | }, 35 | openAll: { 36 | type: Boolean, 37 | default: false 38 | }, 39 | noMaxWidth: { 40 | type: Boolean, 41 | default: false 42 | } 43 | }, 44 | components: { 45 | CaretRight 46 | }, 47 | setup(props, { slots }) { 48 | 49 | const expandedItem = ref(null); 50 | const selectedItem = ref(null); 51 | const id = ref(0); 52 | const navigationPath = ref>([]) 53 | 54 | const router = useRouter() 55 | 56 | 57 | const items = computed(() => { 58 | 59 | let routes = router.options?.routes || router.getRoutes(); 60 | 61 | let _items = props.items ? props.items : routes.filter(route => !route.meta || route.meta && !route.meta[props.excludeField]); 62 | 63 | return addId(_items) 64 | }) 65 | 66 | watchEffect(() => { 67 | findOpenItems(items.value) 68 | 69 | 70 | }) 71 | 72 | 73 | /*** 74 | * functions 75 | */ 76 | function findOpenItems(_items: TreeNode[]) { 77 | _items.forEach((_item: TreeNode) => { 78 | if (_item.children) { 79 | findOpenItems(_item.children); 80 | } 81 | 82 | if (props.defaultOpen.includes(_item.name)) { 83 | 84 | navigationPath.value.push(_item.id) 85 | } 86 | }) 87 | 88 | 89 | } 90 | 91 | function renderTree(_items: Array) { 92 | return (_items as any[]).map((item: TreeNode, index) => { 93 | return
94 | 95 |
{ 96 | event.stopPropagation(); 97 | if (item.children) { 98 | expandItem(item) 99 | } else { 100 | if (expandedItem.value?.children.find((_item) => _item.id === item.id)) { 101 | 102 | } else { 103 | navigationPath.value = [] 104 | } 105 | 106 | selectedItem.value = item; 107 | } 108 | 109 | }} 110 | style={{ color: selectedItem.value?.id === item.id ? props.activeColor : '' }} 111 | class={`flex items-center justify-between p-2 hover:text-xl `} 112 | 113 | > 114 | {renderSlot(item)} 115 | {item.children && } 116 |
117 | 118 |
119 | 120 | {((item.children) && (navigationPath.value.includes(item.id) || props.openAll)) && renderTree(item.children)} 121 | 122 | 123 |
124 | 125 | 126 | 127 |
128 | }) 129 | } 130 | 131 | function renderSlot(item: TreeNode) { 132 | return slots.item ? slots.item({ 133 | item: item 134 | }) : item.name 135 | } 136 | 137 | function addId(items: Array): any { 138 | return items.map(item => { 139 | return item.children ? { ...item, id: id.value++ ,children: addId(item.children) } : { ...item, id: id.value++, }; 140 | }) 141 | } 142 | 143 | function expandItem(item: TreeNode) { 144 | 145 | if (navigationPath.value.includes(item.id)) { 146 | let index = navigationPath.value.findIndex(p => p === item.id) 147 | navigationPath.value.splice(index); 148 | 149 | navigationPath.value = navigationPath.value.filter(el => el !== item.id) 150 | } else { 151 | let found =items.value.find((_item: TreeNode) => _item.id === item.id) 152 | 153 | 154 | if (found && navigationPath.value.length > 0) { 155 | navigationPath.value = []; 156 | navigationPath.value.push(item.id) 157 | } else { 158 | navigationPath.value.push(item.id) 159 | } 160 | 161 | expandedItem.value = item; 162 | } 163 | 164 | 165 | } 166 | /** 167 | * 168 | * jsx template 169 | */ 170 | return () => ( 171 |
172 | { 173 | renderTree(items.value) 174 | } 175 | 176 |
177 | ) 178 | } 179 | 180 | }); 181 | -------------------------------------------------------------------------------- /src/components/Vue3RouterTree/style.scss: -------------------------------------------------------------------------------- 1 | 2 | @tailwind base; 3 | @tailwind components; 4 | @tailwind utilities; 5 | 6 | 7 | 8 | /*.vrt-tree { 9 | // color: #444645; 10 | list-style: none; 11 | padding: 12px; 12 | max-width: 280px; 13 | // width: 20px; 14 | a{ 15 | text-decoration: none; 16 | } 17 | &__item { 18 | padding: 10px; 19 | position: relative; 20 | cursor: pointer; 21 | display: grid; 22 | grid-template-columns: 32px auto minmax(32px, 64px); 23 | ; 24 | white-space: nowrap; 25 | &:hover { 26 | background-color: #e1e1e1; 27 | border-radius: 4px; 28 | } 29 | &_caret { 30 | position: absolute; 31 | right: 0px; 32 | padding: 4px 0; 33 | opacity: .6; 34 | cursor: pointer; 35 | transform: rotate(90deg); 36 | transition: transform .3s ease-in; 37 | &--open { 38 | transform: rotate(-90deg); 39 | transition: transform .3s ease-in; 40 | } 41 | } 42 | &--has-more { 43 | padding: 4px 0; 44 | height: 100%; 45 | &:hover { 46 | background-color: unset; 47 | // border-radius: 4px; 48 | } 49 | } 50 | } 51 | &--has-children { 52 | padding: 8px 8px; 53 | position: relative; 54 | } 55 | &__wrapper { 56 | box-shadow: 0 0 10px #ddd; 57 | } 58 | } 59 | */ 60 | .slide-enter-active, 61 | .slide-leave-active { 62 | -moz-transition-duration: 0.3s; 63 | -webkit-transition-duration: 0.3s; 64 | -o-transition-duration: 0.3s; 65 | transition-duration: 0.3s; 66 | -moz-transition-timing-function: ease-in; 67 | -webkit-transition-timing-function: ease-in; 68 | -o-transition-timing-function: ease-in; 69 | transition-timing-function: ease-in; 70 | } 71 | 72 | .slide-enter-to, 73 | .slide-leave-from { 74 | max-height: 100px; 75 | overflow: hidden; 76 | } 77 | 78 | .slide-enter-from, 79 | .slide-leave-to { 80 | overflow: hidden; 81 | max-height: 0; 82 | } -------------------------------------------------------------------------------- /src/components/icons/Icon.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 52 | -------------------------------------------------------------------------------- /src/devComponents/Prism.ts: -------------------------------------------------------------------------------- 1 | import * as Vue from 'vue'; 2 | import Prism from 'prismjs'; 3 | import { Slots, VNode } from 'vue'; 4 | 5 | declare type Data = Record; 6 | 7 | export default Vue.defineComponent({ 8 | props: { 9 | code: { 10 | type: String, 11 | default:'' 12 | }, 13 | inline: { 14 | type: Boolean, 15 | default: false, 16 | }, 17 | language: { 18 | type: String, 19 | default: 'markup', 20 | }, 21 | }, 22 | setup(props, { slots, attrs }: { slots: Slots; attrs: Data }) { 23 | const { h } = Vue; 24 | // const slotsData = (slots && slots.default && slots.default()) || []; 25 | const code = props.code ; 26 | const { inline, language } = props; 27 | const prismLanguage = Prism.languages[language]; 28 | const className = `language-${language}`; 29 | 30 | if (inline) { 31 | return (): VNode => 32 | h('code', { ...attrs, class: [attrs.class, className], innerHTML: Prism.highlight(code, prismLanguage,props.language) }); 33 | } 34 | 35 | const d = Prism.highlight(code, prismLanguage,props.language); 36 | return (): VNode => 37 | h('pre', { ...attrs, class: [attrs.class, className] }, [ 38 | h('code', { 39 | class: className, 40 | innerHTML: d, 41 | }), 42 | ]); 43 | }, 44 | }); -------------------------------------------------------------------------------- /src/devComponents/icons/Icon.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 52 | -------------------------------------------------------------------------------- /src/devComponents/icons/IconLogoGithub.vue: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /src/devComponents/navigation/TabView.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 60 | 61 | 98 | -------------------------------------------------------------------------------- /src/layouts/DefaultLayout/DefaultLayout.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 45 | 46 | 48 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import router from "./router/index"; 3 | import App from "./App.vue"; 4 | import "./styles/index.scss" 5 | import 'prismjs' 6 | import './styles/prism-nocture-birds.css' 7 | import Prism from '@/devComponents/Prism' 8 | import TabView from "@/devComponents/navigation/TabView.vue"; 9 | let app = createApp(App); 10 | 11 | app.component('Prism',Prism) 12 | app.component('TabView',TabView) 13 | app.use(router).mount("#app"); 14 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import About from "@/views/About.vue"; 2 | import Home from "@/views/Home.vue"; 3 | import Guide from "@/views/guide/Index.vue"; 4 | import BasicExample from "@/views/guide/BasicExample.vue"; 5 | import WithIcons from "@/views/guide/WithIcons.vue"; 6 | import WithMetaInfo from "@/views/guide/WithMetaInfo.vue"; 7 | import DefaultOpenExample from "@/views/guide/DefaultOpenExample.vue"; 8 | import OpenAll from "@/views/guide/OpenAll.vue"; 9 | 10 | import { createWebHistory, createRouter } from "vue-router"; 11 | 12 | 13 | export const routes = [ 14 | { 15 | path: "/", name: "Home", component: Home, 16 | meta: { 17 | exludedOnSidebar: true 18 | } 19 | }, 20 | { 21 | path: "/about", name: "About", component: About, meta: { 22 | exludedOnSidebar: true 23 | } 24 | }, 25 | { 26 | path: "/guide", name: "Guide", component: Guide, children: [ 27 | 28 | { 29 | path: 'basic-example', 30 | name: 'Basic Example', 31 | component: BasicExample 32 | }, 33 | { 34 | path: 'with-icons', 35 | name: 'Render items with icons', 36 | component: WithIcons 37 | }, 38 | { 39 | path: 'with-meta-info', 40 | name: 'Render items with meta info', 41 | component: WithMetaInfo 42 | }, 43 | 44 | { 45 | path: 'default-open-item', 46 | name: 'Default Open one item', 47 | component: DefaultOpenExample 48 | }, 49 | { 50 | path: 'expand-all', 51 | name: 'Expand all items', 52 | component: OpenAll 53 | } 54 | ] 55 | }, 56 | 57 | 58 | ]; 59 | const router = createRouter({ 60 | history: createWebHistory(), 61 | routes 62 | }); 63 | 64 | export default router; -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { defineComponent } from 'vue' 3 | const component: ReturnType 4 | export default component 5 | } 6 | -------------------------------------------------------------------------------- /src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | @font-face { 5 | font-family: 'Inter'; 6 | src: url('../assets//fonts/Inter.ttf') format("truetype-variations"); 7 | font-weight: 1 999; 8 | } 9 | 10 | body{ 11 | @apply bg-purple-50 text-gray-500; 12 | overflow-y: hidden; 13 | font-family: 'Inter',Arial, Helvetica, sans-serif; 14 | } 15 | 16 | #app{ 17 | overflow-x: hidden; 18 | @apply text-gray-500; 19 | } 20 | ::-webkit-scrollbar { 21 | width: 2px; 22 | border-radius: 5px 23 | } 24 | 25 | ::-webkit-scrollbar-track { 26 | background: inherit; 27 | border-radius: 5px; 28 | } 29 | 30 | ::-webkit-scrollbar-thumb { 31 | @apply bg-p-indigo-500; 32 | border-radius: 5px; 33 | } 34 | .section__title{ 35 | @apply text-xl ml-2 pt-16 text-gray-700 36 | } 37 | 38 | .grid--auto-cols{ 39 | display: grid; 40 | grid-template-columns: repeat(auto-fill,minmax(320px,1fr)); 41 | place-items: center; 42 | &-xs{ 43 | grid-template-columns: repeat(auto-fill,minmax(240px,1fr)); 44 | } 45 | 46 | } 47 | 48 | .home{ 49 | overflow-y: auto; 50 | max-height: calc(100vh - 64px); 51 | 52 | } 53 | 54 | .drop-enter-active, 55 | .drop-leave-active { 56 | transition: all 0.5s ease-out; 57 | max-height: 400px; 58 | // max-width: 280px; 59 | } 60 | 61 | .drop-enter, 62 | .drop-leave-to 63 | /* .drop-leave-active below version 2.1.8 */ 64 | 65 | { 66 | max-height: 0; 67 | opacity: 0; 68 | // max-width: 0; 69 | transform: translateY(-200px); 70 | transform: scaleY(0); 71 | } 72 | -------------------------------------------------------------------------------- /src/styles/prism-nocture-birds.css: -------------------------------------------------------------------------------- 1 | /** 2 | * prism.js default theme for JavaScript, CSS and HTML 3 | * Based on dabblet (http://dabblet.com) 4 | * @author Lea Verou 5 | */ 6 | 7 | code[class*="language-"], 8 | pre[class*="language-"] { 9 | color: #ddeBbB; 10 | background: none; 11 | /* text-shadow: 0 1px white; */ 12 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 13 | font-size:13pt; 14 | text-align: left; 15 | white-space: pre; 16 | word-spacing: normal; 17 | word-break: normal; 18 | word-wrap: normal; 19 | line-height: 1.5; 20 | 21 | -moz-tab-size: 4; 22 | -o-tab-size: 4; 23 | tab-size: 4; 24 | 25 | -webkit-hyphens: none; 26 | -moz-hyphens: none; 27 | -ms-hyphens: none; 28 | hyphens: none; 29 | } 30 | 31 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, 32 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { 33 | text-shadow: none; 34 | background: #1c1c31; 35 | } 36 | 37 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection, 38 | code[class*="language-"]::selection, code[class*="language-"] ::selection { 39 | text-shadow: none; 40 | background: #1c1c31; 41 | } 42 | 43 | @media print { 44 | code[class*="language-"], 45 | pre[class*="language-"] { 46 | text-shadow: none; 47 | } 48 | } 49 | 50 | /* Code blocks */ 51 | pre[class*="language-"] { 52 | padding: 1em; 53 | margin: .5em 0; 54 | overflow: auto; 55 | } 56 | 57 | :not(pre) > code[class*="language-"], 58 | pre[class*="language-"] { 59 | background: #1c1c35; 60 | border-radius:4px; 61 | padding:16px; 62 | } 63 | 64 | /* Inline code */ 65 | :not(pre) > code[class*="language-"] { 66 | padding: .1em; 67 | border-radius: .3em; 68 | white-space: normal; 69 | } 70 | 71 | .token.comment, 72 | .token.prolog, 73 | .token.doctype, 74 | .token.cdata { 75 | color: slategray; 76 | } 77 | 78 | .token.punctuation { 79 | color: #65737E; 80 | } 81 | 82 | .token.namespace { 83 | opacity: .7; 84 | } 85 | 86 | .token.property, 87 | .token.tag, 88 | .token.boolean, 89 | .token.number, 90 | .token.constant, 91 | .token.symbol, 92 | .token.deleted { 93 | color: #2eeb9c; 94 | } 95 | 96 | .token.selector, 97 | .token.attr-name, 98 | .token.string, 99 | .token.char, 100 | .token.builtin, 101 | .token.inserted { 102 | color:#f0a7f0; 103 | } 104 | 105 | .token.operator, 106 | .token.entity, 107 | .token.url, 108 | .language-css .token.string, 109 | .style .token.string { 110 | color: #ff0; 111 | /* This background color was intended by the author of this theme. */ 112 | /* background: #4545ff; */ 113 | } 114 | 115 | .token.atrule, 116 | .token.attr-value, 117 | .token.keyword { 118 | color: #ebd82e; 119 | } 120 | 121 | .token.function, 122 | .token.class-name { 123 | color: #ff3076; 124 | } 125 | 126 | .token.regex, 127 | .token.important, 128 | .token.variable { 129 | color: #e90; 130 | } 131 | 132 | .token.important, 133 | .token.bold { 134 | font-weight: bold; 135 | } 136 | .token.italic { 137 | font-style: italic; 138 | } 139 | 140 | .token.entity { 141 | cursor: help; 142 | } 143 | -------------------------------------------------------------------------------- /src/utils/snippets.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | BasicExample: ` 7 | 8 | 122 | 123 | `, 124 | WithIcons: ` 137 | 138 | 252 | 253 | `, 254 | DefaultOpenExample: ` 267 | 268 | 382 | 383 | `, 384 | OpenAll: ` 397 | 398 | 512 | 513 | ` 514 | }; 515 | -------------------------------------------------------------------------------- /src/views/About.vue: -------------------------------------------------------------------------------- 1 | 25 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 83 | 84 | 206 | -------------------------------------------------------------------------------- /src/views/guide/BasicExample.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 141 | -------------------------------------------------------------------------------- /src/views/guide/DefaultOpenExample.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 152 | -------------------------------------------------------------------------------- /src/views/guide/Index.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 61 | 62 | 64 | -------------------------------------------------------------------------------- /src/views/guide/OpenAll.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 170 | -------------------------------------------------------------------------------- /src/views/guide/WithIcons.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 150 | -------------------------------------------------------------------------------- /src/views/guide/WithMetaInfo.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 151 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: ['./src/**/*.html', './src/**/*.vue', './src/**/*.ts','./src/**/*.tsx','./src/**/*.js'], 3 | darkMode: false, // or 'media' or 'class' 4 | theme: { 5 | extend: { 6 | colors: { 7 | 'p-indigo': { 8 | 100: '#d6cfe2', 9 | 200: '#ad9ec5', 10 | 300: '#846ea7', 11 | 400: '#5b3d8a', 12 | 500: '#320d6d', 13 | 600: '#280a57', 14 | 700: '#1e0841', 15 | 800: '#14052c', 16 | 900: '#0a0316', 17 | }, 18 | melon: { 19 | 100: '#fff2f1', 20 | 200: '#ffe5e2', 21 | 300: '#ffd9d4', 22 | 400: '#ffccc5', 23 | 500: '#ffbfb7', 24 | 600: '#cc9992', 25 | 700: '#99736e', 26 | 800: '#664c49', 27 | 900: '#332625', 28 | }, 29 | }, 30 | transitionProperty: { 31 | height: 'height', 32 | }, 33 | }, 34 | }, 35 | variants: {}, 36 | plugins: [], 37 | }; 38 | -------------------------------------------------------------------------------- /tests/unit/example.spec.ts: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import HelloWorld from '@/components/HelloWorld.vue' 3 | 4 | describe('HelloWorld.vue', () => { 5 | it('renders props.msg when passed', () => { 6 | const msg = 'new message' 7 | const wrapper = shallowMount(HelloWorld, { 8 | props: { msg } 9 | }) 10 | expect(wrapper.text()).toMatch(msg) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "skipLibCheck": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "baseUrl": ".", 14 | "types": [ 15 | "webpack-env", 16 | "jest" 17 | ], 18 | "paths": { 19 | "@/*": [ 20 | "src/*" 21 | ] 22 | }, 23 | "lib": [ 24 | "esnext", 25 | "dom", 26 | "dom.iterable", 27 | "scripthost" 28 | ] 29 | }, 30 | "include": [ 31 | "src/**/*.ts", 32 | "src/**/*.tsx", 33 | "src/**/*.vue", 34 | "tests/**/*.ts", 35 | "tests/**/*.tsx" 36 | ], 37 | "exclude": [ 38 | "node_modules" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | publicPath: process.env.NODE_ENV === 'production' ? '/vue3-router-tree/' : '/', 3 | configureWebpack: { 4 | output: { 5 | libraryExport: 'default', 6 | }, 7 | }, 8 | css: { 9 | extract: false, 10 | }, 11 | }; 12 | --------------------------------------------------------------------------------