@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
387 lines (370 loc) • 13.8 kB
JavaScript
import { withActions } from "@storybook/addon-actions/decorator";
import { html } from "lit";
import { ifDefined } from "lit/directives/if-defined.js";
import { createShadowHostClass } from "../../providers/shadow-dom/shadow-host-helper";
const meta = {
title: 'Components/Dropdown Menu',
component: 'modus-wc-dropdown-menu',
args: {
'button-aria-label': 'Dropdown menu button',
'button-color': 'primary',
'button-shape': 'rectangle',
'button-size': 'md',
'button-variant': 'filled',
disabled: false,
'menu-bordered': true,
'menu-offset': 14,
'menu-placement': 'bottom-start',
'menu-size': 'md',
'menu-strategy': 'absolute',
'menu-visible': false,
},
argTypes: {
'button-color': {
control: { type: 'select' },
options: ['primary', 'secondary', 'tertiary', 'warning', 'danger'],
},
'button-size': {
control: { type: 'select' },
options: ['xs', 'sm', 'md', 'lg', 'xl'],
},
'button-shape': {
control: { type: 'select' },
options: ['circle', 'ellipse', 'rectangle', 'square'],
},
'button-variant': {
control: { type: 'select' },
options: ['borderless', 'filled', 'outlined'],
},
'menu-placement': {
control: { type: 'select' },
options: [
'top',
'top-start',
'top-end',
'bottom',
'bottom-start',
'bottom-end',
'left',
'left-start',
'left-end',
'right',
'right-start',
'right-end',
],
},
'menu-size': {
control: { type: 'select' },
options: ['sm', 'md', 'lg'],
},
'menu-strategy': {
control: { type: 'select' },
options: ['absolute', 'fixed'],
},
},
decorators: [withActions],
parameters: {
actions: {
handles: ['menuVisibilityChange'],
},
},
};
export default meta;
const Template = {
render: (args) => {
let selectedValue = '';
const handleItemSelect = (event) => {
selectedValue = event.detail.value;
const displayElement = document.querySelector('#selected-value');
if (displayElement) {
displayElement.textContent = selectedValue;
}
// Close the dropdown menu when an item is selected
const dropdownMenu = event.target;
const dropdownMenuElement = dropdownMenu.closest('modus-wc-dropdown-menu');
if (dropdownMenuElement) {
dropdownMenuElement.menuVisible = false;
}
};
// prettier-ignore
return html `
<style>
/* Storybook styling */
div
display: flex;
align-items: center;
height: 240px;
}
[] {
display: flex;
align-items: center;
gap: 4px;
}
.value {
font-size: 14px;
padding-top: 12px;
}
</style>
<modus-wc-dropdown-menu
button-aria-label=${ifDefined(args['button-aria-label'])}
button-color=${ifDefined(args['button-color'])}
button-shape=${ifDefined(args['button-shape'])}
button-size=${ifDefined(args['button-size'])}
button-variant=${ifDefined(args['button-variant'])}
custom-class=${ifDefined(args['custom-class'])}
?disabled=${args.disabled}
?menu-bordered=${args['menu-bordered']}
menu-offset=${ifDefined(args['menu-offset'])}
menu-placement=${ifDefined(args['menu-placement'])}
menu-size=${ifDefined(args['menu-size'])}
menu-strategy=${ifDefined(args['menu-strategy'])}
?menu-visible=${args['menu-visible']}
>
<div slot="button">
Button
<modus-wc-icon name="expand_more" size="sm" />
</div>
<div slot="menu">
<modus-wc-menu-item label="Item One" value="1" @itemSelect=${handleItemSelect}></modus-wc-menu-item>
<modus-wc-menu-item label="Item Two" value="2" @itemSelect=${handleItemSelect} /></modus-wc-menu-item>
<modus-wc-menu-item label="Item Three" value="3" @itemSelect=${handleItemSelect} /></modus-wc-menu-item>
</div>
</modus-wc-dropdown-menu>
<script>
// // Adding this block to handle menu item selection to update a label and close the dropdown via JS.
// let selectedValue = '';
// const handleItemSelect = (event) => {
// // Update the "Selected Value" label
// selectedValue = event.detail.value;
// const displayElement = document.querySelector('#selected-value');
// if (displayElement) {
// displayElement.textContent = selectedValue;
// }
// // Close the dropdown menu when an item is selected
// const dropdownMenu = event.target;
// const dropdownMenuElement = dropdownMenu.closest(
// 'modus-wc-dropdown-menu'
// );
// if (dropdownMenuElement) {
// dropdownMenuElement.menuVisible = false;
// }
// };
// const menuItems = document.querySelectorAll('modus-wc-menu-item');
// menuItems.forEach(item => {
// item.addEventListener('itemSelect', handleItemSelect);
// });
</script>
<div class="value">
Selected Value:
<span id="selected-value"></span>
</div>
`;
},
};
export const Default = Object.assign({}, Template);
export const IconOnlyDropdownMenu = {
render: () => {
// prettier-ignore
return html `
<style>
/* Storybook styling */
div[id^='story--components-dropdown-menu--icon-only-dropdown-menu'] {
height: 60px;
}
</style>
<modus-wc-dropdown-menu button-shape="square">
<div slot="button">
<modus-wc-icon decorative name="more_vertical"></modus-wc-icon>
</div>
<div slot="menu">
<modus-wc-menu-item label="Item One"></modus-wc-menu-item>
</div>
</modus-wc-dropdown-menu>
`;
},
};
export const WithTreeMenu = {
args: {
'menu-bordered': false,
'menu-placement': 'bottom-end',
'menu-size': 'sm',
},
parameters: {
actions: {
handles: ['menuVisibilityChange', 'itemSelect'],
},
docs: {
source: {
code: `
<modus-wc-dropdown-menu
button-variant="filled"
button-color="primary"
menu-placement="bottom-end"
menu-size="sm"
id="tree-dropdown-menu"
>
<div slot="button">
Browse
<modus-wc-icon name="expand_more" size="sm"></modus-wc-icon>
</div>
<modus-wc-tree-menu slot="menu" aria-label="Tree menu" bordered="true" size="sm">
<modus-wc-tree-item label="Projects" value="projects"></modus-wc-tree-item>
<modus-wc-tree-item label="Explorer" value="explorer"></modus-wc-tree-item>
</modus-wc-tree-menu>
</modus-wc-dropdown-menu>
<div>
Selected Value: <span id="tree-dropdown-selected-value"></span>
</div>
<script>
const dropdown = document.getElementById('tree-dropdown-menu');
const display = document.getElementById('tree-dropdown-selected-value');
dropdown.addEventListener('itemSelect', (e) => {
display.textContent = e.detail.value;
dropdown.menuVisible = false;
});
</script>
`,
},
},
},
render: (args) => {
const handleItemSelect = (event) => {
const displayElement = document.querySelector('#tree-dropdown-selected-value');
if (displayElement) {
displayElement.textContent = event.detail.value;
}
const dropdownMenuElement = event.target.closest('modus-wc-dropdown-menu');
if (dropdownMenuElement) {
dropdownMenuElement.menuVisible = false;
}
};
// prettier-ignore
return html `
<style>
div[id^='story--components-dropdown-menu--with-tree-menu'] {
display: flex;
align-items: center;
height: 320px;
}
[] {
display: flex;
align-items: center;
gap: 4px;
}
.value {
font-size: 14px;
padding-top: 12px;
}
</style>
<modus-wc-dropdown-menu
button-aria-label=${ifDefined(args['button-aria-label'])}
button-color=${ifDefined(args['button-color'])}
button-shape=${ifDefined(args['button-shape'])}
button-size=${ifDefined(args['button-size'])}
button-variant=${ifDefined(args['button-variant'])}
custom-class=${ifDefined(args['custom-class'])}
?disabled=${args.disabled}
?menu-bordered=${args['menu-bordered']}
menu-offset=${ifDefined(args['menu-offset'])}
menu-placement=${ifDefined(args['menu-placement'])}
menu-size=${ifDefined(args['menu-size'])}
menu-strategy=${ifDefined(args['menu-strategy'])}
?menu-visible=${args['menu-visible']}
>
<div slot="button">
Browse
<modus-wc-icon name="expand_more" size="sm"></modus-wc-icon>
</div>
<modus-wc-tree-menu slot="menu" aria-label="Tree menu" bordered="true" size="sm">
<modus-wc-tree-item
label="Projects"
value="projects"
@itemSelect=${handleItemSelect}
></modus-wc-tree-item>
<modus-wc-tree-item
label="Explorer"
value="explorer"
@itemSelect=${handleItemSelect}
></modus-wc-tree-item>
</modus-wc-tree-menu>
</modus-wc-dropdown-menu>
<div class="value">
Selected Value:
<span id="tree-dropdown-selected-value"></span>
</div>
`;
},
};
export const ShadowDomParent = {
render: (args) => {
if (!customElements.get('dropdown-menu-shadow-host')) {
const DropdownMenuShadowHost = createShadowHostClass({
componentTag: 'modus-wc-dropdown-menu',
propsMapper: (v, el) => {
var _a;
const dropdownEl = el;
dropdownEl.buttonAriaLabel = v['button-aria-label'] || '';
dropdownEl.buttonColor = v['button-color'] || 'primary';
dropdownEl.buttonShape = v['button-shape'] || 'rectangle';
dropdownEl.buttonSize = v['button-size'];
dropdownEl.buttonVariant = v['button-variant'] || 'filled';
dropdownEl.customClass = v['custom-class'] || '';
dropdownEl.disabled = Boolean(v.disabled);
dropdownEl.menuBordered = Boolean(v['menu-bordered']);
dropdownEl.menuOffset = (_a = v['menu-offset']) !== null && _a !== void 0 ? _a : 10;
dropdownEl.menuPlacement = v['menu-placement'];
dropdownEl.menuSize = v['menu-size'];
dropdownEl.menuStrategy = v['menu-strategy'] || 'absolute';
dropdownEl.menuVisible = Boolean(v['menu-visible']);
// On first render: add slot content and append the Selected Value
// display as a sibling of el inside the helper's display:contents
// wrapper — both become direct layout children of the shadow root.
if (!el.hasAttribute('data-layout-built')) {
el.setAttribute('data-layout-built', '');
const wrapper = el.parentElement;
const buttonSlot = document.createElement('div');
buttonSlot.setAttribute('slot', 'button');
buttonSlot.style.cssText =
'display: flex; align-items: center; gap: 4px;';
buttonSlot.appendChild(document.createTextNode('Button'));
const expandIcon = document.createElement('modus-wc-icon');
expandIcon.setAttribute('name', 'expand_more');
expandIcon.setAttribute('size', 'sm');
buttonSlot.appendChild(expandIcon);
const menuSlot = document.createElement('div');
menuSlot.setAttribute('slot', 'menu');
[
{ label: 'Item One', value: '1' },
{ label: 'Item Two', value: '2' },
{ label: 'Item Three', value: '3' },
].forEach(({ label, value }) => {
const item = document.createElement('modus-wc-menu-item');
item.setAttribute('label', label);
item.setAttribute('value', value);
menuSlot.appendChild(item);
});
el.appendChild(buttonSlot);
el.appendChild(menuSlot);
// Selected value display as sibling in wrapper
const valueDiv = document.createElement('div');
valueDiv.style.cssText = 'font-size: 14px; padding-top: 12px;';
valueDiv.textContent = 'Selected Value: ';
const valueSpan = document.createElement('span');
valueDiv.appendChild(valueSpan);
wrapper.appendChild(valueDiv);
el.addEventListener('itemSelect', (e) => {
var _a, _b;
const custom = e;
valueSpan.textContent = (_b = (_a = custom.detail) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : '';
el.menuVisible = false;
});
}
},
});
customElements.define('dropdown-menu-shadow-host', DropdownMenuShadowHost);
}
return html `<dropdown-menu-shadow-host
.props=${Object.assign({}, args)}
></dropdown-menu-shadow-host>`;
},
};