├── .github └── dependabot.yml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .nvmrc ├── .prettierignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── netlify.toml ├── package.json ├── prettier.config.js ├── public ├── apple-touch-icon.png ├── favicon.png └── index.html ├── rollup.config.package.js ├── rollup.config.playground.js ├── serve.json ├── src ├── App.svelte ├── components │ ├── Button.svelte │ ├── ColorInput.svelte │ ├── NumericInput.svelte │ ├── Sidebar │ │ ├── NavigationLink.svelte │ │ ├── NavigationLinkGroup.svelte │ │ ├── Sidebar.svelte │ │ ├── SidebarStore.js │ │ └── index.js │ ├── Textarea.svelte │ └── ThemeSwitcher.svelte ├── exampleConfig │ ├── routes.js │ └── sidebarProps.js ├── main.js ├── pages │ ├── documentation │ │ └── Readme.svelte │ └── playground │ │ ├── Playground.svelte │ │ └── SidebarCodePreview.svelte └── utils │ ├── compareObjects.js │ ├── copyObjectDeep.js │ ├── detectDarkMode.js │ └── prettyPrintJson.js └── yarn.lock /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'npm' 4 | directory: '/' 5 | schedule: 6 | interval: 'weekly' 7 | versioning-strategy: 'increase' 8 | ignore: 9 | # For all dependencies, ignore all minor and patch updates 10 | - dependency-name: '*' 11 | update-types: 12 | ['version-update:semver-minor', 'version-update:semver-patch'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | public/*.css 4 | public/*.js 5 | public/*.map 6 | stats.html 7 | dist 8 | *.log -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | yarn lint-staged 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 16 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.md 2 | stats.html -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | ## [0.1.1](https://github.com/philipp-tailor/svelte_sidebar/releases/tag/0.1.1) - 2022-04-19 9 | ### Fixed 10 | - Conditional exports in `package.json` reference valid file paths after build, see 922bb46d6228ac0d6aa6357356b2b4981ba85294. Fixes #504. 11 | - Screen reader in Safari announces link groups as lists, see 31b3f69273a04d96fadba7de534c3167198ec74c. 12 | - Theme switch no longer flickers after mount, see e9f3c334a2bb2e2dc856bde16e640b3a24987ab2. 13 | ### Changed 14 | - Style definitions use CSS logical properties, see 2fb17d85e617960a4f2f9ede26f5ebcef5162700. 15 | ## [0.1.0](https://github.com/philipp-tailor/svelte_sidebar/releases/tag/0.1.0) - 2022-04-18 16 | ### Added 17 | - Allow to collapse link groups by default with `collapseTree: true`, see 4688ba561dc7152d96bf402efd3ba9c1624a6a98. Implements #456. 18 | - Allow to disable transitions with `transitionEnabled: false`, see 01b07d7921050621561f80532c34567f5407ffde. 19 | - Automatically disable transitions when `prefers-reduced-motion` is set, see dccebea7e1d057e5c9c5a7b8165d5f8c7667fe49. 20 | - Indicate currently active route with `aria-current="true"`, see a1ee97e7b7ac426240265d0410969ebd77350e59. 21 | ### Changed 22 | - Update (dev) dependencies. 23 | - Fix typo from `README.md`, remove broken badge, more cleanups. 24 | ## [0.0.1] 25 | ### Added 26 | - Initial Release 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Philipp Rottner 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Svelte Sidebar 2 | ![npm](https://img.shields.io/npm/v/svelte_sidebar) 3 | ![NPM](https://img.shields.io/npm/l/svelte_sidebar) 4 | ![Bundlephobia](https://badgen.net/bundlephobia/minzip/svelte_sidebar) 5 | [![Netlify Status](https://api.netlify.com/api/v1/badges/e957276f-3f1c-413a-ad78-2b5894f82de9/deploy-status)](https://app.netlify.com/sites/admiring-kare-b5e53d/deploys) 6 | [![Deploy playground to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/philipp-tailor/svelte_sidebar) 7 | 8 | Sidebar navigation component written in svelte and compiled to an ES module of 8kb. 9 | 10 | Check it out [in the playground](https://sidebar.schneiders.space). 11 | 12 | ## What it is 13 | 14 | This sidebar navigation for web application replicates the experience of browsing the file tree in a code editor. The benefit is that the user can understand the application's information architecture (IA) at first glance, and can quickly jump between different hierarchy levels. 15 | The UI pattern goes well with complex web applications with a lot of nested routes, especially for applications where the user can create data entries (e.g. where multiple users create projects, each of which is composed of multiple screens). 16 | 17 | ## Features 18 | 19 | * Usable in any web-app where the navigation routes are known / computable, as the sidebar is an ES module. 20 | * Semantic, accessible DOM. Navigation is entirely possible with the keyboard. Animations are disabled when `prefers-reduced-motion` is set. 21 | * The navigation hierarchy can be nested as deeply as required. Navigation links with sub-links are collapsible groups. 22 | * Automatically un-collapses the active part of the navigation hierarchy and scrolls it into the viewport. Parent navigation items of the active navigation item are also highlighted compared to inactive navigation items. 23 | * Every navigation link can be disabled. 24 | * Just enough configuration possibilities for re-usability: 25 | - By default, the regular browser navigation occurs when the user clicks on a link (or presses `Enter`). A function can be passed as property to interrupt the regular navigation to handle navigation on the client side. 26 | - Themeable: Colors (including hover styles), font sizing, and the minimum and maximum width (depending on the length of the names of navigation links). 27 | * h(n) performance when matching the active route in the navigation hierarchy, where `n` signifies the count of navigation links. 28 | 29 | ## Installation 30 | 31 | * Without installation from [skypack](https://www.skypack.dev/view/svelte_sidebar): 32 | 33 | ```js 34 | import Sidebar from 'https://cdn.skypack.dev/svelte_sidebar' 35 | ``` 36 | 37 | * Yarn package manager: `yarn add svelte_sidebar` 38 | * NPM package manager: `npm install --save svelte_sidebar` 39 | 40 | ## Usage 41 | 42 | ### On any web site / app as ES module 43 | 44 | ```html 45 | 46 |
47 | 48 | 64 | ``` 65 | 66 | ### In a svelte web app 67 | 68 | ```js 69 | // App.svelte 70 | import Sidebar from 'svelte_sidebar' // or from skypack 71 | 72 | const props = {...} 73 | 74 | 75 | ``` 76 | 77 | ## Sidebar component properties 78 | 79 | | property name | required | default | description | value type | example value | 80 | | ------------- | -------- | ------- | ----------- | ---------- | ------------- | 81 | | `activeUrl` | no | `null` | The URL that is active in the application. | string | `'https://sidebar.``schneiders.space'`. In a SPA, one would pass the string in from the router, or `window.location.path` | 82 | | `routes` | no | `[]` | This property contains the navigation hierarchy rendered by the sidebar. It's made up from an array of objects mapping URLs to navigation link names and defining the nesting of sub-routes. Not passing values is possible and could e.g. be used while fetching the data required to compute the menu hierarchy. | array, as specified below | see `src/exampleConfig/``routes.js` | 83 | | `onLinkClick` | no | `null` | Function that is executed when the user selects a navigation link. Prevents the regular navigation event. Therefore useful in applications with client side routing. | function receiving the event | `(event) => alert(event.target.href)` | 84 | | `theme` | no | see `defaultTheme` in `src/components/``Sidebar/``Sidebar.svelte` | Allows to customize the most important styles. | object, as described below | `{ backgroundColor_nav: '###ccc' }` | 85 | | `open` | no | true | Allows to customize whether the navigation sidebar is open or horizontally minimized. | boolean | `true`. To collapse the sidebar by default on smaller viewports, the result of an expression like `window.innerWidth < 720px` could be passed. | 86 | | `transitionEnabled` | no | `true` | Whether the route's content will transition in when being displayed. The default is `true`. `False` is less playful, and always used when `prefers-reduced-motion` is enabled. | `boolean` | 87 | 88 | 89 | [The playground](https://sidebar.schneiders.space) allows to play around with every property. It shows the sidebar on the left, and the component usage on the right. It's therefore the quickest way to find out how to use the component, and to see whether it covers your needs. 90 | 91 | ### The `routes` array 92 | 93 | As specified above, `routes` is an array of navigation route objects. Every one of those objects can have the attributes shown below (others are ignored): 94 | 95 | ```js 96 | { 97 | name: 'Products', 98 | route: '/products', 99 | disabled: true, /* optional */ 100 | collapseTree: true, /* optional */ 101 | childRoutes: [] /* optional */ 102 | } 103 | ``` 104 | 105 | Following the description of a route object's attributes: 106 | 107 | | attribute name | required | default | description | value type | 108 | | -------------- | -------- | -------- | ----------- | ---------- | 109 | | ´name´ | yes | - | The name under which the navigation link will be shown in the navigation hierarchy | `string` | 110 | | ´route´ | yes | - | The navigation links' URL | `string` | 111 | | `disabled` | no | `false` | Whether the link is selectable by the user. The default is `false`. `True` can make sense, e.g. if the user is not authorized to access a part of the application. If the route has child routes, the navigation link group can not be uncollapsed by the user. | `boolean` | 112 | | `collapseTree` | no | `false` | Allows to customize whether child routes are shown or vertically collapsed by default. | boolean | 113 | | `childRoutes` | no | `[]` | An array of more route objects. As every route object can have child routes, there's theoretically no limit to the depth of the navigation hierarchy. | `array` | 114 | 115 | ### The `theme` property 116 | 117 | The theme property object takes the following attributes with matching CSS values: 118 | 119 | | attribute name | default | description | 120 | | -------------- | ------- | ----------- | 121 | | `backgroundColor_linkActive` | #F4442E | Background color of the active link in the navigation hierarchy. | 122 | | `backgroundColor_nav` | #555B6E | Background color of the whole navigation sidebar. | 123 | | `color_link` | #F7F7F2 | Text color of a navigation link in default state. | 124 | | `color_linkHover` | #FCA311 | Text color of a navigation link when it's hovered upon or focused with the keyboard. | 125 | | `fontSize` | 1.2rem | Font size of navigation links. | 126 | | `maxWidth_nav` | 20vw | The maximum width of the navigation sidebar. Navigation links' name will wrap into multiple lines, if their width in addition to their nesting would surpass `maxWidth_nav`. | 127 | | `minWidth_nav` | 320px | Minimum width of the navigation sidebar. `minWidth_nav` is stronger than `maxWidth_nav`. If min width is larger than max width, min width will define the applied style. | 128 | | `opacity_linkDisabled` | 0.5 | Text opacity of navigation links, that have been specified to be disabled. | 129 | | `opacity_linkInactive` | 0.75 | Text opacity of navigation links that are not part of the navigation hierarchy that contains the active link. | 130 | 131 | The property names are CSS custom properties attached to the sidebar element. 132 | 133 | ## Slots 134 | 135 | The sidebar can also be passed children that are placed in pre-defined slots in the sidebar. There's a slot named `header` that is rendered above all navigation links (e.g. for company name and logo), and one below all navigation links called `footer`. On the playground, this slot is used to render the theme switcher. 136 | 137 | ## License 138 | 139 | The code is licensed under MIT, see `LICENSE`. 140 | 141 | ## Contribution 142 | 143 | This code-base is meant to be a one-time effort. I don't plan to extend it, unless there's actual usage and user feedback. 144 | 145 | Feel free to report issues, and/or to open PRs. I'll typically respond within a business day. 146 | 147 | If you want to contribute, you are welcome to pick one of the open issues. 148 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = "public" 3 | command = "yarn build:playground" 4 | 5 | [build.processing] 6 | skip_processing = true 7 | 8 | [[redirects]] 9 | from = "https://admiring-kare-b5e53d.netlify.com/*" 10 | to = "https://sidebar.schneiders.space/" 11 | status = 302 12 | force = true 13 | 14 | [[redirects]] 15 | from = "/*" 16 | to = "/index.html" 17 | status = 200 18 | 19 | [[headers]] 20 | for = "/*" 21 | [headers.values] 22 | X-Content-Type-Options = "nosniff" 23 | X-Frame-Options = "DENY" 24 | X-XSS-Protection = "1; mode=block" -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte_sidebar", 3 | "version": "0.1.1", 4 | "keywords": [ 5 | "svelte", 6 | "sidebar", 7 | "sidebar-component", 8 | "sidebar-navigation", 9 | "collapsible-tree", 10 | "navigation-links", 11 | "navigation-hierarchy", 12 | "navigation-routes" 13 | ], 14 | "homepage": "http://sidebar.schneiders.space", 15 | "bugs": { 16 | "url": "https://github.com/philipp-tailor/svelte_sidebar/issues" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/philipp-tailor/svelte_sidebar.git" 21 | }, 22 | "license": "MIT", 23 | "contributors": [ 24 | "Philipp Tailor (http://schneiders.space)" 25 | ], 26 | "exports": { 27 | "import": "./dist/index.mjs", 28 | "default": "./dist/index.js", 29 | ".": { 30 | "svelte": "./dist/index.mjs" 31 | } 32 | }, 33 | "main": "dist/index.js", 34 | "module": "dist/index.mjs", 35 | "files": [ 36 | "dist", 37 | "src/components/Sidebar" 38 | ], 39 | "scripts": { 40 | "autobuild:playground": "rollup -c rollup.config.playground.js -w", 41 | "build:package": "rollup -c rollup.config.package.js", 42 | "build:playground": "rollup -c rollup.config.playground.js", 43 | "clean:playground": "find ./public -type f -name \"*.js\" -o -name \"*.map\" -o -name \"*.css\" | xargs rm", 44 | "dev:playground": "yarn clean:playground && yarn start:dev:playground & yarn autobuild:playground", 45 | "prepublishOnly": "pinst --disable", 46 | "postpublish": "pinst --enable", 47 | "start:dev:playground": "serve" 48 | }, 49 | "lint-staged": { 50 | "*.{js,css,json,svelte}": [ 51 | "prettier --write" 52 | ] 53 | }, 54 | "devDependencies": { 55 | "@rollup/plugin-commonjs": "^24.0.1", 56 | "@rollup/plugin-node-resolve": "^13.2.0", 57 | "husky": "^8.0.1", 58 | "lint-staged": "^13.0.3", 59 | "pinst": "^3.0.0", 60 | "prettier": "^2.6.0", 61 | "prettier-plugin-packagejson": "^2.2.15", 62 | "prettier-plugin-svelte": "^2.7.0", 63 | "rollup": "^2.70.1", 64 | "rollup-plugin-css-only": "^4.3.0", 65 | "rollup-plugin-livereload": "^2.0.5", 66 | "rollup-plugin-md": "^1.0.1", 67 | "rollup-plugin-svelte": "^7.1.0", 68 | "rollup-plugin-terser": "^7.0.2", 69 | "rollup-plugin-visualizer": "^5.6.0", 70 | "serve": "^14.2.0", 71 | "svelte": "^3.49.0" 72 | }, 73 | "engines": { 74 | "node": ">=16.0.0" 75 | }, 76 | "svelte": "./dist/index.mjs" 77 | } 78 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | tabWidth: 4, 3 | useTabs: true, 4 | semi: false, 5 | singleQuote: true, 6 | endOfLine: 'lf', 7 | } 8 | -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philipp-tailor/svelte_sidebar/c3e80249a4539d332f63f15cb25230f2fb417d46/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philipp-tailor/svelte_sidebar/c3e80249a4539d332f63f15cb25230f2fb417d46/public/favicon.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Svelte Sidebar 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /rollup.config.package.js: -------------------------------------------------------------------------------- 1 | import resolve from '@rollup/plugin-node-resolve' 2 | import svelte from 'rollup-plugin-svelte' 3 | import { terser } from 'rollup-plugin-terser' 4 | 5 | import pkg from './package.json' 6 | 7 | export default { 8 | input: 'src/components/Sidebar/Sidebar.svelte', 9 | output: [ 10 | { file: pkg.module, format: 'es' }, 11 | { file: pkg.main, format: 'umd', name: 'Sidebar' }, 12 | ], 13 | plugins: [svelte({ emitCss: false }), resolve(), terser()], 14 | } 15 | -------------------------------------------------------------------------------- /rollup.config.playground.js: -------------------------------------------------------------------------------- 1 | import commonjs from '@rollup/plugin-commonjs' 2 | import resolve from '@rollup/plugin-node-resolve' 3 | import svelte from 'rollup-plugin-svelte' 4 | import md from 'rollup-plugin-md' 5 | import css from 'rollup-plugin-css-only' 6 | import livereload from 'rollup-plugin-livereload' 7 | import { terser } from 'rollup-plugin-terser' 8 | import visualizer from 'rollup-plugin-visualizer' 9 | 10 | const production = !process.env.ROLLUP_WATCH 11 | 12 | export default { 13 | input: ['src/main.js', `src/components/Sidebar/Sidebar.svelte`], 14 | output: { 15 | chunkFileNames: '[name].js', 16 | sourcemap: true, 17 | format: 'esm', 18 | name: 'app', 19 | dir: 'public/', 20 | }, 21 | plugins: [ 22 | svelte({ 23 | compilerOptions: { 24 | // enable run-time checks when not in production 25 | dev: !production, 26 | }, 27 | }), 28 | // we'll extract any component CSS out into 29 | // a separate file - better for performance 30 | css({ output: 'bundle.css' }), 31 | 32 | // If you have external dependencies installed from 33 | // npm, you'll most likely need these plugins. In 34 | // some cases you'll need additional configuration — 35 | // consult the documentation for details: 36 | // https://github.com/rollup/rollup-plugin-commonjs 37 | resolve({ 38 | browser: true, 39 | dedupe: ['svelte'], 40 | }), 41 | commonjs(), 42 | 43 | md({ marked: {} }), 44 | 45 | // Watch the `public` directory and refresh the 46 | // browser on changes when not in production 47 | !production && livereload('public'), 48 | 49 | // If we're building for production (npm run build 50 | // instead of npm run dev), minify 51 | production && terser(), 52 | 53 | production && visualizer({ open: true, title: 'bundle visualizer' }), 54 | ], 55 | watch: { 56 | clearScreen: false, 57 | }, 58 | } 59 | -------------------------------------------------------------------------------- /serve.json: -------------------------------------------------------------------------------- 1 | { 2 | "public": "public", 3 | "rewrites": [{ "source": "/**", "destination": "/index.html" }] 4 | } 5 | -------------------------------------------------------------------------------- /src/App.svelte: -------------------------------------------------------------------------------- 1 | 51 | 52 | 53 | (pathname = window.location.pathname)} /> 54 | 55 | 59 | 60 |
61 | 62 |
63 |
64 | 65 |
66 | 67 | {#if pathname && pathname.includes('/readme')} 68 | 69 | {:else} 70 | 71 | {/if} 72 |
73 | 74 | 140 | -------------------------------------------------------------------------------- /src/components/Button.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | 10 | 43 | -------------------------------------------------------------------------------- /src/components/ColorInput.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | 43 | -------------------------------------------------------------------------------- /src/components/NumericInput.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 15 | 16 | 62 | -------------------------------------------------------------------------------- /src/components/Sidebar/NavigationLink.svelte: -------------------------------------------------------------------------------- 1 | 51 | 52 | 62 | {name} 63 | 64 | 65 | 113 | -------------------------------------------------------------------------------- /src/components/Sidebar/NavigationLinkGroup.svelte: -------------------------------------------------------------------------------- 1 | 58 | 59 | 60 | {#if name && route} 61 | 65 | {#if !disabled} 66 | 77 | {/if} 78 | 82 | 89 | {/if} 90 | 91 | 92 | 97 | 130 | 131 | 170 | -------------------------------------------------------------------------------- /src/components/Sidebar/Sidebar.svelte: -------------------------------------------------------------------------------- 1 | 57 | 58 | 76 | 77 | 134 | -------------------------------------------------------------------------------- /src/components/Sidebar/SidebarStore.js: -------------------------------------------------------------------------------- 1 | import { writable, derived } from 'svelte/store' 2 | 3 | /* 4 | * The store allows to avoid unnecessary prop-drilling. 5 | */ 6 | 7 | export const activeUrl = writable(null) 8 | 9 | export const onLinkClick = writable(null) 10 | 11 | export const transitionEnabled = writable(true) 12 | 13 | export const transitionDurationInMs = derived( 14 | transitionEnabled, 15 | ($transitionEnabled) => ($transitionEnabled ? 250 : 0) 16 | ) 17 | -------------------------------------------------------------------------------- /src/components/Sidebar/index.js: -------------------------------------------------------------------------------- 1 | import Sidebar from './Sidebar.svelte' 2 | 3 | export default Sidebar 4 | -------------------------------------------------------------------------------- /src/components/Textarea.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | {#if label} 10 | 11 | {/if} 12 |