@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
303 lines (286 loc) • 11.3 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/Button Group',
component: 'modus-wc-button-group',
args: {
variant: 'outlined',
color: 'primary',
disabled: false,
orientation: 'horizontal',
'selection-type': 'default',
},
argTypes: {
variant: {
control: { type: 'select' },
options: ['borderless', 'filled', 'outlined'],
},
color: {
control: { type: 'select' },
options: [
'primary',
'secondary',
'tertiary',
'warning',
'danger',
'neutral',
],
},
disabled: { control: 'boolean' },
orientation: {
control: { type: 'select' },
options: ['horizontal', 'vertical'],
},
'selection-type': {
control: { type: 'select' },
options: ['default', 'single', 'multiple'],
},
},
decorators: [withActions],
parameters: {
actions: {
handles: ['buttonSelectionChange', 'buttonGroupClick'],
},
},
};
export default meta;
const Template = {
render: (args) => {
// prettier-ignore
return html `
<modus-wc-button-group
variant=${ifDefined(args.variant)}
color=${ifDefined(args.color)}
?disabled=${(args.disabled)}
orientation=${ifDefined(args.orientation)}
selection-type=${ifDefined(args['selection-type'])}
>
<modus-wc-button>Button 1</modus-wc-button>
<modus-wc-button>Button 2</modus-wc-button>
<modus-wc-button>Button 3</modus-wc-button>
</modus-wc-button-group>
`;
},
};
export const Default = Object.assign({}, Template);
export const Vertical = Object.assign(Object.assign({}, Template), { args: {
variant: 'outlined',
orientation: 'vertical',
} });
export const SplitButton = {
render: () => {
// prettier-ignore
return html `
<script>
const handleItemSelect = (event) => {
// Update the "Selected Value" label
const 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;
}
};
window.addEventListener('DOMContentLoaded', () => {
const menuItems = document.querySelectorAll('#split-button-menu modus-wc-menu-item');
menuItems.forEach(item =>
item.addEventListener('itemSelect', handleItemSelect)
);
});
</script>
<div style="display: flex; flex-direction: column; gap: 16px; min-height: 300px;">
<modus-wc-button-group variant="outlined">
<modus-wc-button>Save Document</modus-wc-button>
<modus-wc-dropdown-menu
menu-placement="bottom-end"
menu-size="lg"
>
<div slot="button">
<modus-wc-icon name="expand_more" size="16"></modus-wc-icon>
</div>
<div slot="menu" id="split-button-menu">
<modus-wc-menu-item label="Save as Draft" value="Save as Draft"></modus-wc-menu-item>
<modus-wc-menu-item label="Save as Template" value="Save as Template"></modus-wc-menu-item>
<modus-wc-menu-item label="Save and Share" value="Save and Share"></modus-wc-menu-item>
</div>
</modus-wc-dropdown-menu>
</modus-wc-button-group>
<div style="display: flex; gap: 8px;">
<modus-wc-typography hierarchy="h4">Selected Option:</modus-wc-typography>
<modus-wc-typography id="selected-value"></modus-wc-typography>
</div>
</div>
`;
},
};
export const SingleSelection = {
render: () => {
// prettier-ignore
return html `
<div style="display: flex; flex-direction: column; gap: 16px;">
<div>
<h4 style="margin-bottom: 8px;">Single Selection (Only one button can be active at a time)</h4>
<modus-wc-button-group selection-type="single" variant="outlined">
<modus-wc-button>Option 1</modus-wc-button>
<modus-wc-button pressed>Option 2</modus-wc-button>
<modus-wc-button>Option 3</modus-wc-button>
<modus-wc-button>Option 4</modus-wc-button>
</modus-wc-button-group>
</div>
<div>
<h4 style="margin-bottom: 8px;">Vertical Single Selection</h4>
<modus-wc-button-group selection-type="single" orientation="vertical" variant="outlined">
<modus-wc-button pressed>Option 1</modus-wc-button>
<modus-wc-button>Option 2</modus-wc-button>
<modus-wc-button>Option 3</modus-wc-button>
<modus-wc-button>Option 4</modus-wc-button>
</modus-wc-button-group>
</div>
</div>
`;
},
};
export const MultipleSelection = {
render: () => {
// prettier-ignore
return html `
<div style="display: flex; flex-direction: column; gap: 16px;">
<div>
<h4 style="margin-bottom: 8px;">Multiple Selection (Multiple buttons can be active)</h4>
<modus-wc-button-group selection-type="multiple" variant="outlined">
<modus-wc-button pressed>Bold</modus-wc-button>
<modus-wc-button>Italic</modus-wc-button>
<modus-wc-button pressed>Underline</modus-wc-button>
<modus-wc-button>Strikethrough</modus-wc-button>
</modus-wc-button-group>
</div>
<div>
<h4 style="margin-bottom: 8px;">Multiple Selection with Icons</h4>
<modus-wc-button-group selection-type="multiple" variant="outlined" color="primary">
<modus-wc-button pressed icon-only aria-label="Align left">
<modus-wc-icon name="align_left" size="16"></modus-wc-icon>
</modus-wc-button>
<modus-wc-button pressed icon-only aria-label="Align center">
<modus-wc-icon name="align_top" size="16"></modus-wc-icon>
</modus-wc-button>
<modus-wc-button icon-only aria-label="Align right">
<modus-wc-icon name="align_right" size="16"></modus-wc-icon>
</modus-wc-button>
<modus-wc-button icon-only aria-label="Align justify">
<modus-wc-icon name="align_bottom" size="16"></modus-wc-icon>
</modus-wc-button>
</modus-wc-button-group>
</div>
</div>
`;
},
};
export const SelectionEvent = {
render: () => {
// prettier-ignore
return html `
<script>
window.addEventListener('DOMContentLoaded', () => {
const buttonGroup = document.querySelector('#event-demo-group');
if (buttonGroup) {
buttonGroup.addEventListener('buttonSelectionChange', (event) => {
const selectedButtons = event.detail.selectedButtons;
const buttonTexts = selectedButtons.map(btn => btn.textContent.trim());
// Update the display
const displayElement = document.querySelector('#selected-buttons-display');
if (displayElement) {
displayElement.textContent = buttonTexts.length > 0
? buttonTexts.join(', ')
: 'None';
}
});
}
});
</script>
<div style="display: flex; flex-direction: column; gap: 16px;">
<div>
<modus-wc-button-group
id="event-demo-group"
selection-type="single"
variant="outlined"
color="primary"
>
<modus-wc-button>Apple</modus-wc-button>
<modus-wc-button>Banana</modus-wc-button>
<modus-wc-button>Cherry</modus-wc-button>
<modus-wc-button>Date</modus-wc-button>
</modus-wc-button-group>
</div>
<div style="display: flex; gap: 8px;">
<modus-wc-typography hierarchy="h4">Selected Button(s):</modus-wc-typography>
<modus-wc-typography id="selected-buttons-display"></modus-wc-typography>
</div>
`;
},
};
export const ShadowDomParent = {
render: (args) => {
if (!customElements.get('button-group-shadow-host')) {
const ButtonGroupShadowHost = createShadowHostClass({
componentTag: 'modus-wc-button-group',
propsMapper: (v, el) => {
const bgEl = el;
bgEl.variant = v.variant;
bgEl.color = v.color;
bgEl.disabled = Boolean(v.disabled);
bgEl.orientation = v.orientation;
bgEl.selectionType = v['selection-type'];
if (!el.hasChildNodes()) {
el.innerHTML = `<modus-wc-button>Button 1</modus-wc-button><modus-wc-button>Button 2</modus-wc-button><modus-wc-button>Button 3</modus-wc-button>`;
}
},
});
customElements.define('button-group-shadow-host', ButtonGroupShadowHost);
}
return html `<button-group-shadow-host
.props=${Object.assign({}, args)}
></button-group-shadow-host>`;
},
};
export const Migration = {
parameters: {
docs: {
description: {
story: `
#### Breaking Changes
- The \`size\` prop has been removed. Button sizes are now controlled individually on each button within the group.
- Selection state is now managed internally - the \`selected\` attribute on individual buttons is no longer used.
- Shadow DOM has been removed (\`shadow: false\`) for better composability.
#### Prop Mapping
| 1.0 Prop | 2.0 Prop | Notes |
|-----------------|-----------------|--------------------------------------------------------------------------|
| aria-label | aria-label | Unchanged |
| aria-disabled | - | Removed. Use \`disabled\` prop instead |
| button-style | variant | Values changed: \`fill\` → \`filled\`, \`outline\` → \`outlined\` |
| color | color | \`special\` color removed. Other values unchanged |
| disabled | disabled | Unchanged |
| - | orientation | New in 2.0. Controls horizontal/vertical layout |
| selection-type | selection-type | Values changed: \`none\` → \`default\`, \`single\` and \`multiple\` unchanged |
| size | - | Removed. Control size on individual buttons |
#### Event Mapping
| 1.0 Event | 2.0 Event | Notes |
|------------------------|------------------------|------------------------------------------------------------|
| buttonGroupClick | buttonGroupClick | Event detail changed: now includes \`{ button, isSelected }\` |
| buttonSelectionChange | buttonSelectionChange | Returns array of selected buttons |
`,
},
},
controls: { disable: true },
canvas: { disable: true },
},
render: () => html `<div></div>`,
};