@postnord/web-components
Version:
PostNord Web Components
366 lines (365 loc) • 13 kB
JavaScript
/*!
* Built with Stencil
* By PostNord.
*/
import { createDocumentation, createComponent } from "../../../globals/documentation/story";
import docs from "./pn-multiselect-docs.json";
import { company, small_business } from "pn-design-assets/pn-assets/icons";
const { argTypes, textContent } = createDocumentation(docs);
/**
* The `pn-multiselect` allows you to display a multiselect list with a ton of additional features.
* Only need a simple select component? Use the [pn-select](./?path=/docs/components-input-select--docs) instead.
*
* Under the hood it is actually a fieldset with nested checkbox inputs, but styled to look like a dropdown select.
*
* <pn-toast heading="New method of assigning the options" text="This is the first component to use JS instead of nested components to build the options.">
* <pn-button small href="./?path=/docs/components-input-multiselect-implementation--docs"><span>Learn how to assign the options</span></pn-button>
* </pn-toast>
*
* This component deal with most accessibility concerns on its own.
* As long as you provide a label for the component and a label+value for all its nested options, you have nothing to worry about.
*/
const meta = {
title: 'Components/Input/Multiselect',
tags: ['input', 'select'],
parameters: {
layout: 'padded',
actions: {
handles: ['toggleSelect', 'selectedOption', 'allOptions', 'selectedAllOptions', 'searchInput'],
},
},
args: {
label: 'Select customers',
helpertext: '',
placeholder: '',
name: '',
selectId: '',
selectName: '',
icon: '',
language: '',
selectAll: false,
allValue: '',
search: false,
searchQuery: '',
itemCount: null,
maxHeight: '',
top: false,
bottom: false,
invalid: false,
error: '',
required: false,
disabled: false,
},
argTypes,
};
meta.argTypes.allValue.if = { arg: 'selectAll', truthy: true };
meta.argTypes.searchQuery.if = { arg: 'search', truthy: true };
meta.argTypes.itemCount.if = { arg: 'search', truthy: true };
export default meta;
const data = [
{
label: 'Webhallen',
helpertext: '123332121-357',
value: 'webhallen',
checked: false,
},
{
label: 'IKEA',
value: 'ikea',
helpertext: '236820336-166',
checked: false,
group: [
{
label: 'IKEA Communications',
helpertext: '123123123-123',
value: 'ikea-com',
checked: false,
},
{
label: 'IKEA Transport',
helpertext: '053425134-789',
value: 'ikea-trans',
checked: false,
},
{
label: 'IKEA Internal',
helpertext: '722473472-345',
value: 'ikea-int',
checked: false,
},
],
},
{
label: 'Willys',
helpertext: '357357357-357',
value: 'willys',
checked: false,
},
{
label: 'NetOnNet',
helpertext: '483213141-974',
value: 'netonnet',
checked: false,
},
{
label: 'Biltema',
value: 'biltema',
checked: false,
helpertext: '964846745-517',
group: [
{
label: 'Halmstad',
helpertext: '631531667-123',
value: 'biltema-hal',
checked: false,
},
{
label: 'Malmö',
helpertext: '789789789-789',
value: 'biltema-mal',
checked: false,
},
{
label: 'Göteborg',
helpertext: '3453455345-345',
value: 'biltema-got',
checked: false,
},
],
},
{
label: 'Handelsbanken',
helpertext: '233946734-406',
value: 'handelsbanken',
checked: false,
},
];
function renderMultiselect(args, nested = false) {
const select = createComponent('pn-multiselect', args);
const options = [...data];
select.options = options
.map(({ label, helpertext, value, checked, group }) => {
if (nested)
return { label, helpertext, value, checked, group };
return { label, helpertext, value, checked };
})
.map(item => {
if (!args.helpertext)
delete item.helpertext;
if (args.icon) {
item.icon = company;
item?.group?.length && (item.group = item.group.map(opt => ({ ...opt, icon: small_business })));
}
return item;
});
const container = document.createElement('div');
container.style.width = '20em';
container.style.paddingBottom = '10em';
container.style.margin = '0 auto';
container.style.display = 'flex';
container.style.flexDirection = 'column';
container.appendChild(select);
return container;
}
export const PnMultiselect = {
name: 'pn-multiselect',
parameters: {
docs: {
description: {
story: textContent,
},
},
},
render: args => renderMultiselect(args),
};
/**
* This is the first component we have created that use an array of objects as a prop instead of nested components.
* An important note is that you cannot assign the array in regular HTML, it must be done via javascript.
**/
export const PnMultiselectOptions = {
name: 'pn-multiselect (options)',
render: PnMultiselect.render,
};
/**
* You can use any SVG content as an icon in the `pn-multiselect`.
* If you select an icon here in Storybook, we add the icon `company` and `small_business` to the options just to showcase the feature.
*
* Every individual option can have an icon, [read more about how to do that here](./?path=/docs/components-input-multiselect-implementation--docs).
*/
export const PnMultiselectIcon = {
name: 'pn-multiselect (icon)',
render: PnMultiselect.render,
args: {
icon: 'funnel',
},
};
/**
* You can use a `helpertext` for the select itself, but also for each option.
* If you enter a helpertext here in Storybook, we add a helpertext to the options just to showcase the feature.
*/
export const PnMultiselectHelpertext = {
name: 'pn-multiselect (helpertext)',
render: PnMultiselect.render,
args: {
helpertext: 'Select your favorite stores.',
},
};
/** Allow the user to select all options at once. */
export const PnMultiselectSelectAll = {
name: 'pn-multiselect (select all)',
render: PnMultiselect.render,
args: {
selectAll: true,
},
};
/** The `pn-multiselect` has a search field prop, allowing you to search among the options. */
export const PnMultiselectSearch = {
name: 'pn-multiselect (search)',
render: PnMultiselect.render,
args: {
search: true,
},
};
/** You can combine the `search` and `select-all` option at the same time. */
export const PnMultiselectSearchSelectAll = {
name: 'pn-multiselect (search + select all)',
render: PnMultiselect.render,
args: {
search: true,
selectAll: true,
},
};
/**
* You can trigger the invalid state with either the `invalid` or `error` prop.
*/
export const PnMultiselectSelectInvalid = {
name: 'pn-multiselect (invalid/error)',
render: PnMultiselect.render,
args: {
error: 'Invalid selection.',
},
};
/**
* You may notice that the select jump alot when making selections.
* Like all of our components, we build the components to use up the space it needs.
* To prevent wide or jumping in content width, set a CSS width, either on the component or parent element.
*
* This example presents better if you view the story page in the left meny.
*/
export const PnMultiselectForm = {
name: 'pn-multiselect (form)',
parameters: {
layout: 'padded',
},
render: (args, context) => {
const select = PnMultiselect.render(args, context)
.firstElementChild;
select.style.width = '20em';
const input = createComponent('pn-input', { label: 'Enter your name' });
const container = document.createElement('div');
container.className = 'sb-button-row';
container.style.flexWrap = 'nowrap';
container.style.margin = '0 auto';
container.style.maxWidth = '30em';
container.style.margin = '0 auto';
container.appendChild(input);
container.appendChild(select);
return container;
},
args: {
search: true,
selectAll: true,
icon: 'funnel',
helpertext: 'Filter the view based on the companies selected',
},
};
/**
* You can create groups with nested options. You can use the same properties on the nested options.
* Using this feature, the top level option will now act as a toggle for all of its nested options.
*
* If you use the `search` feature at the same time and you find 2 out of 4 options inside a nested group.
* Using the "Select all found options" button will toggle the nested options and not the parent option.
*
* If you find all of the nested options with the search, the top level will be selected.
*/
export const PnMultiselectGroups = {
name: 'pn-multiselect (group)',
render: args => renderMultiselect(args, true),
args: {
selectAll: true,
icon: 'funnel',
helpertext: 'Filter the view based on the companies selected',
},
};
/**
* If you do not provide any options, the `pn-multiselect` will display a "No options available" text when expanding the list.
* The component should only be used if you actually have any options to display.
*
* We have this fallback incase you conditionally render options and have edge cases where there are no options available,
* but you still wish to display the multiselect.
*/
export const PnMultiselectNoOptions = {
name: 'pn-multiselect (handle no options)',
render: args => createComponent('pn-multiselect', args),
};
/**
* If you use the multiselect in a modal or another type of overflown container, the default behaviour of the multiselect may cause overflow issues.
* This is because the component acts and calcultates the distance, offsets, etc... based on the viewport. Not its container.
*
* <pn-toast heading="Overflow edge cases" text="These examples are a last resort if the default behaviour is not working as intended." appearance="warning"></pn-toast>
*
* You can build the solution to this on your own or make use of the props: `top`, `bottom` and `max-height`.
* This way, you can open it in which direction you want and limit its height.
*
* In this example below, we force the dropdown to open upwards with the props `top="true"` and `max-height="12em"`.
* If we did not, the dropdown would most likely open downwards and take up more space than the modal allows, causing overflow.
*
* Some edge cases may need you to dynamically set the height and or direction.
* Use the `toggleSelect` event to listen when the multiselect is opening/closing and set the top/bottom and max-width prop for the component.
*/
export const PnMultiselectModal = {
name: 'pn-multiselect (deal with overflow)',
parameters: {
layout: 'none',
docs: {
story: {
iframeHeight: '30em',
iframeWidth: '100%',
inline: false,
},
},
},
render: (args, context) => {
const select = PnMultiselect.render(args, context)
.firstElementChild;
select.style.margin = '1em 0 0';
select.style.width = '100%';
const container = document.createElement('div');
const modal = document.createElement('pn-modal');
modal.open = true;
modal.innerHTML += `
<h1 style="margin-bottom:1em">pn-modal</h1>
<p style="margin-bottom:1em">
While there is no built in way to detect if the pn-multiselect is inside of an overflown container.
However, you can build the solution on your own or make use of the props: top, bottom and max-height.
</p>
<p>Use the toggleSelect event to listen when the select is opening/closing and toggle the overflow of the element.</p>
`;
modal.appendChild(select);
container.appendChild(modal);
const btn = createComponent('pn-button', { label: 'Open modal' });
btn.addEventListener('click', () => modal.setAttribute('open', 'true'));
container.appendChild(btn);
container.style.padding = '2em';
return container;
},
args: {
search: true,
selectAll: true,
top: true,
maxHeight: '12em',
icon: 'funnel',
helpertext: 'Filter the view based on the companies selected',
},
};
//# sourceMappingURL=pn-multiselect.stories.js.map