UNPKG

@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

415 lines (403 loc) 12.2 kB
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/Tabs', component: 'modus-wc-tabs', args: { size: 'md', tabs: [ { label: 'Tab 1' }, { label: 'Tab 2' }, { label: 'Tab 3', disabled: true }, { icon: 'home' }, ], 'tab-style': 'bordered', }, argTypes: { tabs: { description: 'Array of tab objects defining the tabs to display', table: { type: { detail: ` Interface: ITab Properties: - customClass (string, optional): Custom CSS class for the inner button - disabled (boolean, optional): Whether the tab is disabled - icon (string, optional): A Modus Icon name to display - iconPosition ('left' | 'right', optional): The position of the icon - label (string, optional): The content to display in the tab - slotName (string, optional): The slot name to use for custom tab header content `, }, }, }, 'tab-style': { control: { type: 'select' }, options: ['boxed', 'bordered', 'lifted', 'none'], }, size: { control: { type: 'select' }, options: ['xs', 'sm', 'md', 'lg'], }, }, decorators: [ withActions, (story, context) => { var _a; // Normalize tabs before rendering the story const { tabs } = (_a = context.args) !== null && _a !== void 0 ? _a : {}; if (Array.isArray(tabs)) { context.args.tabs = normalizeTabs(tabs); } return story(); }, ], parameters: { actions: { handles: ['tabChange'], }, }, }; export default meta; // Shared normalization function used by both decorator and source code const normalizeTabs = (tabs) => { return tabs.map((tab) => { const normalized = Object.assign({}, tab); // Only keep disabled when it is explicitly true if (normalized.disabled !== true) { delete normalized.disabled; } return normalized; }); }; const getSourceCode = (tabs) => { const normalizedTabs = normalizeTabs(tabs); return ` <script> const tabs = ${JSON.stringify(normalizedTabs, null, 2)}; const tabElement = document.querySelector('modus-wc-tabs'); tabElement.tabs = tabs; </script>`; }; const Template = { parameters: { docs: { source: { transform: (_src, { args }) => `<modus-wc-tabs aria-label="Tab group" tab-style="${ifDefined(args['tab-style'])}" size="${ifDefined(args.size)}"> </modus-wc-tabs>${getSourceCode(args.tabs)}`, }, }, }, render: (args) => { // prettier-ignore return html ` <modus-wc-tabs active-tab-index="${ifDefined(args.activeTabIndex)}" aria-label="Tab group" tab-style="${ifDefined(args['tab-style'])}" .tabs=${args.tabs} size="${ifDefined(args.size)}" > </modus-wc-tabs> `; }, }; export const Default = Object.assign({}, Template); export const CustomContent = { args: { tabs: [ { icon: 'home', iconPosition: 'left', label: 'Home', slotName: 'home-tab-content', }, { icon: 'clipboard', iconPosition: 'right', label: 'Tasks', }, { slotName: 'actions-tab-content', }, { slotName: 'notifications-tab-content', }, ], }, parameters: { docs: { description: { story: 'Tabs now include slots, offering a flexible approach for users to add relevant components within the tab for more complex use cases.', }, source: { transform: (_src, { args }) => `<style> .red-icon { color: red; } /* Style for disabled badge and icon components */ modus-wc-badge[disabled="true"], modus-wc-icon[disabled="true"] , button[disabled] modus-wc-badge, button[disabled] modus-wc-icon { opacity: 0.3; pointer-events: none; } </style> <modus-wc-tabs size="md" tab-style="bordered" aria-label="Custom tab group" > <span slot="home-tab-content" style="display: inline-flex; align-items: center;padding-top: 6px" > <modus-wc-badge color="warning" size="md" variant="filled" > <modus-wc-icon decorative="" name="home" size="xs"></modus-wc-icon> Home </modus-wc-badge> </span> <span slot="actions-tab-content" style="display: inline-flex; align-items: center; gap: 8px;" > Actions <modus-wc-icon name="warning" variant="solid" size="md" custom-class="red-icon" ></modus-wc-icon> </span> <span slot="notifications-tab-content" style="display: inline-flex; align-items: center; gap: 8px;" > Notifications <modus-wc-badge color="primary" size="md" variant="counter" >5</modus-wc-badge > </span> </modus-wc-tabs>${getSourceCode(args.tabs)}`, }, }, }, // prettier-ignore render: (args) => { return html ` <style> .red-icon { color: red; } /* Style for disabled badge and icon components */ modus-wc-badge[disabled="true"], modus-wc-icon[disabled="true"] , button[disabled] modus-wc-badge, button[disabled] modus-wc-icon { opacity: 0.3; pointer-events: none; } </style> <modus-wc-tabs .tabs=${args.tabs} size="${ifDefined(args.size)}" tab-style="${ifDefined(args['tab-style'])}" active-tab-index="${ifDefined(args.activeTabIndex)}" aria-label="Custom tab group" > <span slot="home-tab-content" style="display: inline-flex; align-items: center;padding-top: 6px" > <modus-wc-badge color="warning" size="${ifDefined(args.size)}" variant="filled" > <modus-wc-icon decorative="" name="home" size="xs"></modus-wc-icon> Home </modus-wc-badge> </span> <span slot="actions-tab-content" style="display: inline-flex; align-items: center; gap: 8px;" > Actions <modus-wc-icon name="warning" variant="solid" size="${ifDefined(args.size)}" custom-class="red-icon" ></modus-wc-icon> </span> <span slot="notifications-tab-content" style="display: inline-flex; align-items: center; gap: 8px;" > Notifications <modus-wc-badge color="primary" size="${ifDefined(args.size)}" variant="counter" >5</modus-wc-badge > </span> </modus-wc-tabs> `; }, }; export const ActiveAndDisabled = Object.assign(Object.assign({}, Template), { args: { activeTabIndex: 1, tabs: [ { label: 'Normal' }, { label: 'Active' }, { label: 'Disabled', disabled: true }, ], } }); export const Icons = Object.assign(Object.assign({}, Template), { args: { tabs: [ { icon: 'home' }, { icon: 'settings', iconPosition: 'left', label: 'Settings' }, { icon: 'alert', iconPosition: 'right', label: 'Alerts', }, ], } }); export const TabsWithPanel = { parameters: { docs: { source: { transform: (_src, { args }) => `<modus-wc-tabs aria-label="Tab group" tab-style="bordered" size="md" > <p slot="tab-0"> Modus (noun) : a mode of procedure : a way of doing something </p> <p slot="tab-1"> input (noun) : information fed into a data processing system or computer </p> <p slot="tab-2"> secret (noun) : kept from knowledge or view : hidden </p> <p slot="tab-3"> snapshot (noun) : an impression or view of something brief or transitory </p> </modus-wc-tabs>${getSourceCode(args.tabs)}`, }, }, }, render: (args) => { // prettier-ignore return html ` <modus-wc-tabs active-tab-index="${ifDefined(args.activeTabIndex)}" aria-label="Tab group" custom-class="${ifDefined(args['custom-class'])}" ?img-src="${args['img-src']}" tab-style="${ifDefined(args['tab-style'])}" .tabs=${args.tabs} size="${ifDefined(args.size)}" > <p slot="tab-0"> Modus (noun) : a mode of procedure : a way of doing something </p> <p slot="tab-1"> input (noun) : information fed into a data processing system or computer </p> <p slot="tab-2"> secret (noun) : kept from knowledge or view : hidden </p> <p slot="tab-3"> snapshot (noun) : an impression or view of something brief or transitory </p> </modus-wc-tabs> `; }, }; export const ShadowDomParent = { render: (args) => { if (!customElements.get('tabs-shadow-host')) { const TabsShadowHost = createShadowHostClass({ componentTag: 'modus-wc-tabs', propsMapper: (v, el) => { var _a, _b, _c; const tabsEl = el; tabsEl.activeTabIndex = (_a = v.activeTabIndex) !== null && _a !== void 0 ? _a : 0; tabsEl.customClass = v['custom-class'] || ''; tabsEl.size = (_b = v.size) !== null && _b !== void 0 ? _b : 'md'; tabsEl.tabs = v.tabs; tabsEl.tabStyle = (_c = v['tab-style']) !== null && _c !== void 0 ? _c : 'bordered'; }, }); customElements.define('tabs-shadow-host', TabsShadowHost); } return html `<tabs-shadow-host .props=${Object.assign({}, args)}></tabs-shadow-host>`; }, }; export const Migration = { parameters: { docs: { description: { story: ` #### Breaking Changes - In 2.0 tabs use the \`ITab\` interface, see details of interface changes below. - Size values have changed from verbose names (\`small\`, \`medium\`) to abbreviations (\`xs\`, \`sm\`, \`md\`, \`lg\`). - The \`tabChange\` event now emits an object with both previous and new tab indices, rather than just the tab ID. #### Prop Mapping | 1.0 Prop | 2.0 Prop | Notes | |--------------|--------------------|----------------------------------------------------------------| | aria-label | aria-label | | | full-width | | Not carried over, use CSS instead | | size | size | \`small\` → \`sm\`, \`medium\` → \`md\` | | tabs | tabs | Tab object structure has changed. See Interface changes below. | #### Event Mapping | 1.0 Event | 2.0 Event | Notes | |-------------|-----------|-------------------------------------------------------| | tabChange | tabChange | Now emits \`{ previousTab: number; newTab: number }\` | #### Interfaces ##### 1.0 \`\`\`typescript export interface Tab { active?: boolean; iconOnly?: string; id: string; label?: string; leftIcon?: string; rightIcon?: string; } \`\`\` ##### 2.0 \`\`\`typescript export interface ITab { customClass?: string; disabled?: boolean; icon?: string; iconPosition?: 'left' | 'right'; label?: string; } \`\`\` `, }, }, controls: { disable: true }, canvas: { disable: true }, }, render: () => html `<div></div>`, };