UNPKG

@components-1812/offcanvas

Version:

A web component for custom offcanvas, lateral panel

613 lines (403 loc) 19.2 kB
# Offcanvas custom element ![](./assets/preview.webp) ## Examples - [**Codepen**](https://codepen.io/collection/MWrrvV): [`Overview`](https://codepen.io/FrancoJavierGadea/pen/WbQzvqB) [`2 - Panel placement`](https://codepen.io/FrancoJavierGadea/pen/QwjmbXX) [`3 - Panel scroll`](https://codepen.io/FrancoJavierGadea/pen/empMNqZ) [`4 - Custom icons`](https://codepen.io/FrancoJavierGadea/pen/xbwWGvd) [`5 - Backdrop content`](https://codepen.io/FrancoJavierGadea/pen/GgpxJVX) [`6 - Backdrop static`](https://codepen.io/FrancoJavierGadea/pen/NPGYqQV) [`7 - Panel responsive`](https://codepen.io/FrancoJavierGadea/pen/yyYKYBN) - [**Stackblitz**](): [`Overview`](https://stackblitz.com/edit/vitejs-vite-puxrtegx?file=index.html) <br> ## Installation #### NPM ```bash npm install @components-1812/offcanvas ``` - [`JSON visualizer package`](https://www.npmjs.com/package/@components-1812/offcanvas) #### CDN Load the component bundle directly from a CDN — this will automatically register the element and inject styles: ```html <!-- Classic script --> <script src="https://cdn.jsdelivr.net/npm/@components-1812/offcanvas@0.0.3/dist/index.min.js"></script> <!-- ES module --> <script type="module"> import "https://cdn.jsdelivr.net/npm/@components-1812/offcanvas@0.0.3/dist/index.min.js"; </script> ``` Alternatively, you can manually import, load the styles, and define the element yourself: ```html <script type="module"> import Offcanvas from "https://cdn.jsdelivr.net/npm/@components-1812/offcanvas@0.0.3/src/Offcanvas.min.js"; // Load the stylesheet from a CDN Offcanvas.stylesSheets.links.push( "https://cdn.jsdelivr.net/npm/@components-1812/offcanvas@0.0.3/src/Offcanvas.min.css" ); //Define with the default tag Offcanvas.define(); </script> ``` - **jsdelivr**: [`Offcanvas package`](https://www.jsdelivr.com/package/npm/@components-1812/offcanvas) [`Offcanvas.js`](https://cdn.jsdelivr.net/npm/@components-1812/offcanvas@0.0.3/src/Offcanvas.min.js) [`Offcanvas.css`](https://cdn.jsdelivr.net/npm/@components-1812/offcanvas@0.0.3/src/Offcanvas.css) [`Offcanvas.min.js`](https://cdn.jsdelivr.net/npm/@components-1812/offcanvas@0.0.3/dist/Offcanvas.min.js) [`Offcanvas.min.css`](https://cdn.jsdelivr.net/npm/@components-1812/offcanvas@0.0.3/dist/Offcanvas.min.css) [`Bundle`](https://cdn.jsdelivr.net/npm/@components-1812/offcanvas@0.0.3/dist/index.min.js) - **unpkg**: [`Offcanvas package`](https://app.unpkg.com/@components-1812/offcanvas) [`Offcanvas.js`](https://unpkg.com/@components-1812/offcanvas@0.0.3/src/Offcanvas.js) [`Offcanvas.css`](https://unpkg.com/@components-1812/offcanvas@0.0.3/src/Offcanvas.css) [`Offcanvas.min.js`](https://unpkg.com/@components-1812/offcanvas@0.0.3/dist/Offcanvas.min.js) [`Offcanvas.min.css`](https://unpkg.com/@components-1812/offcanvas@0.0.3/dist/Offcanvas.min.css) [`Bundle`](https://unpkg.com/@components-1812/offcanvas@0.0.3/dist/index.min.js) <br> ## Usage ### Vite Ecosystem If you are using **Vite** or a **Vite-based** framework such as **Astro**, you can import the component in a *client-side script*: ```js import '@components-1812/offcanvas'; ``` and use it in your HTML: ```html <custom-offcanvas open variant="right global" handle-button> <!-- Panel content --> <div slot="header">Header</div> <div> <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quod.</p> </div> <div slot="footer">Footer</div> <!--Custom icons --> <div slot="close-button">❌</div> <div slot="handle-button" data-rotate-icon>➡️</div> <!-- Backdrop content --> <div slot="backdrop" style="position: absolute; top: 0; left: 0;"> <p>This is the backdrop content.</p> <p>You can add any content here.</p> </div> </custom-offcanvas> ``` ### Other Frameworks If you are using a builder or framework that does not support importing with `?raw`, you can load and register the component using the `bundle` version in `dist/index.min.js`, which includes all CSS injected via **CSS-in-JS** and `AdoptedStyleSheets`: ```js import '@components-1812/offcanvas/dist/index.min.js'; ``` For customizing the component definition or manually loading the stylesheets, see [Defining and Adding Stylesheets Manually](#Defining and Adding Stylesheets Manually). > **Node** > > The `dist` folder includes minified versions: `Offcanvas.min.css` and `Offcanvas.min.js`, which can be used anywhere. <br> ## Defining and Adding Stylesheets Manually If you want to add custom stylesheets to the component or need to load stylesheets from a different path, you can do it like this: - ### AdoptedStyleSheets (recommended) Using your builder’s import raw method, `CSSStyleSheet`, and the components `AdoptedStyleSheets` property: ```js import Offcanvas from "@components-1812/offcanvas/Offcanvas.js"; import OffcanvasRawCSS from "@components-1812/offcanvas/Offcanvas.css?raw"; //Create a CSSStyleSheet and add it to the component const OffcanvasCSS = new CSSStyleSheet(); OffcanvasCSS.replaceSync(OffcanvasRawCSS); Offcanvas.stylesSheets.adopted.push(OffcanvasCSS); //Define the component with default tag name Offcanvas.define(); ``` - ### Raw CSS in a `<style>` tag Using a `<style>` tag inside the shadow root of the component: ```js import Offcanvas from "@components-1812/offcanvas/Offcanvas.js"; //Add the raw stylesheet to the component const OffcanvasRawCSS = `:host { /* ...Offcanvas.css styles... */ }`; Offcanvas.stylesSheets.raw.push(OffcanvasRawCSS); //Define the component custom tag name Offcanvas.define('other-custom-tag-name'); ``` - ### External CSS files in a `<link>` tag Using a `<link>` tag inside the shadow root of the component: ```js import Offcanvas from "@components-1812/offcanvas/Offcanvas.js"; //Add the url source stylesheets to the component Offcanvas.stylesSheets.links.push('https://cdn.example.com/Offcanvas.css'); //Define the component manually customElements.define('custom-offcanvas', Offcanvas); ``` <br> ## Customization: CSS Variables ```css --offcanvas-position: absolute; --offcanvas-z-index: 8010; /* Panel */ --offcanvas-panel-width: 300px; --offcanvas-panel-height: 100%; --offcanvas-panel-padding: 5px; --offcanvas-panel-transition: margin 0.3s ease-in-out; --offcanvas-panel-header-padding: 5px; --offcanvas-panel-body-padding: 5px; --offcanvas-panel-footer-padding: 5px; --offcanvas-shadow: 0 1px 3px 0 #3c40434d, 0 4px 8px 3px #3c404326; --offcanvas-panel-bg: #222; --offcanvas-panel-color: #fff; --offcanvas-panel-header-bg: var(--offcanvas-panel-bg); --offcanvas-panel-header-color: var(--offcanvas-panel-color); --offcanvas-panel-footer-bg: var(--offcanvas-panel-bg); --offcanvas-panel-footer-color: var(--offcanvas-panel-color); --offcanvas-panel-border-width: 1px; --offcanvas-panel-border-style: solid; --offcanvas-panel-border-color: #ccc; --offcanvas-panel-border: var(--offcanvas-panel-border-width) var(--offcanvas-panel-border-style) var(--offcanvas-panel-border-color); --offcanvas-panel-border-radius: 0px; /* Backdrop */ --offcanvas-backdrop-bg: #00000080; --offcanvas-backdrop-color: #fff; --offcanvas-backdrop-transition: background-color 0.3s ease-in-out; /* Close button */ --offcanvas-close-button-width: 40px; --offcanvas-close-button-height: 40px; --offcanvas-close-button-padding: 10px; --offcanvas-close-button-bg: transparent; --offcanvas-close-button-color: #fff; --offcanvas-close-button-border: none; --offcanvas-close-button-font-size: 1.5rem; --offcanvas-close-button-cursor: pointer; /* Handle button */ --offcanvas-handle-button-width: 50px; --offcanvas-handle-button-height: 100px; --offcanvas-handle-button-padding: 5px; --offcanvas-handle-button-bg: #444; --offcanvas-handle-button-color: #fff; --offcanvas-handle-button-border: none; --offcanvas-handle-button-border-radius: 10px; --offcanvas-handle-button-cursor: pointer; --offcanvas-handle-button-font-size: 1rem; --offcanvas-handle-button-shadow: var(--offcanvas-shadow); ``` #### Top and Bottom Offcanvas ```css --offcanvas-panel-width: 100%; --offcanvas-panel-height: 300px; --offcanvas-handle-button-width: 100px; --offcanvas-handle-button-height: 50px; --offcanvas-panel-shadow: 1px 0 3px 0 #3c40434d, 4px 0 8px 3px #3c404326; ``` ### Responsive Panel For responsive design, there will be times when you want the panel to occupy the **full width of the screen**, especially on mobile devices. You can achieve this by adding a `CSS media query` like the following: ```css custom-offcanvas { @media (width <= 500px) { --offcanvas-panel-width: 100%; } } ``` This will ensure that the offcanvas panel expands to the **full screen width** on devices with a width of `500px` or less. > See the example: [7 - Panel responsive](https://codepen.io/FrancoJavierGadea/pen/yyYKYBN) <br><br> ## Open and close You can open the **offcanvas panel** using the `open` attribute: ```html <custom-offcanvas open></custom-offcanvas> ``` Via JavaScript, you can use the `open` property (mirrored attribute): ```js const offcanvas = document.querySelector('custom-offcanvas'); // Open the panel offcanvas.open = true; // Close the panel offcanvas.open = false; ``` You can also control the panel with the methods: `.show()`, `.hide()`, `.toggle()` ```js const offcanvas = document.querySelector('custom-offcanvas'); // Show the panel offcanvas.show(); // Hide the panel offcanvas.hide(); // Toggle the panel (switch state) offcanvas.toggle(); // toggle current state offcanvas.toggle(true); // force open offcanvas.toggle(false); // force close ``` <br><br> ## Slots The **default slot** is reserved for the **panel body**, which contains the main content. You can also use the following named slots: - `slot="header"` – for the panel header. - `slot="footer"` – for the panel footer. ```html <custom-offcanvas open variant="right" handle-button> <div slot="header">Header</div> <div> <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quod.</p> </div> <div slot="footer">Footer</div> </custom-offcanvas> ``` #### Customizing Buttons You can replace the default icons for the **close button** and the **handle button** (if present) using: - `slot="close-button"` - `slot="handle-button"` para modificar los iconos por defecto del close button y el handle button (si esta) puedes usar `slot="close-button"` y `slot="handle-button"` ```html <custom-offcanvas open variant="right" handle-button> <div slot="close-button">❌</div> <div slot="handle-button" data-rotate-icon>➡️</div> </custom-offcanvas> ``` > **Note** > > For the **handle button**, you can add the attribute `data-rotate-icon` so that the icon always rotates to point in the correct direction according to the panel position and open/closed state. > > To work correctly, the icon must initially point to the **right**. #### Backdrop The `backdrop` slot allows you to add custom content positioned around the panel for richer interactions or decorations. Elements in this slot should use `position: absolute;` Since the `backdrop` changes size when the **panel** opens or closes, the elements will move together with the panel. ```html <custom-offcanvas open variant="left" handle-button> <!-- Backdrop content --> <div slot="backdrop" style="position: absolute; top: 0; left: 0;"> <p>This is the backdrop content.</p> <p>You can add any content here.</p> </div> <div slot="backdrop" style="position: absolute; bottom: 0; right: 0;"> <p>This is another backdrop content.</p> <p>You can customize the backdrop as needed.</p> </div> </custom-offcanvas> ``` You can also control the visibility of backdrop elements based on the panel state using attributes like: - `data-hide-when-closed` – hides the element when the panel is closed. - `data-hide-when-opened` – hides the element when the panel is open. ```html <custom-offcanvas open variant="left" handle-button> <!-- Backdrop content --> <div slot="backdrop" style="position: absolute; top: 0; right: 0;" data-hide-when-closed> <p>This content will hide when the offcanvas is closed.</p> </div> <div slot="backdrop" style="position: absolute; bottom: 0; left: 0;" data-hide-when-opened> <p>This content will hide when the offcanvas is opened.</p> </div> </custom-offcanvas> ``` > See the example: [5 - Backdrop content](https://codepen.io/FrancoJavierGadea/pen/GgpxJVX) <br><br> ## Variants ### Panel Global and Local Positioning You can control whether the **offcanvas panel** is positioned relative to the `viewport` or inside its container using the `variant` attribute: - `local` (default) – The panel is positioned `absolute` relative to its nearest positioned ancestor. - `global` – The panel is positioned `fixed` relative to the viewport. This is useful for modals or overlays. ```html <!-- Fixed to the viewport --> <custom-offcanvas variant="top global"></custom-offcanvas> <!-- Positioned inside a container --> <div class="container" style="position: relative;"> <custom-offcanvas variant="left local"></custom-offcanvas> </div> ``` ### Panel Placement The `variant` attribute defines the position of the **offcanvas panel**: `left`, `right`, `top`, `bottom` ```html <custom-offcanvas variant="left"></custom-offcanvas> <custom-offcanvas variant="right"></custom-offcanvas> <custom-offcanvas variant="top"></custom-offcanvas> <custom-offcanvas variant="bottom"></custom-offcanvas> ``` > See the example: [2 - Panel placement](https://codepen.io/FrancoJavierGadea/pen/QwjmbXX) ### Panel Scroll By default, the panel does not scroll. You can customize the scroll behavior using the following variants: - `scroll-full`: The entire panel content scrolls. - `scroll-inner`: All content scrolls except the header - `scroll-body`: Only the body scrolls; header and footer remain fixed. ```html <custom-offcanvas variant="scroll-full"></custom-offcanvas> <custom-offcanvas variant="scroll-inner"></custom-offcanvas> <custom-offcanvas variant="scroll-body"></custom-offcanvas> ``` > See the example: [3 - Panel scroll](https://codepen.io/FrancoJavierGadea/pen/empMNqZ) ### Handle button By default, the component does not provide any control to open the panel; you must add it manually. With the handle-button attribute, the component automatically adds a button inside the backdrop content, centered along the edge of the panel. This button allows users to open and close the panel. ```html <custom-offcanvas variant="right" handle-button></custom-offcanvas> ``` ### Static and Transparent Backdrop - `backdrop-static`: By default, clicking on the backdrop closes the **panel**. You can disable this behavior by adding the backdrop-static variant: ```html <custom-offcanvas variant="backdrop-static"></custom-offcanvas> ``` With this `variant`, clicking on the backdrop won’t close the **panel**, and the page behind it remains interactive while the **panel** is open. - `backdrop-transparent` If you want to hide the backdrop background when the **panel** is open, you have two options: - Override the CSS variable: `--offcanvas-backdrop-bg` - Or, more easily, use the `backdrop-transparent` variant: ```html <custom-offcanvas variant="backdrop-transparent"></custom-offcanvas> ``` <br><br> ## API ### Attributes - `variant`: Panel position or visual style. - Panel position: `left`, `top`, `right`, `bottom` - Panel scroll: `scroll-full`, `scroll-inner`, `scroll-body` - Page position: `global`, `local` - `open` (boolean, default false): Whether the panel is open. - `handle-button` (boolean, default false): Whether to show a floating handle button. ### Properties #### Mirrored - `variant` (string): Panel position or visual style. - `open` (boolean): Whether the panel is open. - `handleButton` (boolean): Whether the handle button is visible. ### Methods `show()`: Opens the panel (open = true). `hide()`: Closes the panel (open = false). `toggle(force?: boolean)`: Toggles the panel. If force is `true` or `false`, sets the panel state accordingly. ### Events - `ready-links`: Fired when all external stylesheet links have finished loading. Provides a detail with the results of each stylesheet. - `ready`: Dispatched when the component has finished initializing (end of connectedCallback) ### Static properties - `VERSION` (string): Component version (0.0.3). - `DEFAULT_TAG_NAME` (string): Default tag name (`custom-offcanvas`) use it to define the custom element in `index.js` and `define.js` - `DEFAULT_ICONS` (object): Default svg icons for `close-button` and `handle-button`. ```js Offcanvas.DEFAULT_ICONS = { 'close-button': `<svg>...</svg>`, 'handle-button': `<svg data-rotate-icon>...</svg>` } ``` - `stylesSheets` (object): Contains `links`, `adopted`, and `raw` stylesheets to apply to the component. ```js Offcanvas.stylesSheets = { links: [],//string url css source adopted: [],//CSSStyleSheet instances raw: [],//string raw css }; ``` - `define(tagName?, stylesSheets?)` (function): Defines the custom element and optionally adds stylesheets if the element is not already registered. ```js Offcanvas.define('custom-offcanvas', { links: ['https://cdn.example.com/Offcanvas.css'], adopted: [new CSSStyleSheet()], raw: [':host { /* ...Offcanvas.css styles... */ }'], }); ``` > Internally, this method calls `window.customElements.define(tagName, Offcanvas)` ### Slots - `default`: Main content of the panel - `header`: Header content of the panel - `footer`: Footer content of the panel - `close-button`: Icon or content for the close button in the header of the panel - `handle-button`: Icon or content for the handle button if added - `backdrop`: Content inside the backdrop (around the panel) <br><br> ## License This package is distributed under the [MIT license](./LICENSE). ## Credits Default icons used in this package are sourced from the [Bootstrap Icons](https://icons.getbootstrap.com/) project, licensed under the MIT license. © 2019–2024 The Bootstrap Authors - [x-lg](https://icons.getbootstrap.com/icons/x-lg/) - [arrow-bar-right](https://icons.getbootstrap.com/icons/arrow-bar-right/)