UNPKG

@postnord/web-components

Version:

PostNord Web Components

366 lines (365 loc) 13 kB
/*! * 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