├── .stylelintignore
├── docs
├── CNAME
├── images
│ ├── icons
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── favicon-96x96.png
│ │ ├── ms-icon-144x144.png
│ │ ├── apple-icon-114x114.png
│ │ ├── apple-icon-120x120.png
│ │ ├── apple-icon-144x144.png
│ │ ├── apple-icon-152x152.png
│ │ ├── apple-icon-180x180.png
│ │ ├── apple-icon-57x57.png
│ │ ├── apple-icon-60x60.png
│ │ ├── apple-icon-72x72.png
│ │ ├── apple-icon-76x76.png
│ │ ├── android-icon-144x144.png
│ │ └── android-icon-192x192.png
│ └── logo.svg
├── manifest.json
└── index.html
├── .eslintignore
├── env.d.ts
├── dev
├── env.d.ts
├── main.ts
├── pages
│ ├── Usage.vue
│ ├── Introduction.vue
│ ├── Installation.vue
│ └── Examples
│ │ ├── Manual.vue
│ │ ├── Auto.vue
│ │ └── Website.vue
├── routes.ts
├── App.scss
└── App.vue
├── vue.config.js
├── images
├── icons
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon-96x96.png
│ ├── ms-icon-144x144.png
│ ├── apple-icon-114x114.png
│ ├── apple-icon-120x120.png
│ ├── apple-icon-144x144.png
│ ├── apple-icon-152x152.png
│ ├── apple-icon-180x180.png
│ ├── apple-icon-57x57.png
│ ├── apple-icon-60x60.png
│ ├── apple-icon-72x72.png
│ ├── apple-icon-76x76.png
│ ├── android-icon-144x144.png
│ └── android-icon-192x192.png
└── logo.svg
├── src
├── components
│ ├── NavigationLevel
│ │ ├── NavigationLevel.scss
│ │ ├── NavigationLevel.spec.ts
│ │ └── NavigationLevel.vue
│ ├── NavigationItem
│ │ ├── NavigationItem.scss
│ │ └── NavigationItem.vue
│ ├── TreeNavigation
│ │ ├── TreeNavigation.scss
│ │ ├── TreeNavigation.ts
│ │ ├── core.ts
│ │ └── core.spec.ts
│ ├── NavigationToggle
│ │ ├── NavigationToggle.scss
│ │ ├── NavigationToggle.vue
│ │ └── NavigationToggle.spec.ts
│ ├── utils.ts
│ └── utils.spec.ts
└── index.js
├── .gitignore
├── cypress.json
├── .editorconfig
├── .prettierrc.json
├── tsconfig.json
├── tsconfig.vitest.json
├── tsconfig.node.json
├── .stylelintrc
├── tsconfig.app.json
├── vite.config.ts
├── vitest.config.ts
├── cypress
└── e2e
│ ├── runner.js
│ ├── specs
│ ├── auto-generated.spec.js
│ ├── without-router.spec.js
│ └── with-router.spec.js
│ └── apps
│ ├── auto-generated
│ └── index.html
│ ├── without-router
│ ├── index.html
│ └── running
│ │ └── barefoot
│ │ └── index.html
│ └── with-router
│ └── index.html
├── .github
├── workflows
│ ├── deploy.yaml
│ ├── npm-publish-github-packages.yml
│ ├── node.js.yml
│ └── static.yml
└── dependabot.yml
├── .eslintrc.js
├── manifest.json
├── LICENSE
├── index.html
├── package.json
└── README.md
/.stylelintignore:
--------------------------------------------------------------------------------
1 | node_modules/
--------------------------------------------------------------------------------
/docs/CNAME:
--------------------------------------------------------------------------------
1 | vue-tree-navigation.j3-tech.com
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist/
2 | coverage/
3 | e2e/apps/
4 |
--------------------------------------------------------------------------------
/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/dev/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | publicPath: '/Vue-Tree-Navigation/'
3 | }
--------------------------------------------------------------------------------
/images/icons/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/J3-Tech/Vue-Tree-Navigation/HEAD/images/icons/favicon-16x16.png
--------------------------------------------------------------------------------
/images/icons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/J3-Tech/Vue-Tree-Navigation/HEAD/images/icons/favicon-32x32.png
--------------------------------------------------------------------------------
/images/icons/favicon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/J3-Tech/Vue-Tree-Navigation/HEAD/images/icons/favicon-96x96.png
--------------------------------------------------------------------------------
/images/icons/ms-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/J3-Tech/Vue-Tree-Navigation/HEAD/images/icons/ms-icon-144x144.png
--------------------------------------------------------------------------------
/docs/images/icons/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/J3-Tech/Vue-Tree-Navigation/HEAD/docs/images/icons/favicon-16x16.png
--------------------------------------------------------------------------------
/docs/images/icons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/J3-Tech/Vue-Tree-Navigation/HEAD/docs/images/icons/favicon-32x32.png
--------------------------------------------------------------------------------
/docs/images/icons/favicon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/J3-Tech/Vue-Tree-Navigation/HEAD/docs/images/icons/favicon-96x96.png
--------------------------------------------------------------------------------
/images/icons/apple-icon-114x114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/J3-Tech/Vue-Tree-Navigation/HEAD/images/icons/apple-icon-114x114.png
--------------------------------------------------------------------------------
/images/icons/apple-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/J3-Tech/Vue-Tree-Navigation/HEAD/images/icons/apple-icon-120x120.png
--------------------------------------------------------------------------------
/images/icons/apple-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/J3-Tech/Vue-Tree-Navigation/HEAD/images/icons/apple-icon-144x144.png
--------------------------------------------------------------------------------
/images/icons/apple-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/J3-Tech/Vue-Tree-Navigation/HEAD/images/icons/apple-icon-152x152.png
--------------------------------------------------------------------------------
/images/icons/apple-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/J3-Tech/Vue-Tree-Navigation/HEAD/images/icons/apple-icon-180x180.png
--------------------------------------------------------------------------------
/images/icons/apple-icon-57x57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/J3-Tech/Vue-Tree-Navigation/HEAD/images/icons/apple-icon-57x57.png
--------------------------------------------------------------------------------
/images/icons/apple-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/J3-Tech/Vue-Tree-Navigation/HEAD/images/icons/apple-icon-60x60.png
--------------------------------------------------------------------------------
/images/icons/apple-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/J3-Tech/Vue-Tree-Navigation/HEAD/images/icons/apple-icon-72x72.png
--------------------------------------------------------------------------------
/images/icons/apple-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/J3-Tech/Vue-Tree-Navigation/HEAD/images/icons/apple-icon-76x76.png
--------------------------------------------------------------------------------
/docs/images/icons/ms-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/J3-Tech/Vue-Tree-Navigation/HEAD/docs/images/icons/ms-icon-144x144.png
--------------------------------------------------------------------------------
/images/icons/android-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/J3-Tech/Vue-Tree-Navigation/HEAD/images/icons/android-icon-144x144.png
--------------------------------------------------------------------------------
/images/icons/android-icon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/J3-Tech/Vue-Tree-Navigation/HEAD/images/icons/android-icon-192x192.png
--------------------------------------------------------------------------------
/docs/images/icons/apple-icon-114x114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/J3-Tech/Vue-Tree-Navigation/HEAD/docs/images/icons/apple-icon-114x114.png
--------------------------------------------------------------------------------
/docs/images/icons/apple-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/J3-Tech/Vue-Tree-Navigation/HEAD/docs/images/icons/apple-icon-120x120.png
--------------------------------------------------------------------------------
/docs/images/icons/apple-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/J3-Tech/Vue-Tree-Navigation/HEAD/docs/images/icons/apple-icon-144x144.png
--------------------------------------------------------------------------------
/docs/images/icons/apple-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/J3-Tech/Vue-Tree-Navigation/HEAD/docs/images/icons/apple-icon-152x152.png
--------------------------------------------------------------------------------
/docs/images/icons/apple-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/J3-Tech/Vue-Tree-Navigation/HEAD/docs/images/icons/apple-icon-180x180.png
--------------------------------------------------------------------------------
/docs/images/icons/apple-icon-57x57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/J3-Tech/Vue-Tree-Navigation/HEAD/docs/images/icons/apple-icon-57x57.png
--------------------------------------------------------------------------------
/docs/images/icons/apple-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/J3-Tech/Vue-Tree-Navigation/HEAD/docs/images/icons/apple-icon-60x60.png
--------------------------------------------------------------------------------
/docs/images/icons/apple-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/J3-Tech/Vue-Tree-Navigation/HEAD/docs/images/icons/apple-icon-72x72.png
--------------------------------------------------------------------------------
/docs/images/icons/apple-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/J3-Tech/Vue-Tree-Navigation/HEAD/docs/images/icons/apple-icon-76x76.png
--------------------------------------------------------------------------------
/docs/images/icons/android-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/J3-Tech/Vue-Tree-Navigation/HEAD/docs/images/icons/android-icon-144x144.png
--------------------------------------------------------------------------------
/docs/images/icons/android-icon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/J3-Tech/Vue-Tree-Navigation/HEAD/docs/images/icons/android-icon-192x192.png
--------------------------------------------------------------------------------
/src/components/NavigationLevel/NavigationLevel.scss:
--------------------------------------------------------------------------------
1 | .navigation-level {
2 | &--closed {
3 | ul {
4 | display: none;
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | node_modules/
4 | coverage/
5 | cypress/
6 |
7 | e2e/**/vue-tree-navigation.js
8 | docs/build.js
9 | .vscode
10 | coverage
11 |
--------------------------------------------------------------------------------
/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "integrationFolder": "e2e/specs",
3 | "fixturesFolder": false,
4 | "pluginsFile": false,
5 | "supportFile": false,
6 | "video": false
7 | }
8 |
--------------------------------------------------------------------------------
/dev/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import router from './routes';
3 | import App from './App.vue';
4 |
5 | const app = createApp(App)
6 | app.use(router);
7 | app.mount('#app');
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/prettierrc",
3 | "semi": false,
4 | "tabWidth": 2,
5 | "singleQuote": true,
6 | "printWidth": 100,
7 | "trailingComma": "none"
8 | }
--------------------------------------------------------------------------------
/src/components/NavigationItem/NavigationItem.scss:
--------------------------------------------------------------------------------
1 | .navigation-item {
2 | display: inline-block;
3 | padding-top: 5px;
4 | padding-bottom: 5px;
5 |
6 | span {
7 | cursor: pointer;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import TreeNavigation from './components/TreeNavigation/TreeNavigation';
2 |
3 | module.exports = {
4 | install: function (Vue, options) {
5 | Vue.component('vue-tree-navigation', TreeNavigation);
6 | },
7 | };
8 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": [],
3 | "references": [
4 | {
5 | "path": "./tsconfig.node.json"
6 | },
7 | {
8 | "path": "./tsconfig.app.json"
9 | },
10 | {
11 | "path": "./tsconfig.vitest.json"
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/tsconfig.vitest.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.app.json",
3 | "exclude": [],
4 | "compilerOptions": {
5 | "composite": true,
6 | "lib": [],
7 | "types": ["node", "jsdom"],
8 | "allowJs": true,
9 | "ignoreDeprecations": "5.0"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/images/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/images/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/TreeNavigation/TreeNavigation.scss:
--------------------------------------------------------------------------------
1 | .tree-navigation {
2 | display: inline-block;
3 | padding: 0;
4 | margin: 0;
5 |
6 | ul {
7 | padding: 0;
8 | margin: 0;
9 | list-style-type: none;
10 |
11 | li {
12 | padding-left: 20px;
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@vue/tsconfig/tsconfig.node.json",
3 | "include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"],
4 | "compilerOptions": {
5 | "composite": true,
6 | "types": ["node"],
7 | "ignoreDeprecations": "5.0"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "processors": ["stylelint-processor-arbitrary-tags"],
3 | "extends": [
4 | "stylelint-config-standard",
5 | "stylelint-config-recess-order"
6 | ],
7 | "plugins": [
8 | "stylelint-scss"
9 | ],
10 | "rules": {
11 | "declaration-colon-space-after": null
12 | }
13 | }
--------------------------------------------------------------------------------
/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@vue/tsconfig/tsconfig.web.json",
3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
4 | "exclude": ["src/**/__tests__/*"],
5 | "compilerOptions": {
6 | "composite": true,
7 | "baseUrl": ".",
8 | "paths": {
9 | "@/*": ["./src/*"]
10 | },
11 | "ignoreDeprecations": "5.0"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { fileURLToPath, URL } from 'node:url'
2 |
3 | import { defineConfig } from 'vite'
4 | import vue from '@vitejs/plugin-vue'
5 |
6 | // https://vitejs.dev/config/
7 | export default defineConfig({
8 | plugins: [vue()],
9 | resolve: {
10 | alias: {
11 | '@': fileURLToPath(new URL('./dev', import.meta.url))
12 | }
13 | }
14 | })
15 |
--------------------------------------------------------------------------------
/src/components/NavigationToggle/NavigationToggle.scss:
--------------------------------------------------------------------------------
1 | .navigation-toggle {
2 | position: relative;
3 | top: -3px;
4 | padding: 5px 5px 5px 3px;
5 | cursor: pointer;
6 |
7 | &__icon {
8 | display: inline-block;
9 | padding: 3px;
10 | border: solid #000;
11 | border-width: 0 2px 2px 0;
12 | transform: rotate(45deg);
13 | }
14 |
15 | &__icon--closed {
16 | transform: rotate(-45deg);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { fileURLToPath } from 'node:url'
2 | import { mergeConfig } from 'vite'
3 | import { configDefaults, defineConfig } from 'vitest/config'
4 | import viteConfig from './vite.config'
5 |
6 | export default mergeConfig(
7 | viteConfig,
8 | defineConfig({
9 | test: {
10 | environment: 'jsdom',
11 | exclude: [...configDefaults.exclude, 'e2e/*'],
12 | root: fileURLToPath(new URL('./', import.meta.url))
13 | }
14 | })
15 | )
16 |
--------------------------------------------------------------------------------
/cypress/e2e/runner.js:
--------------------------------------------------------------------------------
1 | const shell = require('shelljs');
2 |
3 | const tests = ['with-router', 'without-router', 'auto-generated'];
4 |
5 | const runTest = test => {
6 | shell.cp(`./dist/vue-tree-navigation.js`, `./e2e/apps/${test}`);
7 |
8 | shell.exec(
9 | `concurrently --kill-others "http-server -p 8000 ./e2e/apps/${test}" "cypress run --spec ./e2e/specs/${test}.spec.js"`
10 | );
11 | };
12 |
13 | shell.exec('yarn build');
14 | tests.forEach(test => runTest(test));
15 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yaml:
--------------------------------------------------------------------------------
1 | name: Build Vue
2 |
3 | on:
4 | push:
5 | branches: [ "master" ]
6 |
7 | jobs:
8 | build_vue:
9 | runs-on: ubuntu-latest
10 | name: Build Vue
11 | steps:
12 | - uses: actions/checkout@v2
13 | - id: Build-Vue
14 | uses: J3-Tech/VuePagesAction@0.0.7
15 | with:
16 | cname: vue-tree-navigation.j3-tech.com
17 | username: 'J3-Tech'
18 | reponame: 'Vue-Tree-Navigation'
19 | token: ${{ secrets.GITHUB_TOKEN }} # Leave this line unchanged
20 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "npm" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "weekly"
12 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | require('@rushstack/eslint-patch/modern-module-resolution')
3 |
4 | module.exports = {
5 | root: true,
6 | 'extends': [
7 | 'plugin:vue/vue3-essential',
8 | 'eslint:recommended',
9 | '@vue/eslint-config-typescript',
10 | '@vue/eslint-config-prettier/skip-formatting'
11 | ],
12 | overrides: [
13 | {
14 | files: [
15 | 'cypress/e2e/**/*.{cy,spec}.{js,ts,jsx,tsx}'
16 | ],
17 | 'extends': [
18 | 'plugin:cypress/recommended'
19 | ]
20 | }
21 | ],
22 | parserOptions: {
23 | ecmaVersion: 'latest'
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/NavigationToggle/NavigationToggle.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
10 |
11 |
12 |
13 |
24 |
25 |
28 |
--------------------------------------------------------------------------------
/src/components/NavigationLevel/NavigationLevel.spec.ts:
--------------------------------------------------------------------------------
1 | import { mount } from '@vue/test-utils';
2 | import { describe, it, expect } from 'vitest';
3 |
4 | import NavigationLevel from './NavigationLevel.vue';
5 |
6 | describe('NavigationLevel ', () => {
7 | it('isVueInstance', () => {
8 | const wrapper = mount(NavigationLevel, {
9 | propsData: {
10 | parentItem: {
11 | meta: {
12 | target: 'https://github.com',
13 | },
14 | },
15 | level: 1,
16 | defaultOpenLevel: 1,
17 | },
18 | });
19 |
20 | expect(wrapper.exists()).toBe(true);
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/src/components/utils.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * First character should be #.
3 | */
4 | export const sanitizeElement = (element?: String) => {
5 | if (element === undefined || element === '') {
6 | return element;
7 | }
8 |
9 | if (element[0] !== '#') {
10 | element = '#' + element;
11 | }
12 |
13 | return element;
14 | };
15 |
16 | /**
17 | * First character should be backslash.
18 | * Last character shouldn't be backslash.
19 | */
20 | export const sanitizePath = (path?: string): string | undefined => {
21 | if (path === undefined) {
22 | return;
23 | }
24 |
25 | if (path[0] !== '/') {
26 | path = '/' + path;
27 | }
28 |
29 | if (path[path.length - 1] === '/') {
30 | path = path.slice(0, -1);
31 | }
32 |
33 | return path;
34 | };
35 |
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {"background_color":"#ffffff","description":"Vue Tree Navigation","display":"standalone","icons":[{"src":"/images/icons/android-icon-36x36.png","sizes":"36x36","type":"image/png","density":"0.75"},{"src":"/images/icons/android-icon-48x48.png","sizes":"48x48","type":"image/png","density":"1.0"},{"src":"/images/icons/android-icon-72x72.png","sizes":"72x72","type":"image/png","density":"1.5"},{"src":"/images/icons/android-icon-96x96.png","sizes":"96x96","type":"image/png","density":"2.0"},{"src":"/images/icons/android-icon-144x144.png","sizes":"144x144","type":"image/png","density":"3.0"},{"src":"/images/icons/android-icon-192x192.png","sizes":"192x192","type":"image/png","density":"4.0"}],"lang":"en-US","name":"Vue.js","short_name":"Vue","start_url":"./menu","theme_color":"#4fc08d"}
--------------------------------------------------------------------------------
/docs/manifest.json:
--------------------------------------------------------------------------------
1 | {"background_color":"#ffffff","description":"Vue Tree Navigation","display":"standalone","icons":[{"src":"/images/icons/android-icon-36x36.png","sizes":"36x36","type":"image/png","density":"0.75"},{"src":"/images/icons/android-icon-48x48.png","sizes":"48x48","type":"image/png","density":"1.0"},{"src":"/images/icons/android-icon-72x72.png","sizes":"72x72","type":"image/png","density":"1.5"},{"src":"/images/icons/android-icon-96x96.png","sizes":"96x96","type":"image/png","density":"2.0"},{"src":"/images/icons/android-icon-144x144.png","sizes":"144x144","type":"image/png","density":"3.0"},{"src":"/images/icons/android-icon-192x192.png","sizes":"192x192","type":"image/png","density":"4.0"}],"lang":"en-US","name":"Vue.js","short_name":"Vue","start_url":"./menu","theme_color":"#4fc08d"}
--------------------------------------------------------------------------------
/cypress/e2e/specs/auto-generated.spec.js:
--------------------------------------------------------------------------------
1 | describe('with items automatically generated from vue-router routes', () => {
2 | beforeEach(() => {
3 | cy.visit('http://127.0.0.1:8000');
4 | });
5 |
6 | it('renders all menu items with a correct targets', () => {
7 | cy.contains('Home').should('have.attr', 'href', '#/');
8 | cy.contains('Running').should('have.attr', 'href', '#/running');
9 | cy.contains('Barefoot').should('have.attr', 'href', '#/running/barefoot');
10 | cy.contains('Yoga').should('have.attr', 'href', '#/yoga');
11 | cy.contains('Mats').should('have.attr', 'href', '#/yoga/mats');
12 | cy.contains('Tops').should('have.attr', 'href', '#/yoga/tops');
13 | cy.contains('About').should('have.attr', 'href', '#/about');
14 | cy.contains('Career').should('have.attr', 'href', '#/about/career');
15 | cy.contains('Design').should('have.attr', 'href', '#/about/career/design');
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/src/components/NavigationToggle/NavigationToggle.spec.ts:
--------------------------------------------------------------------------------
1 | import { mount } from '@vue/test-utils';
2 | import { describe, it, expect } from 'vitest';
3 |
4 | import NavigationToggle from './NavigationToggle.vue';
5 |
6 | describe('NavigationToggle ', () => {
7 | it('isVueInstance', () => {
8 | const wrapper = mount(NavigationToggle);
9 |
10 | expect(wrapper.exists()).toBe(true);
11 | });
12 |
13 | describe('when closed', () => {
14 | it('is assigned closed class', () => {
15 | const wrapper = mount(NavigationToggle, {
16 | propsData: {
17 | open: false,
18 | },
19 | });
20 |
21 | expect(wrapper.classes()).toContain('navigation-toggle--closed');
22 | });
23 | });
24 |
25 | describe('when opened', () => {
26 | it('is not assigned closed class', () => {
27 | const wrapper = mount(NavigationToggle, {
28 | propsData: {
29 | open: true,
30 | },
31 | });
32 |
33 | expect(wrapper.classes()).not.toContain('navigation-toggle--closed');
34 | });
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/.github/workflows/npm-publish-github-packages.yml:
--------------------------------------------------------------------------------
1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
2 | # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
3 |
4 | name: Node.js Package
5 |
6 | on:
7 | push:
8 | tags:
9 | - '*'
10 |
11 | jobs:
12 | build:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v3
16 | - uses: actions/setup-node@v3
17 | with:
18 | node-version: 16
19 | - run: npm ci
20 | - run: npm test
21 |
22 | publish-gpr:
23 | needs: build
24 | runs-on: ubuntu-latest
25 | permissions:
26 | contents: read
27 | packages: write
28 | steps:
29 | - uses: actions/checkout@v3
30 | - uses: actions/setup-node@v3
31 | with:
32 | node-version: 16
33 | registry-url: https://npm.pkg.github.com/
34 | - run: npm ci
35 | - run: npm publish
36 | env:
37 | NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}
38 |
--------------------------------------------------------------------------------
/src/components/utils.spec.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect } from 'vitest';
2 |
3 | import { sanitizeElement, sanitizePath } from './utils';
4 |
5 | describe('utils', () => {
6 | describe('sanitizeElement', () => {
7 | [
8 | { expected: undefined, element: undefined },
9 | { expected: '', element: '' },
10 | { expected: '#element', element: '#element' },
11 | { expected: '#element', element: 'element' },
12 | ].forEach((item, i) => {
13 | it('returns %s for path %s', () => {
14 | expect(sanitizeElement(item.element)).toBe(item.expected);
15 | });
16 | });
17 | });
18 |
19 | describe('sanitizePath', () => {
20 | [
21 | { expected: undefined, path: undefined },
22 | { expected: '', path: '' },
23 | { expected: '/path', path: '/path' },
24 | { expected: '/path', path: 'path' },
25 | { expected: '/path', path: 'path/' },
26 | ].forEach((item) => {
27 | it('returns %s for path %s', () => {
28 | expect(sanitizePath(item.path)).toBe(item.expected);
29 | });
30 | });
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017-present, Michaela Robosova
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/dev/pages/Usage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Usage
4 | Props
5 |
6 |
items
7 |
8 | Optional . An array containing tree navigation items. In case no
9 | items are provided, vue-tree-navigation will check if vue-router is available and
10 | will use its routes to generate navigation items.
11 |
12 |
Fields
13 |
14 | name Item label
15 |
16 | path Relative target.You need to install and configure vue-router
17 | if you wish path items to behave like router links. Otherwise they
18 | will behave like normal hyperlinks.
19 |
20 | element Element ID
21 | external Absolute external target
22 |
23 |
24 |
25 |
defaultOpenLevel
26 |
27 | Optional . Default value is 0 (everything is closed).
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
3 |
4 | name: Node.js CI
5 |
6 | on:
7 | push:
8 | branches: [ "master" ]
9 | pull_request:
10 | branches: [ "master" ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | strategy:
18 | matrix:
19 | node-version: [16.x, 18.x]
20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
21 |
22 | steps:
23 | - uses: actions/checkout@v3
24 | - name: Use Node.js ${{ matrix.node-version }}
25 | uses: actions/setup-node@v3
26 | with:
27 | node-version: ${{ matrix.node-version }}
28 | cache: 'npm'
29 | - run: npm ci
30 | - run: npm run build --if-present
31 | - run: npm test
32 |
33 | - name: Upload coverage reports to Codecov
34 | uses: codecov/codecov-action@v3
35 | with:
36 | token: ${{ secrets.CODE_COV_TOKEN }}
37 | files: ./coverage/clover.xml
38 | verbose: true
39 |
--------------------------------------------------------------------------------
/.github/workflows/static.yml:
--------------------------------------------------------------------------------
1 | # Simple workflow for deploying static content to GitHub Pages
2 | name: Deploy static content to Pages
3 |
4 | on:
5 | # Runs on pushes targeting the default branch
6 | push:
7 | branches: ["gh-pages"]
8 |
9 | # Allows you to run this workflow manually from the Actions tab
10 | workflow_dispatch:
11 |
12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
13 | permissions:
14 | contents: read
15 | pages: write
16 | id-token: write
17 |
18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
20 | concurrency:
21 | group: "pages"
22 | cancel-in-progress: false
23 |
24 | jobs:
25 | # Single deploy job since we're just deploying
26 | deploy:
27 | environment:
28 | name: github-pages
29 | url: ${{ steps.deployment.outputs.page_url }}
30 | runs-on: ubuntu-latest
31 | steps:
32 | - name: Checkout
33 | uses: actions/checkout@v3
34 | - name: Setup Pages
35 | uses: actions/configure-pages@v3
36 | - name: Upload artifact
37 | uses: actions/upload-pages-artifact@v1
38 | with:
39 | # Upload entire repository
40 | path: '.'
41 | - name: Deploy to GitHub Pages
42 | id: deployment
43 | uses: actions/deploy-pages@v1
44 |
--------------------------------------------------------------------------------
/dev/pages/Introduction.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Introduction
4 |
5 | Vue.js 2 tree navigation with vue-router support
6 |
7 |
8 | Features
9 |
10 | unlimited number of levels
11 |
12 | optional
13 | vue-router support
14 | (v2.0.0 or higher)
15 |
16 |
17 | generate navigation items automatically from
18 | vue-router routes or
19 | define them manually
20 |
21 | define a default open level
22 | auto-open a level when a corresponding URL visited
23 | focused on core functionality, only necessary styles included
24 |
25 | elements are provided with meaningful classes to make customizations comfortable
26 | (for example NavigationItem--active,
27 | NavigationLevel--level-1, NavigationLevel--closed)
28 |
29 |
30 |
31 |
32 | Requirements
33 |
36 |
37 |
38 | Demo
39 | This documentation serves as the demo page ;)
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/dev/routes.ts:
--------------------------------------------------------------------------------
1 | import ExamplesAuto from './pages/Examples/Auto.vue'
2 | import ExamplesManual from './pages/Examples/Manual.vue'
3 | import ExamplesWebsite from './pages/Examples/Website.vue'
4 |
5 | import Introduction from './pages/Introduction.vue';
6 | import Installation from './pages/Installation.vue';
7 | import Usage from './pages/Usage.vue';
8 |
9 | import { createRouter, createWebHistory } from 'vue-router'
10 |
11 | const routes = [
12 | {
13 | path: '/',
14 | component: Introduction,
15 | },
16 | {
17 | path: '/installation',
18 | component: Installation,
19 | },
20 | {
21 | path: '/installation/usage',
22 | component: Usage,
23 | },
24 | {
25 | path: '/examples',
26 | component: ExamplesWebsite,
27 | children: [
28 | {
29 | path: '/examples/auto',
30 | component: ExamplesAuto,
31 | },
32 | {
33 | path: '/examples/manually-defined',
34 | component: ExamplesManual,
35 | },
36 | {
37 | path: '/examples/this-website',
38 | component: ExamplesWebsite,
39 | },
40 | ],
41 | },
42 | ];
43 |
44 | const router = createRouter({
45 | history: createWebHistory(import.meta.env.BASE_URL),
46 | routes: routes,
47 | scrollBehavior(to, from, savedPosition) {
48 | if (to.hash) {
49 | return {
50 | el: to.hash,
51 | behavior: 'smooth',
52 | top: -10,
53 | }
54 | }
55 | }
56 | })
57 |
58 | export default router
59 |
--------------------------------------------------------------------------------
/src/components/TreeNavigation/TreeNavigation.ts:
--------------------------------------------------------------------------------
1 | import { insertMetadataToNavItems, generateLevel } from './core';
2 | import { h } from 'vue';
3 | import './TreeNavigation.scss';
4 |
5 | const TreeNavigation = {
6 | props: {
7 | items: {
8 | type: Array,
9 | required: false,
10 | default: () => [],
11 | },
12 | defaultOpenLevel: {
13 | type: Number,
14 | default: 0,
15 | },
16 | },
17 | computed: {
18 | navItems(props: any): any {
19 | if (props.items && props.items.length) {
20 | return props.items;
21 | }
22 |
23 | if (
24 | props.$router &&
25 | props.$router.options &&
26 | props.$router.options.routes &&
27 | props.$router.options.routes.length
28 | ) {
29 | return props.$router.options.routes;
30 | }
31 |
32 | console.warn(
33 | "[VueTreeNavigation]: Haven't you forget to provide items or define vue-router routes?"
34 | );
35 | return [];
36 | },
37 | navItemsWithMetadata() {
38 | const navItems = JSON.parse(JSON.stringify(this.navItems));
39 | return insertMetadataToNavItems(navItems);
40 | },
41 | },
42 |
43 | render(props: any): any {
44 | const level = 1;
45 | const tree: any = h(
46 | 'ul',
47 | generateLevel(props.navItemsWithMetadata, level, props.defaultOpenLevel)
48 | );
49 |
50 | const level0 = h(
51 | 'div',
52 | {
53 | class: ['navigation-level', 'navigation-level--level-0'],
54 | },
55 | [tree]
56 | );
57 |
58 | const treeNavigation: any = h(
59 | 'div',
60 | {
61 | class: 'tree-navigation',
62 | },
63 | [level0]
64 | );
65 |
66 | return treeNavigation;
67 | },
68 | };
69 |
70 | export default TreeNavigation;
71 |
--------------------------------------------------------------------------------
/cypress/e2e/apps/auto-generated/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | vue-tree-navigation
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/dev/pages/Installation.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Installation
4 |
5 |
NPM
6 |
7 | $ npm install vue-tree-navigation
8 |
9 |
main.js
10 |
11 | import VueTreeNavigation from 'vue-tree-navigation' ;
12 |
13 | Vue.use(VueTreeNavigation);
14 |
15 |
16 |
17 |
Include with a script tag
18 |
19 | <script > src="https://unpkg.com/vue-tree-navigation@4.0.0/dist/vue-tree-navigation.js" > </script >
20 | <script >
21 | Vue.use(VueTreeNavigation)
22 | </script >
23 |
24 |
Example
25 |
26 | <div id ="app" >
27 | <vue-tree-navigation :items ="items" :defaultOpenLevel ="1" />
28 | </div >
29 |
30 | <script >
31 | Vue.use(VueTreeNavigation)
32 |
33 | const app = new Vue({
34 | el: '#app' ,
35 | data: {
36 | items: [
37 | ...
38 | ],
39 | }
40 | })
41 | </script >
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/dev/App.scss:
--------------------------------------------------------------------------------
1 | body,
2 | html {
3 | height: 100%;
4 | overflow: hidden;
5 | }
6 | body {
7 | color: #4e4e4e;
8 | }
9 | h1,
10 | h2,
11 | h3,
12 | h4,
13 | h5,
14 | h6 {
15 | color: #35495e;
16 | }
17 | a {
18 | color: #42b883;
19 | }
20 | main,
21 | nav {
22 | height: 100%;
23 | overflow-y: scroll;
24 | }
25 | nav {
26 | padding: 0;
27 | width: 0;
28 | }
29 | main {
30 | -webkit-box-flex: 1;
31 | -ms-flex: 1;
32 | flex: 1;
33 | padding: 25px;
34 | }
35 | main section {
36 | margin-top: 40px;
37 | }
38 | code {
39 | font-family: Roboto Mono, monospace;
40 | font-weight: 400;
41 | }
42 | pre {
43 | border-left-color: #42b883;
44 | }
45 | ul ul {
46 | font-size: 100%;
47 | }
48 | .hljs-number,
49 | .hljs-string {
50 | color: #af00cd;
51 | }
52 | .tag {
53 | color: #b3b3b3;
54 | }
55 | .additional {
56 | font-size: 0.8em;
57 | }
58 | .container {
59 | height: 100%;
60 | display: -webkit-box;
61 | display: -ms-flexbox;
62 | display: flex;
63 | max-width: 1400px;
64 | padding: 0;
65 | }
66 | .tree-navigation {
67 | margin-top: 42px;
68 | margin-right: 20px;
69 | font-size: 0.9em;
70 | a {
71 | color: inherit;
72 | }
73 | .navigation-level__children {
74 | padding-left: 10px;
75 | }
76 | .navigation-level__parent {
77 | font-weight: 600;
78 | padding-bottom: 5px;
79 | }
80 | .navigation-item {
81 | color: #545454;
82 | padding: 0;
83 | }
84 | .navigation-item--active {
85 | color: #42b883;
86 | }
87 | .navigation-toggle__icon {
88 | border-color: #42b883;
89 | }
90 | }
91 |
92 | #app {
93 | height: 100%;
94 | }
95 | #app.navOpen {
96 | nav {
97 | padding: 20px;
98 | width: 100vw;
99 | }
100 | main {
101 | display: none;
102 | }
103 | }
104 | #hamburger {
105 | position: absolute;
106 | right: 20px;
107 | top: 10px;
108 | z-index: 1;
109 | padding: 10px;
110 | cursor: pointer;
111 | }
112 | #github,
113 | #hamburger {
114 | color: #b3b3b3;
115 | }
116 | #github:hover,
117 | #hamburger:hover {
118 | color: #7f7f7f;
119 | }
120 | #installation div {
121 | margin-top: 50px;
122 | }
123 |
124 | @media only screen and (min-width: 900px) {
125 | #hamburger {
126 | display: none;
127 | }
128 | #github {
129 | float: right;
130 | }
131 | nav {
132 | padding: 20px;
133 | width: auto;
134 | }
135 | main {
136 | padding: 30px 60px 60px;
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/src/components/NavigationLevel/NavigationLevel.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
14 |
94 |
95 |
98 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Vue Tree Navigation
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/dev/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
86 |
87 |
90 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Vue Tree Navigation
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/components/NavigationItem/NavigationItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ item?.name }}
4 |
5 | {{ item.name }}
11 |
12 | {{
13 | item.name
14 | }}
15 |
16 | {{ item.name }}
23 |
24 |
25 |
26 |
103 |
104 |
107 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-tree-navigation",
3 | "version": "5.0.0",
4 | "description": "Vue.js 2 tree navigation",
5 | "keywords": [
6 | "tree",
7 | "navigation",
8 | "vue",
9 | "vuejs",
10 | "component",
11 | "plugin",
12 | "menu"
13 | ],
14 | "homepage": "https://github.com/J3-Tech/vue-tree-navigation",
15 | "bugs": "https://github.com/J3-Tech/vue-tree-navigation/issues",
16 | "license": "MIT",
17 | "author": "Michaela Robošová ",
18 | "files": [
19 | "dist"
20 | ],
21 | "main": "dist/vue-tree-navigation.js",
22 | "husky": {
23 | "hooks": {
24 | "pre-commit": "npm run lint && npm run unit"
25 | }
26 | },
27 | "engines": {
28 | "npm": ">=7.10.0 <=9.5.0",
29 | "node": ">=16.13.0 <=19.7.0"
30 | },
31 | "repository": {
32 | "type": "git",
33 | "url": "https://github.com/J3-Tech/Vue-Tree-Navigation.git"
34 | },
35 | "scripts": {
36 | "dev": "vite",
37 | "build": "run-p type-check build-only",
38 | "build:docs": "run-p type-check build-only",
39 | "lint:ts": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
40 | "lint:scss": "stylelint './**/*.scss'",
41 | "lint": "npm run lint:ts & npm run lint:scss",
42 | "prettier-list": "prettier --list-different '**/*.{js,vue}' --ignore-path .eslintignore",
43 | "prettier": "prettier --write '**/*.{js,vue}' --ignore-path .eslintignore",
44 | "test": "vitest",
45 | "e2e": "node ./e2e/runner.js",
46 | "build-only": "vite build",
47 | "type-check": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
48 | "format": "prettier --write src/"
49 | },
50 | "dependencies": {
51 | "husky": "^9.1.5",
52 | "vue": "^3.2.47",
53 | "vue-router": "^4.1.6"
54 | },
55 | "devDependencies": {
56 | "@rushstack/eslint-patch": "^1.2.0",
57 | "@types/jsdom": "^21.1.0",
58 | "@types/node": "^22.5.2",
59 | "@vitejs/plugin-vue": "^4.0.0",
60 | "@vue/eslint-config-prettier": "^7.1.0",
61 | "@vue/eslint-config-typescript": "^13.0.0",
62 | "@vue/compiler-sfc": "^3.1.0",
63 | "@vue/test-utils": "^2.3.1",
64 | "@vue/tsconfig": "^0.1.3",
65 | "cypress": "^13.1.0",
66 | "eslint": "^8.34.0",
67 | "eslint-config-prettier": "^9.1.0",
68 | "eslint-config-standard": "^17.0.0",
69 | "eslint-plugin-cypress": "^2.0.1",
70 | "eslint-plugin-html": "^8.1.1",
71 | "eslint-plugin-import": "^2.14.0",
72 | "eslint-plugin-node": "^11.1.0",
73 | "eslint-plugin-promise": "^6.1.1",
74 | "eslint-plugin-standard": "^5.0.0",
75 | "eslint-plugin-vue": "^9.9.0",
76 | "jsdom": "^22.1.0",
77 | "npm-run-all": "^4.1.5",
78 | "prettier": "^2.8.4",
79 | "sass": "^1.59.3",
80 | "shelljs": "^0.8.2",
81 | "start-server-and-test": "^2.0.0",
82 | "stylelint": "~15.11.0",
83 | "stylelint-config-recess-order": "~4.6.0",
84 | "stylelint-config-standard": "~31.0.0",
85 | "stylelint-processor-arbitrary-tags": "^0.1.0",
86 | "stylelint-scss": "~5.3.2",
87 | "typescript": "^5.0.4",
88 | "vite": "^4.1.4",
89 | "vitest": "^0.32.2",
90 | "vue-tsc": "^1.2.0"
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/cypress/e2e/apps/without-router/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | vue-tree-navigation
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/cypress/e2e/apps/without-router/running/barefoot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | vue-tree-navigation
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/dev/pages/Examples/Manual.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Menu items defined manually
4 | The following configuration
5 |
6 |
7 | <template>
8 | <vue-tree-navigation :items ="items" />
9 | </template >
10 |
11 | <script>
12 | export default {
13 | data() {
14 | return {
15 | items : [
16 | { name : 'Products' , children : [
17 | { name : 'Shoes' , path : 'shoes' }
18 | ]},
19 | { name : 'About' , path : 'about' , children : [
20 | { name : 'Contact' , path : 'contact' , children : [
21 | { name : 'E-mail' , element : 'email' },
22 | { name : 'Phone' , element : 'phone' }
23 | ]},
24 | ]},
25 | { name : 'Github' , external : 'https://github.com' },
26 | ],
27 | };
28 | },
29 | };
30 | </script >
31 |
32 |
33 | will generate
34 |
35 |
36 | Products (category label)
37 |
38 | Shoes (→ /shoes)
39 |
40 |
41 |
42 | About (→ /about)
43 |
44 |
45 | Contact (→ /about/contact)
46 |
47 | E-mail (→ /about/contact#email)
48 | Phone (→ /about/contact#phone)
49 |
50 |
51 |
52 |
53 | Github (→ https://github.com)
54 |
55 |
56 | Targets (except of external) will behave like router links in case you installed and
57 | configured vue-router. Otherwise they will behave like normal hyperlinks.
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/cypress/e2e/specs/without-router.spec.js:
--------------------------------------------------------------------------------
1 | describe('without router', () => {
2 | describe('when loaded first time', () => {
3 | beforeEach(() => {
4 | cy.visit('http://127.0.0.1:8000');
5 | });
6 |
7 | it('renders all menu items with a correct targets', () => {
8 | cy.contains('Home').should('have.attr', 'href', '');
9 | cy.contains('Products').should('have.attr', 'href', '/#products');
10 | cy.contains('Running').should('have.attr', 'href', '/running');
11 | cy.contains('Shoes').should('have.attr', 'href', '/running#shoes');
12 | cy.contains('Barefoot').should('have.attr', 'href', '/running/barefoot');
13 | cy.contains('Minimal').should('have.attr', 'href', '/running#minimal');
14 | cy.contains('Yoga').should('have.attr', 'href', '/yoga');
15 | cy.contains('Mats').should('have.attr', 'href', '/yoga/mats');
16 | cy.contains('Clothing').should(
17 | 'have.attr',
18 | 'href',
19 | 'https://www.yogarebel.com'
20 | );
21 | cy.contains('Tops').should('have.attr', 'href', '/yoga/tops');
22 | cy.contains('About').should('have.attr', 'href', '/about');
23 | cy.contains('Company').should('have.attr', 'href', '/about#company');
24 | cy.contains('Career').should('have.attr', 'href', '/about/career');
25 | cy.contains('Design').should('have.attr', 'href', '/about/career/design');
26 | cy.contains('Development').should(
27 | 'have.attr',
28 | 'href',
29 | '/about/career#development'
30 | );
31 | cy.contains('Github').should('have.attr', 'href', 'https://github.com');
32 | cy.contains('Press').should('have.attr', 'href', '/#press');
33 | });
34 |
35 | it('all root items are visible', () => {
36 | cy.contains('Home').should('be.visible');
37 | cy.contains('Products').should('be.visible');
38 | cy.contains('About').should('be.visible');
39 | cy.contains('Github').should('be.visible');
40 | cy.contains('Press').should('be.visible');
41 | });
42 |
43 | it('first level is open', () => {
44 | // "Products" is open
45 | cy.contains('Running').should('be.visible');
46 | cy.contains('Yoga').should('be.visible');
47 |
48 | // "About" is open
49 | cy.contains('Company').should('be.visible');
50 | cy.contains('Career').should('be.visible');
51 | });
52 |
53 | it('second level is open', () => {
54 | // "Running" is open
55 | cy.contains('Shoes').should('be.visible');
56 |
57 | // "Yoga" is open
58 | cy.contains('Mats').should('be.visible');
59 | cy.contains('Clothing').should('be.visible');
60 |
61 | // "Career" is open
62 | cy.contains('Design').should('be.visible');
63 | cy.contains('Development').should('be.visible');
64 | });
65 |
66 | it('third level is closed', () => {
67 | // "Shoes" is closed
68 | cy.contains('Barefoot').should('not.be.visible');
69 | cy.contains('Minimal').should('not.be.visible');
70 |
71 | // "Clothing" is closed
72 | cy.contains('Tops').should('not.be.visible');
73 | });
74 | });
75 |
76 | describe('when a target URL of menu item visited', () => {
77 | beforeEach(() => {
78 | cy.visit('http://127.0.0.1:8000/running/barefoot');
79 | });
80 |
81 | it('opens a corresponding level so the item is visible in menu', () => {
82 | cy.contains('Barefoot').should('be.visible');
83 | });
84 |
85 | it('assigns active class to an item which target URL is visited', () => {
86 | cy.contains('Barefoot')
87 | .parent()
88 | .should('have.class', 'NavigationItem--active');
89 | });
90 | });
91 | });
92 |
--------------------------------------------------------------------------------
/cypress/e2e/specs/with-router.spec.js:
--------------------------------------------------------------------------------
1 | describe('with router', () => {
2 | describe('when loaded first time', () => {
3 | beforeEach(() => {
4 | cy.visit('http://127.0.0.1:8000');
5 | });
6 |
7 | it('renders all menu items with a correct targets', () => {
8 | cy.contains('Home').should('have.attr', 'href', '#/');
9 | cy.contains('Products').should('have.attr', 'href', '#/#products');
10 | cy.contains('Running').should('have.attr', 'href', '#/running');
11 | cy.contains('Shoes').should('have.attr', 'href', '#/running#shoes');
12 | cy.contains('Barefoot').should('have.attr', 'href', '#/running/barefoot');
13 | cy.contains('Minimal').should('have.attr', 'href', '#/running#minimal');
14 | cy.contains('Yoga').should('have.attr', 'href', '#/yoga');
15 | cy.contains('Mats').should('have.attr', 'href', '#/yoga/mats');
16 | cy.contains('Clothing').should(
17 | 'have.attr',
18 | 'href',
19 | 'https://www.yogarebel.com'
20 | );
21 | cy.contains('Tops').should('have.attr', 'href', '#/yoga/tops');
22 | cy.contains('About').should('have.attr', 'href', '#/about');
23 | cy.contains('Company').should('have.attr', 'href', '#/about#company');
24 | cy.contains('Career').should('have.attr', 'href', '#/about/career');
25 | cy.contains('Design').should(
26 | 'have.attr',
27 | 'href',
28 | '#/about/career/design'
29 | );
30 | cy.contains('Development').should(
31 | 'have.attr',
32 | 'href',
33 | '#/about/career#development'
34 | );
35 | cy.contains('Github').should('have.attr', 'href', 'https://github.com');
36 | cy.contains('Press').should('have.attr', 'href', '#/#press');
37 | });
38 |
39 | it('all root items are visible', () => {
40 | cy.contains('Home').should('be.visible');
41 | cy.contains('Products').should('be.visible');
42 | cy.contains('About').should('be.visible');
43 | cy.contains('Github').should('be.visible');
44 | cy.contains('Press').should('be.visible');
45 | });
46 |
47 | it('first level is open', () => {
48 | // "Products" is open
49 | cy.contains('Running').should('be.visible');
50 | cy.contains('Yoga').should('be.visible');
51 |
52 | // "About" is open
53 | cy.contains('Company').should('be.visible');
54 | cy.contains('Career').should('be.visible');
55 | });
56 |
57 | it('second level is open', () => {
58 | // "Running" is open
59 | cy.contains('Shoes').should('be.visible');
60 |
61 | // "Yoga" is open
62 | cy.contains('Mats').should('be.visible');
63 | cy.contains('Clothing').should('be.visible');
64 |
65 | // "Career" is open
66 | cy.contains('Design').should('be.visible');
67 | cy.contains('Development').should('be.visible');
68 | });
69 |
70 | it('third level is closed', () => {
71 | // "Shoes" is closed
72 | cy.contains('Barefoot').should('not.be.visible');
73 | cy.contains('Minimal').should('not.be.visible');
74 |
75 | // "Clothing" is closed
76 | cy.contains('Tops').should('not.be.visible');
77 | });
78 | });
79 |
80 | describe('when a target URL of menu item visited', () => {
81 | beforeEach(() => {
82 | cy.visit('http://127.0.0.1:8000/#/running/barefoot');
83 | });
84 |
85 | it('opens a corresponding level so the item is visible in menu', () => {
86 | cy.contains('Barefoot').should('be.visible');
87 | });
88 |
89 | it('assigns active class to an item which target URL is visited', () => {
90 | cy.contains('Barefoot')
91 | .parent()
92 | .should('have.class', 'NavigationItem--active');
93 | });
94 | });
95 | });
96 |
--------------------------------------------------------------------------------
/src/components/TreeNavigation/core.ts:
--------------------------------------------------------------------------------
1 | import NavigationLevel from '../NavigationLevel/NavigationLevel.vue';
2 | import NavigationItem from '../NavigationItem/NavigationItem.vue';
3 |
4 | import { sanitizeElement, sanitizePath } from '../utils';
5 | import { h } from 'vue';
6 |
7 | export interface ItemMetaData {
8 | name?: string;
9 | element?: string;
10 | path?: string;
11 | external?: string;
12 | target?: string;
13 | meta?: ItemMetaData;
14 | children?: ItemMetaData[];
15 | }
16 |
17 | /**
18 | * Recursive function.
19 | * One call generates one level of the tree.
20 | */
21 | export const generateLevel = (
22 | //createElement: (el: String | Object | Function, prop: Object, children: VNode[]) => VNode,
23 | items: ItemMetaData[] | undefined,
24 | level: number,
25 | defaultOpenLevel: number
26 | ): any[] => {
27 | const children: any[] = [];
28 |
29 | items?.forEach((item) => {
30 | if (item.children) {
31 | const navLevel = h(
32 | NavigationLevel as never,
33 | {
34 | parentItem: item,
35 | level,
36 | defaultOpenLevel,
37 | },
38 | () => [...generateLevel(item.children, level + 1, defaultOpenLevel)]
39 | );
40 |
41 | children.push(h('li', {}, [navLevel]));
42 | } else {
43 | const navItem = h(
44 | NavigationItem as never,
45 | {
46 | item: item,
47 | },
48 | () => []
49 | );
50 |
51 | children.push(h('li', {}, [navItem]));
52 | }
53 | });
54 |
55 | return children;
56 | };
57 |
58 | /**
59 | * Recursive function.
60 | * Insert metadata containing the navigation path and its type to each item.
61 | **/
62 | export const insertMetadataToNavItems = (
63 | items: ItemMetaData[],
64 | parent?: ItemMetaData
65 | ): ItemMetaData[] => {
66 | items.forEach((item) => {
67 | item.meta = getItemMetadata(item, parent);
68 |
69 | if (item.children) {
70 | item.children = insertMetadataToNavItems(item.children, item);
71 | }
72 | });
73 |
74 | return items;
75 | };
76 |
77 | /**
78 | * Return item metadata object: { path: ..., target: ... }
79 | */
80 | export const getItemMetadata = (
81 | item: ItemMetaData,
82 | parent?: ItemMetaData
83 | ): ItemMetaData => {
84 | const element = sanitizeElement(item.element);
85 | const path = sanitizePath(item.path);
86 | const external = item.external;
87 |
88 | // item is its own parent
89 | if (parent === undefined) {
90 | if (element === undefined && path === undefined && external === undefined) {
91 | return {
92 | path: '',
93 | target: '',
94 | };
95 | }
96 |
97 | if (external !== undefined) {
98 | return {
99 | path: '',
100 | target: external,
101 | };
102 | }
103 |
104 | if (path !== undefined) {
105 | return {
106 | path,
107 | target: path,
108 | };
109 | }
110 |
111 | if (element !== undefined) {
112 | return {
113 | path: '',
114 | target: '/' + element,
115 | };
116 | }
117 | }
118 |
119 | const parentPath = sanitizePath(parent?.meta?.path);
120 |
121 | if (external !== undefined) {
122 | return {
123 | path: parentPath,
124 | target: external,
125 | };
126 | }
127 |
128 | if (path !== undefined) {
129 | return {
130 | path: parentPath + path,
131 | target: parentPath + path,
132 | };
133 | }
134 |
135 | if (element !== undefined && parentPath !== undefined) {
136 | return {
137 | path: parentPath,
138 | target: sanitizePath(parentPath + element),
139 | };
140 | }
141 |
142 | return {
143 | path: parentPath,
144 | target: '',
145 | };
146 | };
147 |
--------------------------------------------------------------------------------
/cypress/e2e/apps/with-router/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | vue-tree-navigation
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
158 |
159 |
160 |
--------------------------------------------------------------------------------
/dev/pages/Examples/Auto.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Examples
4 |
5 | Click a button and check how an active navigation item is higlighted in the tree on
6 | the left
7 |
8 |
13 |
14 | Navigation items generated from vue-router routes
15 | Let's suppose you use vue-router with the following routes definition
16 | const routes = [
17 | {
18 | name : 'Home' ,
19 | path : '/' ,
20 | },
21 | {
22 | name : 'Running' ,
23 | path : '/running' ,
24 | children : [
25 | {
26 | name : 'Barefoot' ,
27 | path : 'barefoot' ,
28 | },
29 | ],
30 | },
31 | {
32 | name : 'Yoga' ,
33 | path : '/yoga' ,
34 | children : [
35 | {
36 | name : 'Mats' ,
37 | path : 'mats' ,
38 | },
39 | {
40 | name : 'Tops' ,
41 | path : 'tops' ,
42 | },
43 | ],
44 | },
45 | {
46 | name : 'About' ,
47 | path : '/about' ,
48 | children : [
49 | {
50 | name : 'Career' ,
51 | path : 'career' ,
52 | children : [
53 | {
54 | name : 'Design' ,
55 | path : 'design' ,
56 | },
57 | ],
58 | },
59 | ],
60 | },
61 | ];
62 | Then simply include vue-tree-navigation
63 | <template>
64 | <vue-tree-navigation />
65 | </template >
66 | and it will generate the following menu:
67 |
68 | Home (→ /)
69 |
70 | Running (→ /running)
71 |
72 | Barefoot (→ /running/barefoot)
73 |
74 |
75 |
76 | Yoga (→ /yoga)
77 |
78 | Mats (→ /yoga/mats)
79 | Tops (→ /yoga/tops)
80 |
81 |
82 |
83 | About (→ /about)
84 |
85 |
86 | Career (→ /about/career)
87 |
88 | Design (→ /about/career/design)
89 |
90 |
91 |
92 |
93 |
94 |
95 | Do not forget to use named routes since vue-tree-navigation uses name field to
96 | label navigation items.
97 |
98 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/src/components/TreeNavigation/core.spec.ts:
--------------------------------------------------------------------------------
1 | import { getItemMetadata } from './core';
2 | import { describe, it, expect } from 'vitest';
3 |
4 | describe('TreeNavigation', () => {
5 | describe('core', () => {
6 | describe('getItemMetadata', () => {
7 | describe('without a parent', () => {
8 | describe('with a label item', () => {
9 | it('returns a correct metadata', () => {
10 | const item = {
11 | name: 'item',
12 | };
13 |
14 | const expected = {
15 | path: '',
16 | target: '',
17 | };
18 |
19 | expect(getItemMetadata(item)).toEqual(expected);
20 | });
21 | });
22 |
23 | describe('with an external item', () => {
24 | it('returns a correct metadata', () => {
25 | const item = {
26 | external: 'https://github.com',
27 | };
28 |
29 | const expected = {
30 | path: '',
31 | target: 'https://github.com',
32 | };
33 |
34 | expect(getItemMetadata(item)).toEqual(expected);
35 | });
36 | });
37 |
38 | describe('with a path item', () => {
39 | it('returns a correct metadata', () => {
40 | const item = {
41 | path: 'path',
42 | };
43 |
44 | const expected = {
45 | path: '/path',
46 | target: '/path',
47 | };
48 |
49 | expect(getItemMetadata(item)).toEqual(expected);
50 | });
51 | });
52 |
53 | describe('with an element item', () => {
54 | it('returns a correct metadata', () => {
55 | const item = {
56 | element: 'element',
57 | };
58 |
59 | const expected = {
60 | path: '',
61 | target: '/#element',
62 | };
63 |
64 | expect(getItemMetadata(item)).toEqual(expected);
65 | });
66 | });
67 | });
68 |
69 | describe('with a parent', () => {
70 | describe('with a label item', () => {
71 | it('returns correct metadata', () => {
72 | const parent = {
73 | meta: {
74 | path: '/home',
75 | target: '/home#element',
76 | },
77 | };
78 |
79 | const item = {
80 | name: 'item',
81 | };
82 |
83 | const expected = {
84 | path: '/home',
85 | target: '',
86 | };
87 |
88 | expect(getItemMetadata(item, parent)).toEqual(expected);
89 | });
90 | });
91 |
92 | describe('with an external item', () => {
93 | it('returns a correct metadata', () => {
94 | const parent = {
95 | meta: {
96 | path: '/home',
97 | target: '/home#element',
98 | },
99 | };
100 |
101 | const item = {
102 | external: 'https://github.com',
103 | };
104 |
105 | const expected = {
106 | path: '/home',
107 | target: 'https://github.com',
108 | };
109 |
110 | expect(getItemMetadata(item, parent)).toEqual(expected);
111 | });
112 | });
113 |
114 | describe('with a path item', () => {
115 | it('returns a correct metadata', () => {
116 | const parent = {
117 | meta: {
118 | path: '/home',
119 | target: '/home#element',
120 | },
121 | };
122 |
123 | const item = {
124 | path: 'path',
125 | };
126 |
127 | const expected = {
128 | path: '/home/path',
129 | target: '/home/path',
130 | };
131 |
132 | expect(getItemMetadata(item, parent)).toEqual(expected);
133 | });
134 | });
135 |
136 | describe('with an element item', () => {
137 | it('returns a correct metadata', () => {
138 | const parent = {
139 | meta: {
140 | path: '/home',
141 | target: '/home#element',
142 | },
143 | };
144 |
145 | const item = {
146 | element: 'contact',
147 | };
148 |
149 | const expected = {
150 | path: '/home',
151 | target: '/home#contact',
152 | };
153 |
154 | expect(getItemMetadata(item, parent)).toEqual(expected);
155 | });
156 | });
157 | });
158 | });
159 | });
160 | });
161 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-tree-navigation
2 | [](https://www.npmjs.com/package/vue-tree-navigation)
3 | [](https://github.com/J3-Tech/Vue-Tree-Navigation/actions/workflows/node.js.yml)
4 | [](https://github.com/J3-Tech/Vue-Tree-Navigation/actions/workflows/deploy.yaml)
5 | [](https://codecov.io/gh/J3-Tech/Vue-Tree-Navigation)
6 |
7 | > Vue.js tree navigation with vue-router support
8 |
9 | For more detailed information see [documentation/demo](https://vue-tree-navigation.j3-tech.com)
10 |
11 | ## Features
12 |
13 | - unlimited number of levels
14 | - optional [vue-router](https://router.vuejs.org/en/) support (v2.0.0 or higher)
15 | - generate navigation items automatically from _vue-router_ routes or define them manually
16 | - define a default open level
17 | - auto-open a level when a corresponding URL visited
18 | - focused on core functionality, only necessary styles included
19 | - elements are provided with meaningful classes to make customizations comfortable (for example `NavigationItem--active`, `NavigationLevel--level-1`, `NavigationLevel--closed`)
20 |
21 | ## Example
22 |
23 | ### 1. Navigation items generated from _vue-router_ routes
24 |
25 | Let's suppose you use _vue-router_ with the following routes definition
26 |
27 | ```javascript
28 | const routes = [
29 | {
30 | name: 'Home',
31 | path: '/',
32 | },
33 | {
34 | name: 'Running',
35 | path: '/running',
36 | children: [
37 | {
38 | name: 'Barefoot',
39 | path: 'barefoot',
40 | },
41 | ],
42 | },
43 | {
44 | name: 'Yoga',
45 | path: '/yoga',
46 | children: [
47 | {
48 | name: 'Mats',
49 | path: 'mats',
50 | },
51 | {
52 | name: 'Tops',
53 | path: 'tops',
54 | },
55 | ],
56 | },
57 | {
58 | name: 'About',
59 | path: '/about',
60 | children: [
61 | {
62 | name: 'Career',
63 | path: 'career',
64 | children: [
65 | {
66 | name: 'Design',
67 | path: 'design',
68 | },
69 | ],
70 | },
71 | ],
72 | },
73 | ];
74 | ```
75 |
76 | Then simply include _vue-tree-navigation_
77 |
78 | ```html
79 |
80 |
81 |
82 | ```
83 |
84 | and it will generate the following menu:
85 |
86 | ```
87 | - Home // --> /
88 | - Running // --> /running
89 | - Barefoot // --> /running/barefoot
90 | - Yoga // --> /yoga
91 | - Mats // --> /yoga/mats
92 | - Tops // --> /yoga/tops
93 | - About // --> /about
94 | - Career // --> /about/career
95 | - Design // --> /about/career/design
96 | ```
97 |
98 | Do not forget to use named routes since _vue-tree-navigation_ uses `name` field to label navigation items.
99 |
100 | ### 2. Menu items defined manually
101 |
102 | The following configuration
103 |
104 | ```html
105 |
106 |
107 |
108 |
109 |
129 | ```
130 |
131 | will generate
132 |
133 | ```
134 | - Products // category label
135 | - Shoes // --> /shoes
136 | - About // --> /about
137 | - Contact // --> /about/contact
138 | - E-mail // --> /about/contact#email
139 | - Phone // --> /about/contact#phone
140 | - Github // --> https://github.com
141 | ```
142 |
143 | For more examples see [documentation/demo](https://vue-tree-navigation.j3-tech.com)
144 |
145 | ## Installation
146 |
147 | ### NPM
148 |
149 | ```console
150 | $ npm install vue-tree-navigation
151 | ```
152 |
153 | _main.js_
154 |
155 | ```javascript
156 | import VueTreeNavigation from 'vue-tree-navigation';
157 |
158 | Vue.use(VueTreeNavigation);
159 | ```
160 |
161 | ### Include with a script tag
162 |
163 | ```html
164 |
165 |
166 |
169 | ```
170 |
171 | _Example_
172 |
173 | ```html
174 |
175 |
176 |
177 |
178 |
190 | ```
191 |
192 | ## Requirements
193 |
194 | - [Vue.js](https://v2.vuejs.org/)
195 |
196 | ## Developers
197 |
198 | ```console
199 | $ yarn dev
200 |
201 | $ yarn build
202 |
203 | $ yarn prettier
204 | $ yarn lint
205 |
206 | $ yarn unit
207 | $ yarn unit --verbose
208 |
209 | $ yarn e2e
210 | ```
211 |
--------------------------------------------------------------------------------
/dev/pages/Examples/Website.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | This website
4 |
5 | The side navigation for this documentation was built with vue-tree-navigation and
6 | the following setup.
7 |
8 |
9 | You need to style the tree navigation on your own like I did for this website. I
10 | included only a few basic styles in the library because I believe this approach
11 | makes customizing the look much easier as you don't need to override plenty of
12 | styles.
13 |
14 |
15 |
16 | <template>
17 | <vue-tree-navigation :items ="items" :defaultOpenLevel ="1" />
18 | </template >
19 |
20 | <script>
21 | export default {
22 | data() {
23 | return {
24 | items : [
25 | {
26 | name : "Introduction" ,
27 | path : "introduction" ,
28 | children : [
29 | { name : "Features" , element : "features" },
30 | { name : "Requirements" , element : "requirements" },
31 | { name : "Demo" , element : "demo" }
32 | ]
33 | },
34 | { name : "Installation" , path : "installation" },
35 | {
36 | name : "Usage" ,
37 | path : "usage" ,
38 | children : [
39 | {
40 | name : "Props" ,
41 | element : "props" ,
42 | children : [
43 | { name : "items" , element : "items" },
44 | { name : "defaultOpenLevel" , element : "default-open-level" }
45 | ]
46 | }
47 | ]
48 | },
49 | {
50 | name : "Examples" ,
51 | path : "examples" ,
52 | children : [
53 | { name : "Auto-generated" , path : "auto" },
54 | { name : "Manually defined" , path : "manual" },
55 | { name : "This website" , path : "this" }
56 | ]
57 | },
58 | { name : "Styling" , path : "styling" },
59 | {
60 | name : "Visit GitHub" ,
61 | external : "https://github.com/J3-Tech/Vue-Tree-Navigation"
62 | }
63 | ],
64 | };
65 | },
66 | };
67 | </script >
68 |
69 |
70 | vue-router configuration:
71 |
72 | const routes = [
73 | { path : "/" , redirect : "/introduction" },
74 | { path : "/introduction" , component : Introduction },
75 | { path : "/installation" , component : Installation },
76 | { path : "/usage" , component : Usage },
77 | {
78 | path : "/examples" ,
79 | component : Examples,
80 | redirect : "/examples/auto" ,
81 | children : [
82 | { path : "auto" , component : ExampleAuto },
83 | { path : "manual" , component : ExampleManual },
84 | { path : "this" , component : ExampleThis }
85 | ]
86 | },
87 | { path : "/styling" , component : Styling }
88 | ];
89 |
90 | Custom style adjustments:
91 |
92 | .NavigationLevel__children {
93 | padding-left : 10px ;
94 | }
95 |
96 | .NavigationLevel__parent {
97 | font-weight : 600 ;
98 | padding-bottom : 5px ;
99 | }
100 |
101 | .NavigationItem {
102 | color : #545454 ;
103 | padding : 0 ;
104 | }
105 |
106 | .NavigationItem--active {
107 | color : #42b883 ;
108 | }
109 |
110 | .NavigationToggle__icon {
111 | border-color : #42b883 ;
112 | }
113 |
114 |
115 |
116 |
--------------------------------------------------------------------------------