@trimble-oss/moduswebcomponents
Version:
Modus Web Components is a modern, accessible UI library built with Stencil JS that provides reusable web components following Trimble's Modus design system. This updated version focuses on improved flexibility, enhanced theming options, comprehensive cust
569 lines (532 loc) • 18.9 kB
JavaScript
import { withActions } from "@storybook/addon-actions/decorator";
import { html } from "lit";
import { ifDefined } from "lit/directives/if-defined.js";
const meta = {
title: 'Components/Side Navigation',
component: 'modus-wc-side-navigation',
args: {
'collapse-on-click-outside': true,
expanded: false,
'max-width': '256px',
mode: 'push',
'target-content': '.panel-content',
},
argTypes: {
'max-width': {
control: { type: 'text' },
description: 'Maximum width of the side navigation panel in an expanded state.',
},
mode: {
control: { type: 'select' },
options: ['overlay', 'push'],
description: 'Display mode of the side navigation (overlay or push).',
},
},
decorators: [withActions],
parameters: {
layout: 'padded',
actions: {
handles: ['expandedChange'],
},
},
};
export default meta;
export const Default = {
render: (args) => {
const handleMenuOpenChange = (e) => {
const eventSource = e.target;
const storyContainer = eventSource === null || eventSource === void 0 ? void 0 : eventSource.closest('.layout-with-navbar');
let sideNav;
if (storyContainer) {
sideNav = storyContainer.querySelector('modus-wc-side-navigation');
}
else {
sideNav = document.querySelector('modus-wc-side-navigation');
}
if (sideNav) {
sideNav.expanded = e.detail;
}
};
return html `
<style>
.layout-with-navbar {
box-shadow: rgba(36, 35, 45, 0.3) 1px 0 4px;
display: flex;
flex-direction: column;
height: 100%;
}
.main-content-row {
display: flex;
flex: 1;
overflow: hidden;
}
.modus-wc-menu-item-labels {
padding: 0 16px;
}
.navbar {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.panel-content {
margin-left: 4rem;
padding: 10px;
}
.side-navigation {
height: 500px;
align-self: flex-start;
position: relative;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', () => {
const menuItems = document.querySelectorAll('modus-wc-menu-item');
menuItems.forEach((item) => {
item.addEventListener('itemSelect', () => {
menuItems.forEach((i) => i.removeAttribute('selected'));
item.setAttribute('selected', '');
});
});
});
function handleMenuOpenChange(e) {
const eventSource = e.target;
const storyContainer = eventSource?.closest('.layout-with-navbar');
let sideNav;
if (storyContainer) {
sideNav = storyContainer.querySelector('modus-wc-side-navigation');
} else {
sideNav = document.querySelector('modus-wc-side-navigation');
}
if (sideNav) {
sideNav.expanded = e.detail;
}
}
</script>
<div class="layout-with-navbar">
<modus-wc-navbar
app-title="Modus App"
class="navbar"
logo="/assets/logo.svg"
@mainMenuOpenChange=${handleMenuOpenChange}
.userCard=${{
avatarAlt: 'User Avatar',
avatarSrc: 'https://i1.sndcdn.com/artworks-000405996468-wmh3uv-t500x500.jpg',
email: 'user@trimble.com',
name: 'Sonic the Hedgehog',
}}
.visibility=${{
ai: true,
apps: true,
help: true,
mainMenu: true,
notifications: true,
search: true,
searchInput: false,
user: true,
}}
style="z-index: 2;"
></modus-wc-navbar>
<div class="main-content-row">
<modus-wc-side-navigation
class="side-navigation"
collapse-on-click-outside=${args['collapse-on-click-outside']}
custom-class=${ifDefined(args['custom-class'])}
expanded=${args.expanded}
max-width=${args['max-width']}
mode=${ifDefined(args.mode)}
target-content=${ifDefined(args['target-content'])}
>
<modus-wc-menu size="lg">
<modus-wc-menu-item label="home" selected>
<modus-wc-icon slot="start-icon" name="home"></modus-wc-icon>
</modus-wc-menu-item>
<modus-wc-menu-item label="profile">
<modus-wc-icon slot="start-icon" name="person"></modus-wc-icon>
</modus-wc-menu-item>
<modus-wc-menu-item label="settings">
<modus-wc-icon slot="start-icon" name="gears"></modus-wc-icon>
</modus-wc-menu-item>
</modus-wc-menu>
</modus-wc-side-navigation>
<div class="panel-content">
<div id="overview">
<p>
The side navigation of an application provides context through
accessible menu options and positions a consistent component to
connect to various pages in the application.
</p>
<p>
The side navigation is a collapsible side content of the site’s
pages. It is located alongside the page’s primary content. The
component is designed to add side content to a fullscreen
application. It is activated through the “hamburger” menu in the
Navbar.
</p>
</div>
</div>
</div>
</div>
`;
},
};
export const collapsibleMenu = {
render: (args) => {
const handleMenuOpenChange = (e) => {
const eventSource = e.target;
const storyContainer = eventSource === null || eventSource === void 0 ? void 0 : eventSource.closest('.layout-with-navbar');
let sideNav;
if (storyContainer) {
sideNav = storyContainer.querySelector('modus-wc-side-navigation');
}
else {
sideNav = document.querySelector('modus-wc-side-navigation');
}
if (sideNav) {
sideNav.expanded = e.detail;
}
};
const handleExpandChange = (e) => {
if (!e.detail) {
const eventSource = e.target;
const container = eventSource === null || eventSource === void 0 ? void 0 : eventSource.closest('.layout-with-navbar');
if (container) {
const childrenContainers = container.querySelectorAll('.children-container');
childrenContainers.forEach((container) => {
container.classList.add('hidden');
container.setAttribute('aria-hidden', 'true');
});
const collapseIcons = container.querySelectorAll('.dropdown-toggle');
collapseIcons.forEach((icon) => {
if (icon.getAttribute('name') === 'expand_less') {
icon.setAttribute('name', 'expand_more');
}
});
}
}
};
return html `
<style>
.children-container {
transition: height 0.2s ease-out;
}
.collapse-icon {
min-width: 24px;
padding-inline-start: 0.2rem;
}
.dropdown-menu {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.flex-row {
align-items: center;
display: flex;
gap: 1.3rem;
padding: 0.8rem 0.25rem;
padding-inline-start: 1rem;
}
.hidden {
display: none;
}
.justify-end {
margin-left: auto;
}
.layout-with-navbar {
box-shadow: rgba(36, 35, 45, 0.3) 1px 0 4px;
display: flex;
flex-direction: column;
height: 100%;
}
.main-content-row {
display: flex;
flex: 1;
overflow: hidden;
}
.menu-icon {
margin-right: 1rem;
}
.menu-item {
color: var(--modus-wc-color-gray-9);
display: block;
font-size: 16px;
line-height: 1.5;
padding: 0.5rem 1rem;
text-decoration: none;
}
.menu-width {
width: 100%;
}
.modus-wc-menu li ul {
margin-inline-start: 1.8rem;
}
.modus-wc-menu-dropdown {
padding-left: 1rem;
}
.modus-wc-menu-dropdown-toggle {
align-items: center;
cursor: pointer;
font-size: 16px;
line-height: 1.5;
padding: 0.7rem 1.25rem;
}
.navbar {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.nested-row {
padding-inline-start: 2.5rem;
}
.panel-content {
margin-left: 4rem;
padding: 10px;
}
.side-navigation {
align-self: flex-start;
height: 500px;
position: relative;
}
ul {
list-style: none;
margin: 0;
padding: 0;
}
</style>
<script>
function handleMenuOpenChange(e) {
const eventSource = e.target;
const storyContainer = eventSource?.closest('.layout-with-navbar');
let sideNav;
if (storyContainer) {
sideNav = storyContainer.querySelector('modus-wc-side-navigation');
} else {
sideNav = document.querySelector('modus-wc-side-navigation');
}
if (sideNav) {
sideNav.expanded = e.detail;
}
}
function handleCollapseToggle(e) {
const clickedEl = e.currentTarget;
const parentLi = clickedEl.closest('li');
if (!parentLi) return;
// Find the icon element that needs to be toggled using the dropdown-toggle class
const iconEl = clickedEl.querySelector('.dropdown-toggle');
if (!iconEl) return;
// Find the parent side nav element
const parentContainer = clickedEl.closest('.layout-with-navbar');
const sideNav = parentContainer?.querySelector(
'modus-wc-side-navigation'
);
// Toggle between expand_more and expand_less icons only if side nav is expanded
const isExpanded = iconEl.getAttribute('name') === 'expand_more';
if (sideNav?.expanded) {
iconEl.setAttribute(
'name',
isExpanded ? 'expand_less' : 'expand_more'
);
}
// Find and toggle children visibility
const childContainer =
parentLi.nextElementSibling?.classList.contains(
'children-container'
)
? parentLi.nextElementSibling
: null;
if (childContainer && sideNav?.expanded) {
childContainer.classList.toggle('hidden');
childContainer.setAttribute(
'aria-hidden',
!isExpanded ? 'true' : 'false'
);
}
}
function handleExpandChange(e) {
if (!e.detail) {
const eventSource = e.target;
const container = eventSource?.closest('.layout-with-navbar');
if (container) {
// Collapse all child containers if the side navigation is collapsed
const childrenContainers = container.querySelectorAll(
'.children-container'
);
childrenContainers.forEach((container) => {
container.classList.add('hidden');
container.setAttribute('aria-hidden', 'true');
});
// Reset all collapse icons to expand_more
const collapseIcons =
container.querySelectorAll('.dropdown-toggle');
collapseIcons.forEach((icon) => {
if (icon.getAttribute('name') === 'expand_less') {
icon.setAttribute('name', 'expand_more');
}
});
}
}
}
</script>
<div class="layout-with-navbar">
<modus-wc-navbar
app-title="Modus App"
class="navbar"
logo="/assets/logo.svg"
@mainMenuOpenChange=${handleMenuOpenChange}
.userCard=${{
avatarAlt: 'User Avatar',
avatarSrc: 'https://i1.sndcdn.com/artworks-000405996468-wmh3uv-t500x500.jpg',
email: 'user@trimble.com',
name: 'Sonic the Hedgehog',
}}
.visibility=${{
ai: true,
apps: true,
help: true,
mainMenu: true,
notifications: true,
search: true,
searchInput: false,
user: true,
}}
style="z-index: 2;"
></modus-wc-navbar>
<div class="main-content-row">
<modus-wc-side-navigation
class="side-navigation"
collapse-on-click-outside=${args['collapse-on-click-outside']}
custom-class=${ifDefined(args['custom-class'])}
expanded=${args.expanded}
max-width=${args['max-width']}
mode=${ifDefined(args.mode)}
target-content=${ifDefined(args['target-content'])}
@expandedChange=${handleExpandChange}
>
<modus-wc-menu aria-label="Custom menu" custom-class="menu-width">
<li>
<div class="flex-row" onClick="handleCollapseToggle(event)">
<modus-wc-icon
decorative="true"
name="bar_graph"
class="collapse-icon icon-left"
></modus-wc-icon>
<div class="dropdown-menu">Charts</div>
<div class="justify-end">
<modus-wc-icon
decorative="true"
name="expand_more"
class="collapse-icon dropdown-toggle"
></modus-wc-icon>
</div>
</div>
</li>
<li class="children-container hidden" aria-hidden="true">
<ul>
<li>
<div class="flex-row nested-row">
<div>Bar Chart</div>
</div>
</li>
<li>
<div class="flex-row nested-row">
<div>Line Chart</div>
</div>
</li>
</ul>
</li>
<!-- Item without children -->
<li>
<div class="flex-row">
<modus-wc-icon
decorative="true"
name="calendar"
class="collapse-icon icon-left"
></modus-wc-icon>
<div class="dropdown-menu">Calendar</div>
</div>
</li>
<!-- Second parent group (collapsed) -->
<li>
<div class="flex-row" onClick="handleCollapseToggle(event)">
<modus-wc-icon
decorative="true"
name="compass"
class="collapse-icon icon-left"
></modus-wc-icon>
<div class="dropdown-menu">Maps</div>
<div class="justify-end">
<modus-wc-icon
decorative="true"
name="expand_more"
class="collapse-icon dropdown-toggle"
></modus-wc-icon>
</div>
</div>
</li>
<li class="children-container hidden" aria-hidden="true">
<ul>
<li>
<div class="flex-row nested-row">
<div>Map 1</div>
</div>
</li>
<li>
<div
class="flex-row nested-row"
onClick="handleCollapseToggle(event)"
>
<div>Map 2</div>
<div class="justify-end">
<modus-wc-icon
decorative="true"
name="expand_more"
class="collapse-icon dropdown-toggle"
></modus-wc-icon>
</div>
</div>
</li>
<li class="children-container hidden" aria-hidden="true">
<ul>
<li>
<div
class="flex-row"
style="padding-inline-start: 2rem;"
>
<div>Map 1</div>
</div>
</li>
<li>
<div
class="flex-row"
style="padding-inline-start: 2rem;"
>
<div>Map 2</div>
</div>
</li>
</ul>
</li>
<li>
<div class="flex-row nested-row">
<div>Map 3</div>
</div>
</li>
</ul>
</li>
</modus-wc-menu>
</modus-wc-side-navigation>
<div class="panel-content">
<div id="overview">
<p>
The side navigation of an application provides context through
accessible menu options and positions a consistent component to
connect to various pages in the application.
</p>
<p>
The side navigation is a collapsible side content of the site's
pages. It is located alongside the page's primary content. The
component is designed to add side content to a fullscreen
application. It is activated through the "hamburger" menu in the
Navbar.
</p>
</div>
</div>
</div>
</div>
`;
},
};