diff --git a/mcp/versions/1.5.0/component-docs/_all_components.json b/mcp/versions/1.5.0/component-docs/_all_components.json new file mode 100644 index 000000000..7ae415d9c --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/_all_components.json @@ -0,0 +1,57 @@ +{ + "total": 51, + "components": [ + "modus-wc-accordion", + "modus-wc-alert", + "modus-wc-app-menu", + "modus-wc-autocomplete", + "modus-wc-avatar", + "modus-wc-badge", + "modus-wc-breadcrumbs", + "modus-wc-button", + "modus-wc-button-group", + "modus-wc-card", + "modus-wc-checkbox", + "modus-wc-chip", + "modus-wc-collapse", + "modus-wc-date", + "modus-wc-divider", + "modus-wc-dropdown-menu", + "modus-wc-file-dropzone", + "modus-wc-handle", + "modus-wc-icon", + "modus-wc-input-feedback", + "modus-wc-input-label", + "modus-wc-loader", + "modus-wc-logo", + "modus-wc-menu", + "modus-wc-menu-item", + "modus-wc-modal", + "modus-wc-navbar", + "modus-wc-number-input", + "modus-wc-pagination", + "modus-wc-panel", + "modus-wc-profile-menu", + "modus-wc-progress", + "modus-wc-radio", + "modus-wc-rating", + "modus-wc-select", + "modus-wc-side-navigation", + "modus-wc-skeleton", + "modus-wc-slider", + "modus-wc-stepper", + "modus-wc-switch", + "modus-wc-table", + "modus-wc-tabs", + "modus-wc-text-input", + "modus-wc-textarea", + "modus-wc-theme-switcher", + "modus-wc-time-input", + "modus-wc-toast", + "modus-wc-toolbar", + "modus-wc-tooltip", + "modus-wc-typography", + "modus-wc-utility-panel" + ], + "last_updated": "1778679955.444" +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-accordion.json b/mcp/versions/1.5.0/component-docs/modus-wc-accordion.json new file mode 100644 index 000000000..b4f05911b --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-accordion.json @@ -0,0 +1,48 @@ +{ + "description": "A customizable accordion component used for showing and hiding related groups of content. The component supports a `` called 'content' for injecting `` elements. See [Collapse](/docs/components-collapse--docs) docs for additional info.", + "properties": [ + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the inner div.", + "default": "''", + "mutable": false, + "end_line": 29 + } + ], + "events": [ + { + "name": "expandedChange", + "detail": "{ expanded: boolean; index: number; }", + "description": "When a collapse expanded state is changed, this event outputs the relevant index and state", + "end_line": 35 + } + ], + "methods": [], + "slots": [ + { + "name": "default", + "description": "Slot for default content" + } + ], + "examples": { + "basic": "
\n \n \n
Collapse content
\n
\n \n
Collapse content
\n
\n \n
Collapse content
\n
\n
\n
\n", + "variations": [], + "args": {}, + "argTypes": {}, + "usage": [], + "events": [ + "expandedChange" + ] + }, + "tag": "modus-wc-accordion", + "storyExample": { + "template": "
\n \n \n
Collapse content
\n
\n \n
Collapse content
\n
\n \n
Collapse content
\n
\n
\n
\n", + "args": {}, + "argTypes": {}, + "events": [ + "expandedChange" + ], + "fullContent": "import { withActions } from '@storybook/addon-actions/decorator';\nimport { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\nimport { ICollapseOptions } from '../modus-wc-collapse/modus-wc-collapse';\n\ninterface AccordionArgs {\n 'custom-class'?: string;\n}\n\nconst collapseOptions: ICollapseOptions[] = [\n {\n description: 'Item one description',\n icon: 'alert',\n iconAriaLabel: 'Alert',\n title: 'Item One',\n },\n {\n description: 'Item two description',\n icon: 'alert',\n iconAriaLabel: 'Alert',\n title: 'Item Two',\n },\n {\n description: 'Item three description',\n icon: 'alert',\n iconAriaLabel: 'Alert',\n title: 'Item Three',\n },\n];\n\nconst meta: Meta = {\n title: 'Components/Accordion',\n component: 'modus-wc-accordion',\n decorators: [withActions],\n parameters: {\n actions: {\n handles: ['expandedChange'],\n },\n layout: {\n padded: true,\n },\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nconst Template: Story = {\n render: (args) => {\n // prettier-ignore\n return html`\n
\n \n \n
Collapse content
\n
\n \n
Collapse content
\n
\n \n
Collapse content
\n
\n
\n
\n\n `;\n },\n};\n\nexport const Default: Story = { ...Template };\n\nexport const ShadowDomParent: Story = {\n render: () => {\n // Create a unique shadow host for accordion component\n if (!customElements.get('accordion-shadow-host')) {\n const AccordionShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-accordion',\n propsMapper: (v: AccordionArgs, el: HTMLElement) => {\n const accordionEl = el as unknown as {\n customClass: string;\n };\n accordionEl.customClass = v['custom-class'] || '';\n\n // Create and append collapse elements (no whitespace between to avoid gaps)\n el.innerHTML = '';\n collapseOptions.forEach((options) => {\n const collapse = document.createElement('modus-wc-collapse');\n (collapse as unknown as { options: ICollapseOptions }).options =\n options;\n const contentDiv = document.createElement('div');\n contentDiv.setAttribute('slot', 'content');\n contentDiv.textContent = 'Collapse content';\n collapse.appendChild(contentDiv);\n el.appendChild(collapse);\n });\n },\n });\n customElements.define('accordion-shadow-host', AccordionShadowHost);\n }\n\n return html``;\n },\n};\nexport const Migration: Story = {\n parameters: {\n docs: {\n description: {\n story: `\n#### Breaking Changes\n\n - In 1.0 the accordion was composed of child accordion-item components. In 2.0 accordion children are collapse\n components.\n - The new accordion supports \\`header\\` and \\`content\\` slots to provide maximum flexibility.\n - Size values have changed from (\\`condensed\\`, \\`standard\\`) in 1.0 accordion-item to abbreviations (\\`xs\\`, \\`sm\\`, \\`md\\`, \\`lg\\`) in 2.0 collapse.\n\n#### Prop Mapping\n\n##### accordion\n\n| 1.0 Prop | 2.0 Prop | Notes |\n|--------------------|--------------------|------------------|\n| aria-label | aria-label | |\n\n##### accordion-item → collapse\n\n| 1.0 Prop | 2.0 Prop | Notes |\n|--------------------|---------------------|------------------|\n| aria-label | aria-label | |\n| disabled | | Not carried over |\n| expand-button-type | | Not carried over |\n| expanded | expanded | |\n| header-text | options.title | |\n| icon | options.icon | |\n| supporting-label | options.description | |\n| size | options.size | |\n\n#### Event Mapping\n\n##### accordion-item → accordion\n\nThe new accordion and collapse have their own events. We recommend using the\naccordion events to migrate.\n\n| 1.0 Event | 2.0 Event | Notes |\n|-----------|----------------|------------------|\n| closed | expandedChange | |\n| opened | expandedChange | |\n `,\n },\n },\n // To hide the actual story rendering and only show docs:\n controls: { disable: true },\n canvas: { disable: true },\n },\n // Simple render function or leave it empty\n render: () => html`
`,\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-alert.json b/mcp/versions/1.5.0/component-docs/modus-wc-alert.json new file mode 100644 index 000000000..b50f058e9 --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-alert.json @@ -0,0 +1,112 @@ +{ + "description": "A customizable alert component used to inform the user about important events. The component supports `` elements for injecting custom content and buttons.", + "properties": [ + { + "name": "alertDescription", + "type": "string", + "description": "The description of the alert.", + "default": null, + "mutable": false, + "end_line": 33 + }, + { + "name": "alertTitle", + "type": "string", + "description": "The title of the alert.", + "default": null, + "mutable": false, + "end_line": 36 + }, + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the outer div element.", + "default": "''", + "mutable": false, + "end_line": 39 + }, + { + "name": "delay", + "type": "number", + "description": "Time taken to dismiss the alert in milliseconds", + "default": null, + "mutable": false, + "end_line": 42 + }, + { + "name": "dismissible", + "type": "boolean", + "description": "Whether the alert has a dismiss button", + "default": "false", + "mutable": false, + "end_line": 45 + }, + { + "name": "icon", + "type": "string", + "description": "The Modus icon to render.", + "default": null, + "mutable": false, + "end_line": 48 + }, + { + "name": "variant", + "type": "'error' | 'info' | 'success' | 'warning'", + "description": "The variant of the alert.", + "default": "'info'", + "mutable": false, + "end_line": 51 + } + ], + "events": [ + { + "name": "dismissClick", + "detail": "void", + "description": "An event that fires when the alert is dismissed", + "end_line": 128 + } + ], + "methods": [], + "slots": [ + { + "name": "content", + "description": "Slot for content content" + }, + { + "name": "button", + "description": "Slot for button content" + } + ], + "examples": { + "basic": "\n", + "variations": [], + "args": { + "alert-description": "'You have 3 new messages.'", + "alert-title": "'New message!'", + "dismissible": "false", + "role": "'status'", + "variant": "'info'" + }, + "argTypes": {}, + "usage": [], + "events": [ + "dismissClick" + ] + }, + "tag": "modus-wc-alert", + "storyExample": { + "template": "\n", + "args": { + "alert-description": "'You have 3 new messages.'", + "alert-title": "'New message!'", + "dismissible": "false", + "role": "'status'", + "variant": "'info'" + }, + "argTypes": {}, + "events": [ + "dismissClick" + ], + "fullContent": "import { withActions } from '@storybook/addon-actions/decorator';\nimport { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\n\ninterface AlertArgs {\n 'alert-description'?: string;\n 'alert-title': string;\n 'custom-class'?: string;\n delay?: number;\n dismissible?: boolean;\n dismissClick?: () => void;\n icon?: string;\n variant: 'error' | 'info' | 'success' | 'warning';\n role: 'alert' | 'log' | 'marquee' | 'status' | 'timer';\n}\n\nconst meta: Meta = {\n title: 'Components/Alert',\n component: 'modus-wc-alert',\n args: {\n 'alert-description': 'You have 3 new messages.',\n 'alert-title': 'New message!',\n dismissible: false,\n role: 'status',\n variant: 'info',\n },\n argTypes: {\n role: {\n control: { type: 'select' },\n options: ['', 'alert', 'log', 'marquee', 'status', 'timer'],\n },\n variant: {\n control: { type: 'select' },\n options: ['', 'error', 'info', 'success', 'warning'],\n },\n },\n decorators: [withActions],\n parameters: {\n layout: 'padded',\n actions: {\n handles: ['dismissClick'],\n },\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nconst Template: Story = {\n render: (args) => {\n // prettier-ignore\n return html`\n\n\n `;\n },\n};\n\nexport const Default: Story = { ...Template };\n\nexport const CustomButton: Story = {\n render: (args) => {\n // prettier-ignore\n return html`\n\n View Messages\n\n `;\n },\n};\n\nexport const WithCustomContent: Story = {\n render: (args) => {\n // prettier-ignore\n return html`\n\n
New custom message!
\n\n `;\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n if (!customElements.get('alert-shadow-host')) {\n const AlertShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-alert',\n propsMapper: (v: AlertArgs, el: HTMLElement) => {\n const alertEl = el as unknown as {\n alertDescription: string;\n alertTitle: string;\n customClass: string;\n delay: number;\n dismissible: boolean;\n icon: string;\n variant: string;\n };\n alertEl.alertDescription = v['alert-description'] ?? '';\n alertEl.alertTitle = v['alert-title'];\n alertEl.customClass = v['custom-class'] || '';\n alertEl.delay = v.delay ?? 0;\n alertEl.dismissible = Boolean(v.dismissible);\n alertEl.icon = v.icon ?? '';\n alertEl.variant = v.variant;\n },\n });\n customElements.define('alert-shadow-host', AlertShadowHost);\n }\n\n return html``;\n },\n};\nexport const Migration: Story = {\n parameters: {\n docs: {\n description: {\n story: `\n#### Breaking Changes\n\n - The 2.0 component can render a custom HTML title in the \\`content\\` slot.\n - The 1.0 component rendered a button, while the 2.0 component can render a custom HTML button in the \\`button\\` slot.\n\n#### Prop Mapping\n\n| 1.0 Prop | 2.0 Prop | Notes |\n|-------------------|-------------|---------------------------------------|\n| aria-label | aria-label | |\n| button-aria-label | | Not carried over, use \\`button\\` slot |\n| button-text | | Not carried over, use \\`button\\` slot |\n| dismissible | dismissible | |\n| message | alert-title | |\n| type | variant | |\n\n#### Event Mapping\n\n| 1.0 Event | 2.0 Event | Notes |\n|--------------|--------------|---------------------------------------|\n| actionClick | | Not carried over, use \\`button\\` slot |\n| dismissClick | dismissClick | |\n `,\n },\n },\n // To hide the actual story rendering and only show docs:\n controls: { disable: true },\n canvas: { disable: true },\n },\n // Simple render function or leave it empty\n render: () => html`
`,\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-app-menu.json b/mcp/versions/1.5.0/component-docs/modus-wc-app-menu.json new file mode 100644 index 000000000..26f24b90a --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-app-menu.json @@ -0,0 +1,86 @@ +{ + "description": "", + "properties": [ + { + "name": "customClass", + "type": "string", + "description": "custom class to apply to the menu", + "default": "''", + "mutable": false, + "end_line": 39 + }, + { + "name": "layout", + "type": "'list' | 'grid'", + "description": "The layout of the menu.", + "default": "'list'", + "mutable": true, + "end_line": 42 + }, + { + "name": "apps", + "type": "IAppMenuItem[]", + "description": "The apps to display in the menu.", + "default": "[]", + "mutable": true, + "end_line": 45 + } + ], + "events": [ + { + "name": "layoutChange", + "detail": "{ layout: 'list' | 'grid'; }", + "description": "Emit event when the layout changes", + "end_line": 50 + }, + { + "name": "itemsOrderChange", + "detail": "IAppMenuItem[]", + "description": "Emitted when reordering is confirmed via \"Done\" and the order differs from when edit started", + "end_line": 53 + }, + { + "name": "itemClick", + "detail": "{ appName: AppName }", + "description": "Emitted when an item is clicked", + "end_line": 56 + } + ], + "methods": [], + "slots": [ + { + "name": "header-end-content", + "description": "Slot for header-end-content content" + } + ], + "examples": { + "basic": "
\n \n
", + "variations": [], + "args": { + "layout": "'list'", + "apps": "defaultApps" + }, + "argTypes": {}, + "usage": [], + "events": [ + "layoutChange", + "itemsOrderChange", + "itemClick" + ] + }, + "tag": "modus-wc-app-menu", + "storyExample": { + "template": "
\n \n
", + "args": { + "layout": "'list'", + "apps": "defaultApps" + }, + "argTypes": {}, + "events": [ + "layoutChange", + "itemsOrderChange", + "itemClick" + ], + "fullContent": "import { withActions } from '@storybook/addon-actions/decorator';\nimport { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { IAppMenuItem } from './modus-wc-app-menu';\n\ninterface AppMenuArgs {\n 'custom-class'?: string;\n draggedItemPos?: unknown;\n grabbedItemPos?: unknown;\n isEditMode?: unknown;\n layout?: 'list' | 'grid';\n apps?: IAppMenuItem[];\n}\n\nconst defaultApps: IAppMenuItem[] = [\n { appName: 'trimble' },\n { appName: 'siteworks' },\n { appName: 'earthworks' },\n { appName: 'worksmanager' },\n { appName: 'connect' },\n { appName: 'unity' },\n { appName: 'trade_service_live' },\n { appName: 'livecount' },\n { appName: 'supplier_xchange' },\n { appName: 'projectsight' },\n { appName: 'app_xchange' },\n { appName: 'sketchup' },\n { appName: 'pay' },\n { appName: 'copilot' },\n { appName: 'stabicad' },\n];\n\nconst meta: Meta = {\n title: 'Components/App Menu',\n component: 'modus-wc-app-menu',\n args: {\n layout: 'list',\n apps: defaultApps,\n },\n argTypes: {\n 'custom-class': {\n control: 'text',\n },\n layout: {\n control: { type: 'select' },\n options: ['list', 'grid'],\n },\n apps: {\n control: 'object',\n },\n draggedItemPos: {\n table: { disable: true },\n },\n grabbedItemPos: {\n table: { disable: true },\n },\n isEditMode: {\n table: { disable: true },\n },\n },\n decorators: [withActions],\n parameters: {\n actions: {\n handles: ['layoutChange', 'itemsOrderChange', 'itemClick'],\n },\n docs: {\n description: {\n component: `\nA customizable app menu component that displays application links in list or grid layout.\n\\nThe component uses the modus-wc-panel component for layout , supports dual viewing (List and Grid) modes and allows reordering via drag-and-drop and keyboard in edit mode.\n `,\n },\n },\n },\n};\nexport default meta;\n\ntype Story = StoryObj;\n\nconst getSourceCode = (args: AppMenuArgs) => {\n const appsCode = `const apps = ${JSON.stringify(args.apps, null, 2)};`;\n\n return `\n\n\n`;\n};\n\nconst Template: Story = {\n parameters: {\n docs: {\n source: {\n transform: (_src, { args }: { args: AppMenuArgs }) =>\n getSourceCode(args),\n },\n },\n },\n render: (args) => {\n // prettier-ignore\n return html`\n
\n \n
\n `;\n },\n};\n\nexport const Default: Story = {\n ...Template,\n parameters: {\n docs: {\n description: {\n story: 'App menu displayed in list layout.',\n },\n source: {\n transform: (_src, { args }: { args: AppMenuArgs }) =>\n getSourceCode(args),\n },\n },\n },\n};\n\nexport const GridLayout: Story = {\n ...Template,\n args: {\n layout: 'grid',\n },\n parameters: {\n docs: {\n description: {\n story: 'App menu in grid layout showing all app emblems.',\n },\n source: {\n transform: (_src, { args }: { args: AppMenuArgs }) =>\n getSourceCode(args),\n },\n },\n },\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-autocomplete.json b/mcp/versions/1.5.0/component-docs/modus-wc-autocomplete.json new file mode 100644 index 000000000..08ff47b9f --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-autocomplete.json @@ -0,0 +1,415 @@ +{ + "description": "A customizable autocomplete component used to create searchable text inputs. The component supports a `` for injecting custom content.", + "properties": [ + { + "name": "autoComplete", + "type": "AutocompleteTypes", + "description": "Hint for form autofill feature.", + "default": null, + "mutable": false, + "end_line": 74 + }, + { + "name": "bordered", + "type": "boolean", + "description": "Indicates that the autocomplete should have a border.", + "default": "true", + "mutable": false, + "end_line": 77 + }, + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to host element.", + "default": "''", + "mutable": false, + "end_line": 80 + }, + { + "name": "debounceMs", + "type": "number", + "description": "The debounce timeout in milliseconds. Set to 0 to disable debouncing.", + "default": "300", + "mutable": false, + "end_line": 86 + }, + { + "name": "disabled", + "type": "boolean", + "description": "Whether the form control is disabled.", + "default": "false", + "mutable": false, + "end_line": 89 + }, + { + "name": "feedback", + "type": "IInputFeedbackProp", + "description": "Feedback state for the input field.", + "default": null, + "mutable": false, + "end_line": 92 + }, + { + "name": "includeClear", + "type": "boolean", + "description": "Show the clear button within the input field.", + "default": "false", + "mutable": false, + "end_line": 95 + }, + { + "name": "includeSearch", + "type": "boolean", + "description": "Show the search icon within the input field.", + "default": "false", + "mutable": false, + "end_line": 98 + }, + { + "name": "inputId", + "type": "string", + "description": "The ID of the input element.", + "default": null, + "mutable": false, + "end_line": 101 + }, + { + "name": "inputTabIndex", + "type": "number", + "description": "Determine the control's relative ordering for sequential focus navigation (typically with the Tab key).", + "default": null, + "mutable": false, + "end_line": 104 + }, + { + "name": "items", + "type": "IAutocompleteItem[]", + "description": "The items to display in the menu. Creating a new array of items will ensure proper component re-render.", + "default": "[]", + "mutable": false, + "end_line": 110 + }, + { + "name": "label", + "type": "string", + "description": "The text to display within the label.", + "default": null, + "mutable": false, + "end_line": 113 + }, + { + "name": "leaveMenuOpen", + "type": "boolean", + "description": "Whether the menu should remain open after an item is selected.", + "default": "false", + "mutable": false, + "end_line": 116 + }, + { + "name": "minChars", + "type": "number", + "description": "The minimum number of characters required to render the menu.", + "default": "0", + "mutable": false, + "end_line": 119 + }, + { + "name": "multiSelect", + "type": "boolean", + "description": "Whether the input allows multiple items to be selected.", + "default": "false", + "mutable": false, + "end_line": 122 + }, + { + "name": "name", + "type": "string", + "description": "Name of the form control. Submitted with the form as part of a name/value pair.", + "default": null, + "mutable": false, + "end_line": 125 + }, + { + "name": "noResults", + "type": "IAutocompleteNoResults", + "description": "The content to display when no results are found.", + "default": "{ ariaLabel: 'No results found', label: 'No results found', subLabel: 'Check spelling or try a different keyword', }", + "mutable": false, + "end_line": 132 + }, + { + "name": "placeholder", + "type": "string", + "description": "Text that appears in the form control when it has no value set.", + "default": "''", + "mutable": false, + "end_line": 135 + }, + { + "name": "readOnly", + "type": "boolean", + "description": "Whether the value is editable.", + "default": "false", + "mutable": false, + "end_line": 138 + }, + { + "name": "required", + "type": "boolean", + "description": "A value is required for the form to be submittable.", + "default": "false", + "mutable": false, + "end_line": 141 + }, + { + "name": "showMenuOnFocus", + "type": "boolean", + "description": "Whether to show the menu whenever the input has focus, regardless of input value.", + "default": "false", + "mutable": false, + "end_line": 144 + }, + { + "name": "size", + "type": "ModusSize", + "description": "The size of the autocomplete (input and menu).", + "default": "'md'", + "mutable": false, + "end_line": 147 + }, + { + "name": "showSpinner", + "type": "boolean", + "description": "A spinner that appears when set to true", + "default": "false", + "mutable": false, + "end_line": 150 + }, + { + "name": "value", + "type": "string", + "description": "The value of the control.", + "default": "''", + "mutable": true, + "end_line": 153 + }, + { + "name": "maxChips", + "type": "number", + "description": "Maximum number of chips to display. When exceeded, shows expand/collapse button. Set to -1 to disable limit.", + "default": "-1", + "mutable": false, + "end_line": 156 + }, + { + "name": "customItemSelect", + "type": "(item: IAutocompleteItem)", + "description": "Custom item selection handler - if provided, overrides default selection logic", + "default": "> void", + "mutable": false, + "end_line": 159 + }, + { + "name": "customInputChange", + "type": "(value: string)", + "description": "Custom input change handler - if provided, overrides default search filtering", + "default": "> void", + "mutable": false, + "end_line": 162 + }, + { + "name": "customKeyDown", + "type": "(event: KeyboardEvent)", + "description": "Custom key down handler - if provided, overrides default keyboard navigation", + "default": "> void", + "mutable": false, + "end_line": 165 + }, + { + "name": "customBlur", + "type": "(event: FocusEvent)", + "description": "Custom blur handler - if provided, overrides default blur behavior", + "default": "> void", + "mutable": false, + "end_line": 168 + }, + { + "name": "minInputWidth", + "type": "number", + "description": "Minimum width for the text input in pixels. When chips would make input smaller, container height increases instead.", + "default": "10", + "mutable": false, + "end_line": 171 + } + ], + "events": [ + { + "name": "chipRemove", + "detail": "IAutocompleteItem", + "description": "Event emitted when a selected item chip is removed.", + "end_line": 174 + }, + { + "name": "chipsExpansionChange", + "detail": "{ expanded: boolean }", + "description": "Event emitted when chips expansion state changes.", + "end_line": 177 + }, + { + "name": "clearClick", + "detail": "void", + "description": "Event emitted when the clear button is clicked.", + "end_line": 180 + }, + { + "name": "inputBlur", + "detail": "FocusEvent", + "description": "Event emitted when the input loses focus.", + "end_line": 183 + }, + { + "name": "inputChange", + "detail": "Event", + "description": "Event emitted when the input value changes. This event is debounced based on the debounceMs prop.", + "end_line": 189 + }, + { + "name": "inputFocus", + "detail": "FocusEvent", + "description": "Event emitted when the input gains focus.", + "end_line": 192 + }, + { + "name": "itemSelect", + "detail": "IAutocompleteItem", + "description": "Event emitted when a menu item is selected.", + "end_line": 195 + } + ], + "methods": [ + { + "name": "selectItem", + "signature": "(item: IAutocompleteItem | null)", + "parameters": "item: IAutocompleteItem | null", + "returnType": "void", + "description": "Programmatically select an item", + "end_line": 687 + }, + { + "name": "openMenu", + "signature": "()", + "parameters": "", + "returnType": "void", + "description": "Programmatically open the menu", + "end_line": 710 + }, + { + "name": "closeMenu", + "signature": "()", + "parameters": "", + "returnType": "void", + "description": "Programmatically close the menu", + "end_line": 720 + }, + { + "name": "toggleMenu", + "signature": "()", + "parameters": "", + "returnType": "void", + "description": "Programmatically toggle the menu open/closed", + "end_line": 731 + }, + { + "name": "focusInput", + "signature": "()", + "parameters": "", + "returnType": "void", + "description": "Programmatically set focus to input", + "end_line": 745 + }, + { + "name": "clearInput", + "signature": "()", + "parameters": "", + "returnType": "void", + "description": "Clear the input value and reset items", + "end_line": 757 + } + ], + "slots": [ + { + "name": "menu-items", + "description": "Slot for menu-items content" + } + ], + "examples": { + "basic": "\n\n", + "variations": [], + "args": { + "auto-complete": "undefined", + "bordered": "true", + "debounce-ms": "300", + "disabled": "false", + "include-clear": "false", + "include-search": "false", + "items": "items", + "label": "'Label'", + "leave-menu-open": "false", + "max-chips": "4", + "min-chars": "0", + "min-input-width": "15", + "multi-select": "false", + "show-menu-on-focus": "false", + "show-spinner": "false", + "size": "'md'", + "value": "''" + }, + "argTypes": {}, + "usage": [], + "events": [ + "chipRemove", + "chipsExpansionChange", + "clearClick", + "inputBlur", + "inputChange", + "inputFocus", + "itemSelect", + "" + ] + }, + "tag": "modus-wc-autocomplete", + "storyExample": { + "template": "\n\n", + "args": { + "auto-complete": "undefined", + "bordered": "true", + "debounce-ms": "300", + "disabled": "false", + "include-clear": "false", + "include-search": "false", + "items": "items", + "label": "'Label'", + "leave-menu-open": "false", + "max-chips": "4", + "min-chars": "0", + "min-input-width": "15", + "multi-select": "false", + "show-menu-on-focus": "false", + "show-spinner": "false", + "size": "'md'", + "value": "''" + }, + "argTypes": {}, + "events": [ + "chipRemove", + "chipsExpansionChange", + "clearClick", + "inputBlur", + "inputChange", + "inputFocus", + "itemSelect", + "" + ], + "fullContent": "import { withActions } from '@storybook/addon-actions/decorator';\nimport { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\nimport {\n AutocompleteTypes,\n IAutocompleteItem,\n IAutocompleteNoResults,\n ModusSize,\n} from '../types';\n\n// Updated items array includes an optional \"focused\" property.\nconst items: IAutocompleteItem[] = [\n {\n label: 'Apple',\n value: 'apple',\n visibleInMenu: true,\n focused: false,\n disabled: false,\n checkbox: false,\n },\n {\n label: 'Banana',\n value: 'banana',\n visibleInMenu: true,\n focused: false,\n disabled: false,\n checkbox: false,\n },\n {\n label: 'Blueberry',\n value: 'blueberry',\n visibleInMenu: true,\n focused: false,\n checkbox: false,\n },\n {\n label: 'Cherry',\n value: 'cherry',\n visibleInMenu: true,\n focused: false,\n checkbox: false,\n },\n {\n label: 'Grape',\n value: 'grape',\n visibleInMenu: true,\n focused: false,\n checkbox: false,\n },\n {\n label: 'Lemon',\n value: 'lemon',\n visibleInMenu: true,\n focused: false,\n checkbox: false,\n },\n {\n label: 'Orange',\n value: 'orange',\n visibleInMenu: true,\n focused: false,\n disabled: false,\n checkbox: false,\n },\n {\n label: 'Peach',\n value: 'peach',\n visibleInMenu: true,\n focused: false,\n checkbox: false,\n },\n {\n label: 'Pear',\n value: 'pear',\n visibleInMenu: true,\n focused: false,\n checkbox: false,\n },\n {\n label: 'Strawberry',\n value: 'strawberry',\n visibleInMenu: true,\n focused: false,\n disabled: false,\n checkbox: false,\n },\n {\n label: 'Watermelon',\n value: 'watermelon',\n visibleInMenu: true,\n focused: false,\n disabled: false,\n checkbox: false,\n },\n {\n label: 'Pineapple',\n value: 'pineapple',\n visibleInMenu: true,\n focused: false,\n checkbox: false,\n },\n {\n label: 'Kiwi',\n value: 'kiwi',\n visibleInMenu: true,\n focused: false,\n checkbox: false,\n },\n {\n label: 'Mango',\n value: 'mango',\n visibleInMenu: true,\n focused: false,\n checkbox: false,\n },\n {\n label: 'Papaya',\n value: 'papaya',\n visibleInMenu: true,\n focused: false,\n checkbox: false,\n },\n {\n label: 'Plum',\n value: 'plum',\n visibleInMenu: true,\n focused: false,\n checkbox: false,\n },\n {\n label: 'Raspberry',\n value: 'raspberry',\n visibleInMenu: true,\n focused: false,\n checkbox: false,\n },\n {\n label: 'Tangerine',\n value: 'tangerine',\n visibleInMenu: true,\n focused: false,\n checkbox: false,\n },\n];\n\ninterface AutocompleteArgs {\n visibleItems: IAutocompleteItem[];\n 'auto-complete'?: AutocompleteTypes;\n bordered?: boolean;\n 'custom-class'?: string;\n 'debounce-ms'?: number;\n disabled?: boolean;\n feedback?: { level: 'error' | 'warning' | 'success'; message: string };\n 'include-clear'?: boolean;\n 'include-search'?: boolean;\n 'input-id'?: string;\n 'input-tab-index'?: number;\n items: IAutocompleteItem[];\n initialNavigation?: boolean;\n label?: string;\n 'leave-menu-open'?: boolean;\n 'max-chips'?: number;\n 'min-chars': number;\n 'min-input-width'?: number;\n 'multi-select'?: boolean;\n name?: string;\n 'no-results': IAutocompleteNoResults;\n placeholder?: string;\n 'read-only'?: boolean;\n required?: boolean;\n 'show-menu-on-focus'?: boolean;\n 'show-spinner'?: boolean;\n size?: ModusSize;\n value: string;\n 'custom-blur'?: (event: FocusEvent) => void;\n 'custom-input-change'?: (value: string) => void;\n 'custom-item-select'?: (item: IAutocompleteItem) => void;\n 'custom-key-down'?: (event: KeyboardEvent) => void;\n}\n\nconst meta: Meta = {\n title: 'Components/Forms/Autocomplete',\n component: 'modus-wc-autocomplete',\n args: {\n 'auto-complete': undefined,\n bordered: true,\n 'debounce-ms': 300,\n disabled: false,\n 'include-clear': false,\n 'include-search': false,\n items: items,\n label: 'Label',\n 'leave-menu-open': false,\n 'max-chips': 4,\n 'min-chars': 0,\n 'min-input-width': 15,\n 'multi-select': false,\n 'show-menu-on-focus': false,\n 'show-spinner': false,\n 'no-results': {\n ariaLabel: 'No results found',\n label: 'No results found',\n subLabel: 'Check spelling or try a different keyword',\n },\n size: 'md',\n value: '',\n },\n argTypes: {\n 'auto-complete': {\n control: { type: 'text' },\n },\n items: {\n description: 'Array of items for the autocomplete component',\n table: {\n type: {\n detail: `\n Interface: IAutocompleteItem\n Properties:\n - label (string): The display text shown for the autocomplete item\n - selected (boolean, optional): Whether the item is currently selected\n - focused (boolean, optional): Whether the item is focused\n - value (string): The unique value identifier for the item\n - visibleInMenu (boolean): Whether the item should be shown in the dropdown menu\n `,\n },\n },\n },\n 'max-chips': {\n control: { type: 'number', min: 1, max: 10 },\n description:\n 'Maximum number of chips to display before showing \"+N more\" button',\n },\n 'min-input-width': {\n control: { type: 'number', min: 10, max: 300 },\n description:\n 'Minimum width for the text input in pixels. When chips would make input smaller, container height increases instead. Default: 20px.',\n },\n size: {\n control: { type: 'select' },\n options: ['sm', 'md', 'lg'],\n },\n 'custom-blur': {\n description:\n 'Custom blur handler function that overrides default blur behavior',\n table: {\n type: { summary: '(event: FocusEvent) => void' },\n category: 'Custom Handlers',\n },\n },\n 'custom-input-change': {\n description:\n 'Custom input change handler function that overrides default input change behavior',\n table: {\n type: { summary: '(value: string) => void' },\n category: 'Custom Handlers',\n },\n },\n 'custom-item-select': {\n description:\n 'Custom item select handler function that overrides default item selection behavior',\n table: {\n type: { summary: '(item: IAutocompleteItem) => void' },\n category: 'Custom Handlers',\n },\n },\n 'custom-key-down': {\n description:\n 'Custom keydown handler function that overrides default keyboard navigation',\n table: {\n type: { summary: '(event: KeyboardEvent) => void' },\n category: 'Custom Handlers',\n },\n },\n },\n decorators: [withActions],\n parameters: {\n actions: {\n handles: [\n 'chipRemove',\n 'chipsExpansionChange',\n 'clearClick',\n 'inputBlur',\n 'inputChange',\n 'inputFocus',\n 'itemSelect',\n ],\n },\n },\n};\n\nexport default meta;\n//prettier-ignore\nconst Items = html`\n// const autocompleteItems = [\n// {\n// label: 'Apple',\n// value: 'apple',\n// visibleInMenu: true,\n// focused: false,\n// disabled: false,\n// checkbox: false,\n// },\n// {\n// label: 'Banana',\n// value: 'banana',\n// visibleInMenu: true,\n// focused: false,\n// disabled: false,\n// checkbox: false,\n// },\n// {\n// label: 'Blueberry',\n// value: 'blueberry',\n// visibleInMenu: true,\n// focused: false,\n// checkbox: false,\n// },\n// {\n// label: 'Cherry',\n// value: 'cherry',\n// visibleInMenu: true,\n// focused: false,\n// checkbox: false,\n// },\n// {\n// label: 'Grape',\n// value: 'grape',\n// visibleInMenu: true,\n// focused: false,\n// checkbox: false,\n// },\n// {\n// label: 'Lemon',\n// value: 'lemon',\n// visibleInMenu: true,\n// focused: false,\n// checkbox: false,\n// },\n// {\n// label: 'Orange',\n// value: 'orange',\n// visibleInMenu: true,\n// focused: false,\n// disabled: false,\n// checkbox: false,\n// },\n// {\n// label: 'Peach',\n// value: 'peach',\n// visibleInMenu: true,\n// focused: false,\n// checkbox: false,\n// },\n// {\n// label: 'Pear',\n// value: 'pear',\n// visibleInMenu: true,\n// focused: false,\n// checkbox: false,\n// },\n// {\n// label: 'Strawberry',\n// value: 'strawberry',\n// visibleInMenu: true,\n// focused: false,\n// disabled: false,\n// checkbox: false,\n// },\n// {\n// label: 'Watermelon',\n// value: 'watermelon',\n// visibleInMenu: true,\n// focused: false,\n// disabled: false,\n// checkbox: false,\n// },\n// {\n// label: 'Pineapple',\n// value: 'pineapple',\n// visibleInMenu: true,\n// focused: false,\n// checkbox: false,\n// },\n// {\n// label: 'Kiwi',\n// value: 'kiwi',\n// visibleInMenu: true,\n// focused: false,\n// checkbox: false,\n// },\n// {\n// label: 'Mango',\n// value: 'mango',\n// visibleInMenu: true,\n// focused: false,\n// checkbox: false,\n// },\n// {\n// label: 'Papaya',\n// value: 'papaya',\n// visibleInMenu: true,\n// focused: false,\n// checkbox: false,\n// },\n// {\n// label: 'Plum',\n// value: 'plum',\n// visibleInMenu: true,\n// focused: false,\n// checkbox: false,\n// },\n// {\n// label: 'Raspberry',\n// value: 'raspberry',\n// visibleInMenu: true,\n// focused: false,\n// checkbox: false,\n// },\n// {\n// label: 'Tangerine',\n// value: 'tangerine',\n// visibleInMenu: true,\n// focused: false,\n// checkbox: false,\n// },\n// ];\n`;\ntype Story = StoryObj;\n\nconst Template: Story = {\n render: (args) => {\n // prettier-ignore\n return html`\n\n\n\n `;\n },\n};\n\nexport const Default: Story = {\n ...Template,\n};\n\nexport const WithCustomIconSlot: Story = {\n // prettier-ignore\n render: (args) => html`\n\n\n \n\n\n `,\n args: {\n placeholder: 'Search fruits...',\n },\n};\n\nexport const WithFeedback: Story = {\n render: (args) => html`\n \n \n `,\n args: {\n feedback: {\n level: 'error',\n message: 'This field is required',\n },\n label: 'With Feedback',\n required: true,\n },\n parameters: {\n docs: {\n source: {\n code: `\n\n\n\n `,\n },\n },\n },\n};\n\nexport const WithTooltips: Story = {\n name: 'With Tooltips',\n parameters: {\n docs: {\n description: {\n story:\n 'This example demonstrates menu items with tooltips. Hover over the items to see the tooltips.',\n },\n source: {\n code: `\n \n\n\n \n`,\n },\n },\n },\n render: () => {\n const tooltipItems: IAutocompleteItem[] = [\n {\n label: 'Apple',\n value: 'apple',\n tooltipContent: 'A crisp and sweet fruit',\n tooltipPosition: 'top',\n visibleInMenu: true,\n focused: false,\n },\n {\n label: 'Banana',\n value: 'banana',\n tooltipContent: 'A tropical yellow fruit',\n tooltipPosition: 'right',\n visibleInMenu: true,\n focused: false,\n },\n {\n label: 'Cherry',\n value: 'cherry',\n tooltipContent: 'Small red stone fruit',\n tooltipPosition: 'bottom',\n visibleInMenu: true,\n focused: false,\n },\n {\n label: 'Grape',\n value: 'grape',\n tooltipContent: 'Small juicy fruit that grows in clusters',\n tooltipPosition: 'left',\n visibleInMenu: true,\n focused: false,\n },\n {\n label: 'Orange',\n value: 'orange',\n tooltipContent: 'Citrus fruit with a bright color',\n tooltipPosition: 'top',\n visibleInMenu: true,\n focused: false,\n },\n ];\n\n return html`\n \n
\n \n
\n `;\n },\n};\n\nexport const MultiSelect: Story = {\n render: (args) => {\n // Ensure args.items is initialized\n if (!args.items) {\n args.items = [...items];\n }\n // If multi-select, set selected state for some items\n args.items = args.items.map((item) => {\n if (item.value === 'apple' || item.value === 'banana') {\n return { ...item, selected: true };\n }\n return item;\n });\n // prettier-ignore\n return html`\n \n \n \n `;\n },\n};\n\nexport const WithSpinner: Story = {\n render: (args) => {\n let debounceTimer: number;\n\n const handleInputChange = (e: CustomEvent) => {\n if (!e.detail?.target) return;\n\n const autocomplete = (e.target as HTMLInputElement).closest(\n 'modus-wc-autocomplete'\n ) as Element & {\n items: IAutocompleteItem[];\n showSpinner: boolean;\n value: string;\n };\n\n if (autocomplete) {\n const input = e.detail.target as HTMLInputElement;\n const searchText = input.value.toLowerCase();\n\n // Clear previous timeout to avoid multiple API calls\n if (debounceTimer) {\n window.clearTimeout(debounceTimer);\n }\n\n // Show spinner immediately and update input value\n autocomplete.showSpinner = true;\n\n // Simulate an API call with a 2-second delay\n debounceTimer = window.setTimeout(() => {\n // Filter the master list of items to get the new results\n const filteredItems = items.filter((item) =>\n item.label.toLowerCase().includes(searchText)\n );\n\n // Update the component with the new filtered list and hide the spinner\n autocomplete.items = filteredItems;\n autocomplete.showSpinner = false;\n }, 2000);\n }\n };\n // prettier-ignore\n return html`\n \n \n \n `;\n },\n};\n\nexport const CustomMenuItems: Story = {\n render: (args) => {\n const originalNoResults = args['no-results'];\n if (args['leave-menu-open'] == true) {\n args['no-results'] = {\n ariaLabel: '',\n label: '',\n subLabel: '',\n };\n }\n\n const getVisibleItems = (autocomplete: Element): HTMLElement[] => {\n const menuItems = autocomplete.querySelectorAll(\n 'modus-wc-menu-item:not([disabled])'\n );\n return Array.from(menuItems).filter(\n (item: Element): item is HTMLElement => {\n const style = window.getComputedStyle(item);\n return style.display !== 'none' && !item.classList.contains('hidden');\n }\n );\n };\n\n const handleCustomKeyDown = (e: KeyboardEvent) => {\n const autocomplete = (e.target as HTMLInputElement).closest(\n 'modus-wc-autocomplete'\n ) as Element & {\n openMenu: () => Promise;\n closeMenu: () => Promise;\n readOnly?: boolean;\n disabled?: boolean;\n };\n if (!autocomplete) return;\n\n // Don't process keyboard events when disabled or readOnly\n if (autocomplete.disabled || autocomplete.readOnly) return;\n\n const visibleItems = getVisibleItems(autocomplete);\n\n // Get all button elements within visible menu items\n const buttons = visibleItems\n .map((item) => item.querySelector('button'))\n .filter(Boolean) as HTMLButtonElement[];\n const currentFocusedButton = document.activeElement as HTMLButtonElement;\n const currentIndex = buttons.indexOf(currentFocusedButton);\n\n switch (e.key) {\n case 'ArrowDown': {\n e.preventDefault();\n // Open menu when arrow key is pressed\n void autocomplete.openMenu();\n\n let nextIndex = currentIndex + 1;\n // Stop at the last item instead of wrapping\n if (nextIndex >= buttons.length) return;\n if (nextIndex < 0) nextIndex = 0;\n\n buttons[nextIndex]?.focus();\n break;\n }\n\n case 'ArrowUp': {\n e.preventDefault();\n // Open menu when arrow key is pressed\n void autocomplete.openMenu();\n\n let prevIndex = currentIndex - 1;\n // Stop at the first item instead of wrapping\n if (prevIndex < 0) return;\n\n buttons[prevIndex]?.focus();\n break;\n }\n\n case 'Enter': {\n e.preventDefault();\n // If a button is focused, click it\n if (buttons.includes(currentFocusedButton)) {\n currentFocusedButton.click();\n }\n const input = autocomplete.querySelector('input');\n input?.focus();\n break;\n }\n\n case 'Escape': {\n e.preventDefault();\n void autocomplete.closeMenu();\n // Return focus to input\n const input = autocomplete.querySelector('input');\n input?.focus();\n break;\n }\n }\n };\n\n const handleInputChange = (e: CustomEvent) => {\n if (!e.detail?.target) return;\n\n const autocomplete = (e.target as HTMLInputElement).closest(\n 'modus-wc-autocomplete'\n ) as Element & { noResults: IAutocompleteNoResults };\n\n if (autocomplete) {\n const searchText = (\n e.detail.target as HTMLInputElement\n ).value.toLowerCase();\n\n const menuItems = autocomplete?.querySelectorAll('modus-wc-menu-item');\n\n // Clear selected state when input is empty\n if (searchText === '') {\n menuItems?.forEach((item) => {\n item.removeAttribute('selected');\n });\n }\n\n let hiddenCount = 0;\n Array.from(menuItems ?? []).forEach((menuItem) => {\n const label = menuItem.getAttribute('label')?.toLowerCase() || '';\n if (!label.includes(searchText)) {\n menuItem.classList.add('hidden');\n hiddenCount++;\n } else {\n menuItem.classList.remove('hidden');\n }\n });\n\n // Show no results if all items are hidden\n autocomplete.noResults =\n hiddenCount === menuItems?.length\n ? originalNoResults\n : { ariaLabel: '', label: '', subLabel: '' };\n\n // Show/hide the no results element\n const noResultsElement = autocomplete.querySelector(\n '.no-results-item'\n ) as HTMLElement;\n if (noResultsElement) {\n if (hiddenCount === menuItems?.length) {\n noResultsElement.classList.add('visible');\n } else {\n noResultsElement.classList.remove('visible');\n }\n }\n }\n };\n\n const handleItemSelect = (e: CustomEvent<{ value: string }>) => {\n const autocomplete = (e.target as HTMLInputElement).closest(\n 'modus-wc-autocomplete'\n ) as HTMLElement & { value: string; closeMenu: () => Promise };\n\n if (autocomplete) {\n const selectedValue = e.detail.value;\n autocomplete.value = selectedValue;\n // Update selected state on menu items\n const menuItems = autocomplete.querySelectorAll('modus-wc-menu-item');\n menuItems.forEach((item) => {\n if (item.getAttribute('value') === selectedValue) {\n item.setAttribute('selected', 'true');\n } else {\n item.removeAttribute('selected');\n }\n });\n // Close menu after selection unless leaveMenuOpen is true\n if (!args['leave-menu-open']) {\n void autocomplete.closeMenu();\n }\n }\n };\n // prettier-ignore\n return html`\n\n\n
\n \n
\n \n
\n \n \n
\n \n
\n \n \n
\n \n
\n \n \n
\n \n
\n \n
  • \n
    \n \n
    No results found
    \n
    \n
  • \n
    \n\n\n `;\n },\n};\n\nexport const CustomEventHandlers: Story = {\n render: (args) => {\n interface AutocompleteElement extends HTMLElement {\n items: IAutocompleteItem[];\n value: string;\n openMenu(): Promise;\n closeMenu(): Promise;\n }\n\n // Custom keydown handler with skip navigation and escape animation\n const customKeyDown = (e: KeyboardEvent) => {\n const autocomplete = document.getElementById(\n 'autocomplete-custom-event-handlers'\n ) as AutocompleteElement;\n if (!autocomplete) return;\n\n // Prevent default for navigation keys\n if (['ArrowDown', 'ArrowUp', 'Enter', 'Escape'].includes(e.key)) {\n e.preventDefault();\n }\n\n const visibleItems = args.items.filter(\n (item) => item.visibleInMenu && !item.disabled\n );\n\n switch (e.key) {\n case 'Escape':\n args.items = args.items.map((item) => ({\n ...item,\n focused: false,\n }));\n autocomplete.items = [...args.items];\n void autocomplete.closeMenu();\n // Custom: Show escape animation\n autocomplete.style.transform = 'scale(0.98)';\n setTimeout(() => {\n autocomplete.style.transform = '';\n }, 200);\n break;\n\n case 'ArrowDown': {\n // Open menu if not already open\n void autocomplete.openMenu();\n\n const currentIndex = visibleItems.findIndex((item) => item.focused);\n const nextIndex =\n currentIndex < 0\n ? 0\n : Math.min(currentIndex + 1, visibleItems.length - 1);\n\n // Custom: Skip every other item for faster navigation\n const skipIndex =\n nextIndex + 1 < visibleItems.length ? nextIndex + 1 : nextIndex;\n\n args.items = args.items.map((item) => ({\n ...item,\n focused: visibleItems[skipIndex]?.value === item.value,\n }));\n break;\n }\n\n case 'ArrowUp': {\n const currentIndex = visibleItems.findIndex((item) => item.focused);\n const prevIndex =\n currentIndex < 0\n ? visibleItems.length - 1\n : Math.max(currentIndex - 1, 0);\n\n // Custom: Skip every other item for faster navigation\n const skipIndex = prevIndex - 1 >= 0 ? prevIndex - 1 : prevIndex;\n\n args.items = args.items.map((item) => ({\n ...item,\n focused: visibleItems[skipIndex]?.value === item.value,\n }));\n break;\n }\n\n case 'Enter': {\n const focusedItem = visibleItems.find((item) => item.focused);\n if (focusedItem) {\n // For single select, clear previous selection\n args.items = args.items.map((item) => ({\n ...item,\n selected: item.value === focusedItem.value,\n focused: false,\n }));\n autocomplete.value = focusedItem.label;\n void autocomplete.closeMenu();\n }\n break;\n }\n\n default:\n return;\n }\n\n autocomplete.items = [...args.items];\n };\n\n // Custom input change handler with fuzzy character matching\n const customInputChange = (value: string) => {\n const autocomplete = document.getElementById(\n 'autocomplete-custom-event-handlers'\n ) as AutocompleteElement;\n if (!autocomplete) return;\n\n const searchChars = value.toLowerCase().split('');\n\n // Custom fuzzy search: Match items that contain ALL typed characters (in any order)\n if (value.length > 0) {\n // Calculate match score for each item\n const scoredItems = args.items.map((item) => {\n const itemLower = item.label.toLowerCase();\n let score = 0;\n let allCharsFound = true;\n\n // Check if all search characters exist in the item\n for (const char of searchChars) {\n if (itemLower.includes(char)) {\n // Bonus points for consecutive characters\n const charIndex = itemLower.indexOf(char);\n if (charIndex === 0)\n score += 3; // Start of word bonus\n else if (itemLower[charIndex - 1] === ' ')\n score += 2; // Start of any word\n else score += 1;\n } else {\n allCharsFound = false;\n break;\n }\n }\n\n // Additional bonus for exact substring match\n if (allCharsFound && itemLower.includes(value.toLowerCase())) {\n score += 10;\n }\n\n return {\n item,\n score: allCharsFound ? score : -1,\n visible: allCharsFound,\n };\n });\n\n // Sort by score (highest first) and update items\n scoredItems.sort((a, b) => b.score - a.score);\n args.items = scoredItems.map(({ item, visible }) => ({\n ...item,\n visibleInMenu: visible,\n focused: false,\n selected: item.selected && visible,\n // Add score as part of label for demonstration (you can remove this in production)\n label: item.label,\n }));\n } else {\n // No search text, show all items\n args.items = args.items.map((item) => ({\n ...item,\n visibleInMenu: true,\n focused: false,\n }));\n }\n\n autocomplete.items = [...args.items];\n autocomplete.value = value;\n // Show match count in console for demonstration\n const matchCount = args.items.filter((item) => item.visibleInMenu).length;\n console.log(`Fuzzy search for \"${value}\": ${matchCount} matches found`);\n\n // Show menu if there are visible items\n const hasVisibleItems = args.items.some((item) => item.visibleInMenu);\n if (hasVisibleItems && value.length >= args['min-chars']) {\n void autocomplete.openMenu();\n } else {\n void autocomplete.closeMenu();\n }\n };\n\n // Custom item select handler\n const customItemSelect = (item: IAutocompleteItem) => {\n const autocomplete = document.getElementById(\n 'autocomplete-custom-event-handlers'\n ) as AutocompleteElement;\n if (!autocomplete) return;\n\n // Clear previous selections for single select\n args.items = args.items.map((menuItem) => ({\n ...menuItem,\n selected: menuItem.value === item.value,\n focused: false,\n }));\n\n autocomplete.items = [...args.items];\n autocomplete.value = item.label;\n void autocomplete.closeMenu();\n };\n // prettier-ignore\n return html`\n \n\n \n \n `;\n },\n args: {\n bordered: true,\n 'debounce-ms': 0, // Set to 0 to see immediate feedback\n disabled: false,\n 'include-clear': true,\n 'include-search': true,\n items: items,\n 'leave-menu-open': false,\n 'min-chars': 0,\n 'no-results': {\n label: 'No fruits found',\n subLabel: 'Try different characters',\n },\n placeholder: 'Search fruits...',\n 'input-id': 'custom-handlers-input',\n 'read-only': false,\n required: false,\n 'show-menu-on-focus': true,\n 'show-spinner': false,\n size: 'md',\n value: '',\n },\n parameters: {\n docs: {\n description: {\n story: `This example demonstrates custom event handlers with three specific behaviors:\n\n1. **Skip Navigation**: Arrow keys skip every other item for 2x faster navigation\n2. **Escape Animation**: Pressing Escape triggers a subtle scale animation\n3. **Fuzzy Character Search**: Instead of normal substring matching, this searches for items containing ALL typed characters in any order\n\nThe fuzzy search allows finding items with scattered characters:\n- Type \"pae\" to find Pine**a**ppl**e**\n- Type \"bry\" to find Blue**b**er**ry**, Straw**b**er**ry**, Rasp**b**er**ry**\n\nItems are automatically sorted by relevance with exact substring matches appearing first.`,\n },\n },\n },\n};\n\nexport const WithProgrammaticControl: Story = {\n args: {\n ...meta.args,\n items: items, // Explicitly set items for this story\n },\n render: (args) => {\n // Type for autocomplete element with methods\n interface AutocompleteElement extends HTMLElement {\n selectItem(item: IAutocompleteItem | null): Promise;\n openMenu(): Promise;\n closeMenu(): Promise;\n toggleMenu(): Promise;\n focusInput(): Promise;\n clearInput(): Promise;\n }\n\n // Handler functions that will be attached to buttons\n const handleSelectApple = async () => {\n const autocomplete = document.getElementById(\n 'programmatic-autocomplete'\n ) as AutocompleteElement;\n if (autocomplete) {\n const appleItem = items.find((item) => item.value === 'apple') || null;\n await autocomplete.selectItem(appleItem);\n }\n };\n\n const handleSelectNull = async () => {\n const autocomplete = document.getElementById(\n 'programmatic-autocomplete'\n ) as AutocompleteElement;\n if (autocomplete) {\n await autocomplete.selectItem(null);\n }\n };\n\n const handleOpenMenu = async () => {\n const autocomplete = document.getElementById(\n 'programmatic-autocomplete'\n ) as AutocompleteElement;\n if (autocomplete) {\n await autocomplete.openMenu();\n }\n };\n\n const handleCloseMenu = async () => {\n const autocomplete = document.getElementById(\n 'programmatic-autocomplete'\n ) as AutocompleteElement;\n if (autocomplete) {\n await autocomplete.closeMenu();\n }\n };\n\n const handleToggleMenu = async () => {\n const autocomplete = document.getElementById(\n 'programmatic-autocomplete'\n ) as AutocompleteElement;\n if (autocomplete) {\n await autocomplete.toggleMenu();\n }\n };\n\n const handleFocusInput = async () => {\n const autocomplete = document.getElementById(\n 'programmatic-autocomplete'\n ) as AutocompleteElement;\n if (autocomplete) {\n await autocomplete.focusInput();\n }\n };\n\n const handleClearInput = async () => {\n const autocomplete = document.getElementById(\n 'programmatic-autocomplete'\n ) as AutocompleteElement;\n if (autocomplete) {\n await autocomplete.clearInput();\n }\n };\n\n // Attach handlers to window for inline onclick\n interface WindowWithHandlers extends Window {\n handleSelectApple?: () => Promise;\n handleSelectNull?: () => Promise;\n handleOpenMenu?: () => Promise;\n handleCloseMenu?: () => Promise;\n handleToggleMenu?: () => Promise;\n handleFocusInput?: () => Promise;\n handleClearInput?: () => Promise;\n }\n\n const windowWithHandlers = window as WindowWithHandlers;\n windowWithHandlers.handleSelectApple = handleSelectApple;\n windowWithHandlers.handleSelectNull = handleSelectNull;\n windowWithHandlers.handleOpenMenu = handleOpenMenu;\n windowWithHandlers.handleCloseMenu = handleCloseMenu;\n windowWithHandlers.handleToggleMenu = handleToggleMenu;\n windowWithHandlers.handleFocusInput = handleFocusInput;\n windowWithHandlers.handleClearInput = handleClearInput;\n\n // prettier-ignore\n return html`\n \n\n \n
    \n

    Programmatic Control Methods

    \n\n
    \n \n
    \n \n Select Apple\n \n \n Clear Selection\n \n
    \n
    \n\n
    \n \n
    \n \n Open Menu\n \n \n Close Menu\n \n \n Toggle Menu\n \n
    \n
    \n\n
    \n \n
    \n \n Focus Input\n \n \n Clear All\n \n
    \n
    \n
    \n
    \n \n \n `;\n },\n parameters: {\n docs: {\n description: {\n story: `\n## Public Methods\n\nThe autocomplete component exposes several methods that can be called programmatically:\n\n### selectItem(item: IAutocompleteItem | null): Promise\nProgrammatically select an item. Pass \\`null\\` to clear selection.\n\n\\`\\`\\`javascript\nconst autocomplete = document.querySelector('modus-wc-autocomplete');\nconst item = { label: 'Apple', value: 'apple', visibleInMenu: true };\nawait autocomplete.selectItem(item);\n\\`\\`\\`\n\n### openMenu(): Promise\nProgrammatically open the dropdown menu.\n\n\\`\\`\\`javascript\nawait autocomplete.openMenu();\n\\`\\`\\`\n\n### closeMenu(): Promise\nProgrammatically close the dropdown menu.\n\n\\`\\`\\`javascript\nawait autocomplete.closeMenu();\n\\`\\`\\`\n\n### toggleMenu(): Promise\nToggle the dropdown menu open/closed.\n\n\\`\\`\\`javascript\nawait autocomplete.toggleMenu();\n\\`\\`\\`\n\n### focusInput(): Promise\nSet focus to the input element.\n\n\\`\\`\\`javascript\nawait autocomplete.focusInput();\n\\`\\`\\`\n\n### clearInput(): Promise\nClear the input value and all selections.\n\n\\`\\`\\`javascript\nawait autocomplete.clearInput();\n\\`\\`\\`\n\n `,\n },\n },\n },\n};\n\nexport const DynamicOptions: Story = {\n render: (args) => {\n const defaultFruits = [\n { label: 'Apple', value: 'apple', visibleInMenu: true },\n { label: 'Banana', value: 'banana', visibleInMenu: true },\n { label: 'Orange', value: 'orange', visibleInMenu: true },\n { label: 'Strawberry', value: 'strawberry', visibleInMenu: true },\n ];\n\n // Extended dataset that will be searched when typing\n const allFruits = [\n ...defaultFruits,\n { label: 'Blackberry', value: 'blackberry', visibleInMenu: true },\n { label: 'Blueberry', value: 'blueberry', visibleInMenu: true },\n { label: 'Cherry', value: 'cherry', visibleInMenu: true },\n { label: 'Cranberry', value: 'cranberry', visibleInMenu: true },\n { label: 'Fig', value: 'fig', visibleInMenu: true },\n { label: 'Grape', value: 'grape', visibleInMenu: true },\n { label: 'Kiwi', value: 'kiwi', visibleInMenu: true },\n { label: 'Lemon', value: 'lemon', visibleInMenu: true },\n { label: 'Lime', value: 'lime', visibleInMenu: true },\n { label: 'Mango', value: 'mango', visibleInMenu: true },\n { label: 'Melon', value: 'melon', visibleInMenu: true },\n { label: 'Peach', value: 'peach', visibleInMenu: true },\n { label: 'Pineapple', value: 'pineapple', visibleInMenu: true },\n { label: 'Raspberry', value: 'raspberry', visibleInMenu: true },\n { label: 'Watermelon', value: 'watermelon', visibleInMenu: true },\n ];\n\n const handleInputChange = (e: CustomEvent) => {\n if (!e.detail?.target) return;\n\n const autocomplete = (e.target as HTMLInputElement).closest(\n 'modus-wc-autocomplete'\n );\n\n if (autocomplete) {\n const input = e.detail.target as HTMLInputElement;\n const searchText = input.value.toLowerCase();\n\n if (searchText === '') {\n autocomplete.items = [...defaultFruits];\n autocomplete.value = input.value;\n return;\n }\n\n autocomplete.showSpinner = true;\n setTimeout(() => {\n const filteredFruits = allFruits.filter((fruit) =>\n fruit.label.toLowerCase().includes(searchText)\n );\n\n autocomplete.items = filteredFruits;\n autocomplete.showSpinner = false;\n }, 1000);\n\n autocomplete.value = input.value;\n }\n };\n\n const handleItemSelect = (e: CustomEvent) => {\n const autocomplete = (e.target as HTMLInputElement).closest(\n 'modus-wc-autocomplete'\n );\n\n if (autocomplete) {\n const label = e.detail.label;\n if (label) {\n autocomplete.value = label;\n }\n }\n };\n\n return html`\n \n \n \n `;\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n // Create a unique shadow host for autocomplete component\n if (!customElements.get('autocomplete-shadow-host')) {\n const AutocompleteShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-autocomplete',\n propsMapper: (v: AutocompleteArgs, el: HTMLElement) => {\n const autocompleteEl = el as unknown as {\n bordered: boolean;\n customClass: string;\n debounceMs: number;\n disabled: boolean;\n includeClear: boolean;\n includeSearch: boolean;\n inputId: string;\n inputTabIndex: number;\n items: IAutocompleteItem[];\n label: string;\n leaveMenuOpen: boolean;\n maxChips: number;\n minChars: number;\n minInputWidth: number;\n multiSelect: boolean;\n name: string;\n noResults: IAutocompleteNoResults;\n placeholder: string;\n readOnly: boolean;\n required: boolean;\n showMenuOnFocus: boolean;\n showSpinner: boolean;\n size: string;\n value: string;\n };\n autocompleteEl.bordered = Boolean(v.bordered);\n autocompleteEl.customClass = v['custom-class'] || '';\n if (typeof v['debounce-ms'] === 'number') {\n autocompleteEl.debounceMs = v['debounce-ms'];\n }\n autocompleteEl.disabled = Boolean(v.disabled);\n autocompleteEl.includeClear = Boolean(v['include-clear']);\n autocompleteEl.includeSearch = Boolean(v['include-search']);\n if (typeof v['input-id'] === 'string') {\n autocompleteEl.inputId = v['input-id'];\n }\n if (typeof v['input-tab-index'] === 'number') {\n autocompleteEl.inputTabIndex = v['input-tab-index'];\n }\n autocompleteEl.items = v.items;\n if (typeof v.label === 'string') {\n autocompleteEl.label = v.label;\n }\n if (typeof v['leave-menu-open'] === 'boolean') {\n autocompleteEl.leaveMenuOpen = v['leave-menu-open'];\n }\n if (typeof v['max-chips'] === 'number') {\n autocompleteEl.maxChips = v['max-chips'];\n }\n if (typeof v['min-chars'] === 'number') {\n autocompleteEl.minChars = v['min-chars'];\n }\n if (typeof v['min-input-width'] === 'number') {\n autocompleteEl.minInputWidth = v['min-input-width'];\n }\n autocompleteEl.multiSelect = Boolean(v['multi-select']);\n if (typeof v.name === 'string') {\n autocompleteEl.name = v.name;\n }\n autocompleteEl.noResults = v['no-results'];\n if (typeof v.placeholder === 'string') {\n autocompleteEl.placeholder = v.placeholder;\n }\n autocompleteEl.readOnly = Boolean(v['read-only']);\n autocompleteEl.required = Boolean(v.required);\n if (typeof v['show-menu-on-focus'] === 'boolean') {\n autocompleteEl.showMenuOnFocus = v['show-menu-on-focus'];\n }\n autocompleteEl.showSpinner = Boolean(v['show-spinner']);\n if (typeof v.size === 'string') {\n autocompleteEl.size = v.size;\n }\n autocompleteEl.value = v.value;\n },\n });\n customElements.define('autocomplete-shadow-host', AutocompleteShadowHost);\n }\n\n return html``;\n },\n};\nexport const Migration: Story = {\n parameters: {\n docs: {\n description: {\n story: `\n#### Breaking Changes\n\n - In 1.0 input state was maintained by the component. 2.0 components encourage users to follow a controlled\n input model. See the Form Inputs [documentation]([Angular](?path=/docs/documentation-form-inputs--docs) for\n additional info and examples.\n - To handle updating items in 2.0, simply create a new array of items and bind it to the \\`items\\` prop. The 1.0 prop\n \\`filter-options\\` is no longer necessary.\n - Size values have changed from verbose names (\\`small\\`, \\`medium\\`, \\`large\\`) to abbreviations (\\`sm\\`, \\`md\\`, \\`lg\\`).\n\n#### Prop Mapping\n\n| 1.0 Prop | 2.0 Prop | Notes |\n|-------------------------------|---------------------|-------------------------------------------------------------|\n| aria-label | aria-label | |\n| clearable | include-clear | |\n| disabled | disabled | |\n| disable-close-on-select | leave-menu-open | |\n| dropdown-max-height | | Not carried over, use CSS instead |\n| dropdown-z-index | | Not carried over, use CSS instead |\n| error-text | feedback | feedback.level = 'error', feedback.message = 'Error message'|\n| filter-options | | Rebind options |\n| include-search-icon | include-search | |\n| label | label | |\n| loading | show-spinner | |\n| multiple | multi-select | |\n| no-results-found-text | no-results.label | |\n| no-results-found-subtext | no-results.subLabel | |\n| options | items | |\n| placeholder | placeholder | |\n| read-only | read-only | |\n| required | required | |\n| show-no-results-found-message | | Not carried over, use \\`no-results\\` prop |\n| show-options-on-focus | show-menu-on-focus | |\n| size | size | \\`small\\` → \\`sm\\`, \\`medium\\` → \\`md\\`, \\`large\\` → \\`lg\\` |\n| value | value | |\n\n#### Event Mapping\n\n| 1.0 Event | 2.0 Event | Notes |\n|-------------|-------------|------------------|\n| optionSelected ||\n| selectionsChanged ||\n| valueChange | inputChange | |\n\n#### Interfaces\n\n##### 1.0\n\n\\`\\`\\`typescript\ninterface ModusAutocompleteOption {\n id: string;\n value: string;\n}\n\\`\\`\\`\n\n##### 2.0\n\n\\`\\`\\`typescript\ninterface IAutocompleteItem {\n label: string;\n selected?: boolean;\n value: string;\n visibleInMenu: boolean;\n}\n\\`\\`\\`\n `,\n },\n },\n // To hide the actual story rendering and only show docs:\n controls: { disable: true },\n canvas: { disable: true },\n },\n // Simple render function or leave it empty\n render: () => html`
    `,\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-avatar.json b/mcp/versions/1.5.0/component-docs/modus-wc-avatar.json new file mode 100644 index 000000000..0b25da6a2 --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-avatar.json @@ -0,0 +1,83 @@ +{ + "description": "A customizable avatar component used to create avatars with different images or user initials. When no image is provided, the component can display initials (up to 3 characters) from the initials prop. The component will extract the first letter of each word in the initials string.", + "properties": [ + { + "name": "alt", + "type": "string", + "description": "The image alt attribute for accessibility.", + "default": null, + "mutable": false, + "end_line": 23 + }, + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the inner div.", + "default": "''", + "mutable": false, + "end_line": 26 + }, + { + "name": "imgSrc", + "type": "string", + "description": "The location of the image.", + "default": "''", + "mutable": false, + "end_line": 29 + }, + { + "name": "initials", + "type": "string", + "description": "The initials to display when no image is provided.", + "default": "''", + "mutable": false, + "end_line": 32 + }, + { + "name": "shape", + "type": "'circle' | 'square'", + "description": "The shape of the avatar.", + "default": "'circle'", + "mutable": false, + "end_line": 37 + }, + { + "name": "size", + "type": "DaisySize", + "description": "The size of the avatar.", + "default": "'md'", + "mutable": false, + "end_line": 40 + } + ], + "events": [], + "methods": [], + "slots": [], + "examples": { + "basic": "", + "variations": [], + "args": { + "alt": "'Example avatar'", + "img-src": "'https://i.pinimg.com/474x/73/54/79/7354794bf3873c3ef2666f778da4bcac.jpg'", + "shape": "'circle'", + "initials": "''", + "size": "'md'" + }, + "argTypes": {}, + "usage": [] + }, + "tag": "modus-wc-avatar", + "storyExample": { + "template": "", + "args": { + "alt": "'Example avatar'", + "img-src": "'https://i.pinimg.com/474x/73/54/79/7354794bf3873c3ef2666f778da4bcac.jpg'", + "shape": "'circle'", + "initials": "''", + "size": "'md'" + }, + "argTypes": {}, + "events": [], + "fullContent": "import { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\nimport { DaisySize } from '../types';\n\ninterface AvatarArgs {\n alt: string;\n 'custom-class'?: string;\n 'img-src': string;\n initials: string;\n shape: string;\n size: DaisySize;\n}\n\nconst meta: Meta = {\n title: 'Components/Avatar',\n component: 'modus-wc-avatar',\n args: {\n alt: 'Example avatar',\n 'img-src':\n 'https://i.pinimg.com/474x/73/54/79/7354794bf3873c3ef2666f778da4bcac.jpg',\n shape: 'circle',\n initials: '',\n size: 'md',\n },\n argTypes: {\n shape: {\n control: { type: 'select' },\n options: ['circle', 'square'],\n },\n size: {\n control: { type: 'select' },\n options: ['xs', 'sm', 'md', 'lg'],\n },\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nconst Template: Story = {\n render: (args) => {\n return html`\n \n `;\n },\n};\n\nexport const Default: Story = { ...Template };\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n if (!customElements.get('avatar-shadow-host')) {\n const AvatarShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-avatar',\n propsMapper: (v: AvatarArgs, el: HTMLElement) => {\n const avatarEl = el as unknown as {\n alt: string;\n customClass: string;\n imgSrc: string;\n initials: string;\n shape: string;\n size: string;\n };\n avatarEl.alt = v.alt;\n avatarEl.customClass = v['custom-class'] || '';\n avatarEl.imgSrc = v['img-src'];\n avatarEl.initials = v.initials;\n avatarEl.shape = v.shape;\n avatarEl.size = v.size;\n },\n });\n customElements.define('avatar-shadow-host', AvatarShadowHost);\n }\n\n return html``;\n },\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-badge.json b/mcp/versions/1.5.0/component-docs/modus-wc-badge.json new file mode 100644 index 000000000..24f1a685e --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-badge.json @@ -0,0 +1,68 @@ +{ + "description": "A customizable badge component used to create badges with different sizes, types, and colors. The component supports a `` for injecting content within the badge.", + "properties": [ + { + "name": "color", + "type": "| 'primary' | 'secondary' | 'tertiary' | 'high-contrast' | 'success' | 'warning' | 'danger'", + "description": "The color variant of the badge.", + "default": "'primary'", + "mutable": false, + "end_line": 32 + }, + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the span element.", + "default": "''", + "mutable": false, + "end_line": 35 + }, + { + "name": "size", + "type": "ModusSize", + "description": "The size of the badge.", + "default": "'md'", + "mutable": false, + "end_line": 38 + }, + { + "name": "variant", + "type": "'counter' | 'filled' | 'outlined' | 'text'", + "description": "The variant of the badge.", + "default": "'filled'", + "mutable": false, + "end_line": 41 + } + ], + "events": [], + "methods": [], + "slots": [ + { + "name": "default", + "description": "Slot for default content" + } + ], + "examples": { + "basic": "\n Badge\n", + "variations": [], + "args": { + "color": "'primary'", + "size": "'md'", + "variant": "'filled'" + }, + "argTypes": {}, + "usage": [] + }, + "tag": "modus-wc-badge", + "storyExample": { + "template": "\n Badge\n", + "args": { + "color": "'primary'", + "size": "'md'", + "variant": "'filled'" + }, + "argTypes": {}, + "events": [], + "fullContent": "import { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\nimport { ModusSize } from '../types';\n\ninterface BadgeArgs {\n color:\n | 'primary'\n | 'secondary'\n | 'tertiary'\n | 'high-contrast'\n | 'success'\n | 'warning'\n | 'danger';\n 'custom-class'?: string;\n size: ModusSize;\n variant: 'counter' | 'filled' | 'outlined' | 'text';\n}\n\nconst meta: Meta = {\n title: 'Components/Badge',\n component: 'modus-wc-badge',\n args: {\n color: 'primary',\n size: 'md',\n variant: 'filled',\n },\n argTypes: {\n color: {\n control: { type: 'select' },\n options: [\n 'primary',\n 'secondary',\n 'tertiary',\n 'high-contrast',\n 'success',\n 'warning',\n 'danger',\n ],\n },\n size: {\n control: { type: 'select' },\n options: ['sm', 'md', 'lg'],\n },\n variant: {\n control: { type: 'select' },\n options: ['counter', 'filled', 'outlined', 'text'],\n },\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nconst Template: Story = {\n render: (args) => {\n // prettier-ignore\n return html`\n\n Badge\n\n `;\n },\n};\n\nexport const Default: Story = {\n ...Template,\n};\n\nexport const WithIcon: Story = {\n render: () => {\n // prettier-ignore\n return html`\n\n\n \n Item\n\n `;\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n if (!customElements.get('badge-shadow-host')) {\n const BadgeShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-badge',\n propsMapper: (v: BadgeArgs, el: HTMLElement) => {\n const badgeEl = el as unknown as {\n color: string;\n customClass: string;\n size: string;\n variant: string;\n };\n badgeEl.color = v.color;\n badgeEl.customClass = v['custom-class'] || '';\n badgeEl.size = v.size;\n badgeEl.variant = v.variant;\n },\n defaultContent: 'Badge',\n });\n customElements.define('badge-shadow-host', BadgeShadowHost);\n }\n\n return html``;\n },\n};\nexport const Migration: Story = {\n parameters: {\n docs: {\n description: {\n story: `\n#### Breaking Changes\n\n - The \\`dark\\` color option is now \\`high-contrast\\`\n - The \\`type\\` prop is now \\`variant\\` and \\`default\\` type is now \\`filled\\`\n - Size values have changed from verbose names (\\`small\\`, \\`medium\\`, \\`large\\`) to abbreviations (\\`sm\\`, \\`md\\`, \\`lg\\`).\n\n#### Prop Mapping\n\n| 1.0 Prop | 2.0 Prop | Notes |\n|------------|------------|-------------------------------------------------------------|\n| aria-label | aria-label | |\n| color | color | \\`dark\\` is now \\`high-contrast\\` |\n| size | size | \\`small\\` → \\`sm\\`, \\`medium\\` → \\`md\\`, \\`large\\` → \\`lg\\` |\n| type | variant | \\`default\\` is now \\`filled\\` |\n `,\n },\n },\n // To hide the actual story rendering and only show docs:\n controls: { disable: true },\n canvas: { disable: true },\n },\n // Simple render function or leave it empty\n render: () => html`
    `,\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-breadcrumbs.json b/mcp/versions/1.5.0/component-docs/modus-wc-breadcrumbs.json new file mode 100644 index 000000000..8e6116be6 --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-breadcrumbs.json @@ -0,0 +1,63 @@ +{ + "description": "A customizable breadcrumbs component used to help users navigate through a website.", + "properties": [ + { + "name": "items", + "type": "IBreadcrumb[]", + "description": "The breadcrumbs to render.", + "default": "[]", + "mutable": false, + "end_line": 36 + }, + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the inner div.", + "default": "''", + "mutable": false, + "end_line": 39 + }, + { + "name": "size", + "type": "ModusSize", + "description": "The size of the breadcrumbs.", + "default": "'md'", + "mutable": false, + "end_line": 42 + } + ], + "events": [ + { + "name": "breadcrumbClick", + "detail": "IBreadcrumb", + "description": "Event emitted when a breadcrumb is clicked.", + "end_line": 45 + } + ], + "methods": [], + "slots": [], + "examples": { + "basic": "\n", + "variations": [], + "args": { + "size": "'md'" + }, + "argTypes": {}, + "usage": [], + "events": [ + "breadcrumbClick" + ] + }, + "tag": "modus-wc-breadcrumbs", + "storyExample": { + "template": "\n", + "args": { + "size": "'md'" + }, + "argTypes": {}, + "events": [ + "breadcrumbClick" + ], + "fullContent": "import { withActions } from '@storybook/addon-actions/decorator';\nimport { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { IBreadcrumb } from './modus-wc-breadcrumbs';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\nimport { DaisySize } from '../types';\n\nconst items: IBreadcrumb[] = [\n {\n label: 'Root',\n url: '#',\n },\n {\n label: 'Subpage',\n url: '#',\n },\n {\n label: 'Current Page',\n url: '#',\n },\n];\n\nconst fallbackItems: IBreadcrumb[] = [\n {\n label: 'Unsafe URL fallback',\n url: 'javascript:alert(1)',\n },\n {\n label: 'Safe subpage',\n url: '#',\n },\n {\n label: 'Current Page',\n url: '#',\n },\n];\n\ninterface BreadcrumbArgs {\n 'custom-class'?: string;\n items: IBreadcrumb[];\n size?: DaisySize;\n}\n\nconst meta: Meta = {\n title: 'Components/Breadcrumbs',\n component: 'modus-wc-breadcrumbs',\n args: {\n items,\n size: 'md',\n },\n argTypes: {\n items: {\n description: 'Array of items for the breadcrumbs component',\n table: {\n type: {\n detail: `\n Interface: IBreadcrumb\n Properties:\n - label (string): The text to render in the breadcrumb\n - url (string, optional): The URL emitted when the breadcrumb is clicked\n `,\n },\n },\n },\n size: {\n control: {\n type: 'select',\n },\n options: ['xs', 'sm', 'md', 'lg'],\n },\n },\n decorators: [withActions],\n parameters: {\n actions: {\n handles: ['breadcrumbClick'],\n },\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nconst Template: Story = {\n render: (args) => {\n // prettier-ignore\n return html`\n\n\n `;\n },\n};\n\nexport const Default: Story = { ...Template };\n\nexport const UnderlineLinks: Story = {\n render: (args) => {\n // prettier-ignore\n return html`\n\n\n\n `;\n },\n};\n\nexport const FallbackButton: Story = {\n args: {\n items: fallbackItems,\n },\n parameters: {\n docs: {\n description: {\n story:\n 'Shows the fallback behavior when a breadcrumb item has an unsafe URL. The first item renders as a button instead of a navigable link, while still emitting the `breadcrumbClick` event.',\n },\n },\n },\n render: (args) => {\n // prettier-ignore\n return html`\n\n `;\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n if (!customElements.get('breadcrumbs-shadow-host')) {\n const BreadcrumbsShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-breadcrumbs',\n propsMapper: (v: BreadcrumbArgs, el: HTMLElement) => {\n const breadcrumbsEl = el as unknown as {\n customClass: string;\n items: IBreadcrumb[];\n size: string;\n };\n breadcrumbsEl.customClass = v['custom-class'] || '';\n breadcrumbsEl.items = v.items;\n breadcrumbsEl.size = v.size ?? 'md';\n },\n });\n customElements.define('breadcrumbs-shadow-host', BreadcrumbsShadowHost);\n }\n\n return html``;\n },\n};\n\nexport const Migration: Story = {\n parameters: {\n docs: {\n description: {\n story: `\n#### Breaking Changes\n\n - The structure of the breadcrumb \\`items\\` has changed from \\`Crumb\\` interface to \\`IBreadcrumb\\` interface.\n - Underlined links are now applied using a custom class rather than a dedicated prop.\n\n#### Prop Mapping\n\n| 1.0 Prop | 2.0 Prop | Notes |\n|-----------------|---------------|-----------------------------------------------------|\n| aria-label | aria-label | |\n| crumbs | items | Interface changed from \\`Crumb\\` to \\`IBreadcrumb\\` |\n| underline-links | | Not carried over, use CSS instead |\n\n#### Event Mapping\n\n| 1.0 Event | 2.0 Event | Notes |\n|------------|-----------------|---------------------------------------------------|\n| crumbClick | breadcrumbClick | Payload changed from \\`Crumb\\` to \\`IBreadcrumb\\` |\n\n#### Interfaces\n\n##### 1.0:\n\\`\\`\\`typescript\ninterface Crumb {\n display: string;\n id: string;\n}\n\\`\\`\\`\n\n##### 2.0:\n\\`\\`\\`typescript\ninterface IBreadcrumb {\n label: string;\n url?: string;\n}\n\\`\\`\\`\n `,\n },\n },\n // To hide the actual story rendering and only show docs:\n controls: { disable: true },\n canvas: { disable: true },\n },\n // Simple render function or leave it empty\n render: () => html`
    `,\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-button-group.json b/mcp/versions/1.5.0/component-docs/modus-wc-button-group.json new file mode 100644 index 000000000..00612f2fd --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-button-group.json @@ -0,0 +1,100 @@ +{ + "description": "A customizable buttongroup component that groups multiple Modus buttons together. The component supports a `` for injecting content within the buttongroup.", + "properties": [ + { + "name": "variant", + "type": "'borderless' | 'filled' | 'outlined'", + "description": "Style variant to apply to all buttons within the button group", + "default": "'outlined'", + "mutable": false, + "end_line": 35 + }, + { + "name": "color", + "type": "'primary' | 'secondary' | 'tertiary' | 'warning' | 'danger'", + "description": "Color to apply to all buttons within the button group", + "default": null, + "mutable": false, + "end_line": 38 + }, + { + "name": "disabled", + "type": "boolean", + "description": "Disables all buttons within the button group", + "default": "false", + "mutable": false, + "end_line": 41 + }, + { + "name": "orientation", + "type": "Orientation", + "description": "Orientation of the button group: horizontal or vertical", + "default": "'horizontal'", + "mutable": false, + "end_line": 44 + }, + { + "name": "selectionType", + "type": "'default' | 'single' | 'multiple'", + "description": "Selection type for button group", + "default": "'default'", + "mutable": false, + "end_line": 47 + } + ], + "events": [ + { + "name": "buttonGroupClick", + "detail": "{ button: HTMLElement; isSelected: boolean; }", + "description": "Event emitted when any button in the group is clicked", + "end_line": 53 + }, + { + "name": "buttonSelectionChange", + "detail": "{ selectedButtons: HTMLElement[]; }", + "description": "Event emitted when button selection changes", + "end_line": 58 + } + ], + "methods": [], + "slots": [ + { + "name": "default", + "description": "Slot for default content" + } + ], + "examples": { + "basic": "\n Button 1\n Button 2\n Button 3\n", + "variations": [], + "args": { + "variant": "'outlined'", + "color": "'primary'", + "disabled": "false", + "orientation": "'horizontal'", + "selection-type": "'default'" + }, + "argTypes": {}, + "usage": [], + "events": [ + "buttonSelectionChange", + "buttonGroupClick" + ] + }, + "tag": "modus-wc-button-group", + "storyExample": { + "template": "\n Button 1\n Button 2\n Button 3\n", + "args": { + "variant": "'outlined'", + "color": "'primary'", + "disabled": "false", + "orientation": "'horizontal'", + "selection-type": "'default'" + }, + "argTypes": {}, + "events": [ + "buttonSelectionChange", + "buttonGroupClick" + ], + "fullContent": "import { withActions } from '@storybook/addon-actions/decorator';\nimport { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\nimport { Orientation } from '../types';\n\ninterface ButtonGroupArgs {\n variant: 'borderless' | 'filled' | 'outlined';\n color: 'primary' | 'secondary' | 'tertiary' | 'warning' | 'danger';\n disabled: boolean;\n orientation: Orientation;\n 'selection-type': 'default' | 'single' | 'multiple';\n}\n\nconst meta: Meta = {\n title: 'Components/Button Group',\n component: 'modus-wc-button-group',\n args: {\n variant: 'outlined',\n color: 'primary',\n disabled: false,\n orientation: 'horizontal',\n 'selection-type': 'default',\n },\n argTypes: {\n variant: {\n control: { type: 'select' },\n options: ['borderless', 'filled', 'outlined'],\n },\n color: {\n control: { type: 'select' },\n options: ['primary', 'secondary', 'tertiary', 'warning', 'danger'],\n },\n disabled: { control: 'boolean' },\n orientation: {\n control: { type: 'select' },\n options: ['horizontal', 'vertical'],\n },\n 'selection-type': {\n control: { type: 'select' },\n options: ['default', 'single', 'multiple'],\n },\n },\n decorators: [withActions],\n parameters: {\n actions: {\n handles: ['buttonSelectionChange', 'buttonGroupClick'],\n },\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nconst Template: Story = {\n render: (args) => {\n // prettier-ignore\n return html`\n\n Button 1\n Button 2\n Button 3\n\n `;\n },\n};\n\nexport const Default: Story = { ...Template };\n\nexport const Vertical: Story = {\n ...Template,\n args: {\n variant: 'outlined',\n orientation: 'vertical',\n },\n};\n\nexport const SplitButton: Story = {\n render: () => {\n // prettier-ignore\n return html`\n\n\n
    \n \n Save Document\n \n
    \n \n
    \n \n
    \n \n \n \n
    \n
    \n
    \n\n
    \n Selected Option: \n \n
    \n
    \n `;\n },\n};\n\nexport const SingleSelection: Story = {\n render: () => {\n // prettier-ignore\n return html`\n
    \n
    \n

    Single Selection (Only one button can be active at a time)

    \n \n Option 1\n Option 2\n Option 3\n Option 4\n \n
    \n \n
    \n

    Vertical Single Selection

    \n \n Option 1\n Option 2\n Option 3\n Option 4\n \n
    \n
    \n `;\n },\n};\n\nexport const MultipleSelection: Story = {\n render: () => {\n // prettier-ignore\n return html`\n
    \n
    \n

    Multiple Selection (Multiple buttons can be active)

    \n \n Bold\n Italic\n Underline\n Strikethrough\n \n
    \n \n
    \n

    Multiple Selection with Icons

    \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    \n
    \n `;\n },\n};\n\nexport const SelectionEvent: Story = {\n render: () => {\n // prettier-ignore\n return html`\n\n\n
    \n
    \n \n Apple\n Banana\n Cherry\n Date\n \n
    \n \n
    \n Selected Button(s): \n \n
    \n\n `;\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n if (!customElements.get('button-group-shadow-host')) {\n const ButtonGroupShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-button-group',\n propsMapper: (v: ButtonGroupArgs, el: HTMLElement) => {\n const bgEl = el as unknown as {\n variant: string;\n color: string;\n disabled: boolean;\n orientation: string;\n selectionType: string;\n };\n bgEl.variant = v.variant;\n bgEl.color = v.color;\n bgEl.disabled = Boolean(v.disabled);\n bgEl.orientation = v.orientation;\n bgEl.selectionType = v['selection-type'];\n if (!el.hasChildNodes()) {\n el.innerHTML = `Button 1Button 2Button 3`;\n }\n },\n });\n customElements.define('button-group-shadow-host', ButtonGroupShadowHost);\n }\n\n return html``;\n },\n};\nexport const Migration: Story = {\n parameters: {\n docs: {\n description: {\n story: `\n#### Breaking Changes\n\n- The \\`size\\` prop has been removed. Button sizes are now controlled individually on each button within the group.\n- Selection state is now managed internally - the \\`selected\\` attribute on individual buttons is no longer used.\n- Shadow DOM has been removed (\\`shadow: false\\`) for better composability.\n\n#### Prop Mapping\n\n| 1.0 Prop | 2.0 Prop | Notes |\n|-----------------|-----------------|--------------------------------------------------------------------------|\n| aria-label | aria-label | Unchanged |\n| aria-disabled | - | Removed. Use \\`disabled\\` prop instead |\n| button-style | variant | Values changed: \\`fill\\` → \\`filled\\`, \\`outline\\` → \\`outlined\\` |\n| color | color | \\`special\\` color removed. Other values unchanged |\n| disabled | disabled | Unchanged |\n| - | orientation | New in 2.0. Controls horizontal/vertical layout |\n| selection-type | selection-type | Values changed: \\`none\\` → \\`default\\`, \\`single\\` and \\`multiple\\` unchanged |\n| size | - | Removed. Control size on individual buttons |\n\n#### Event Mapping\n\n| 1.0 Event | 2.0 Event | Notes |\n|------------------------|------------------------|------------------------------------------------------------|\n| buttonGroupClick | buttonGroupClick | Event detail changed: now includes \\`{ button, isSelected }\\` |\n| buttonSelectionChange | buttonSelectionChange | Returns array of selected buttons |\n `,\n },\n },\n controls: { disable: true },\n canvas: { disable: true },\n },\n render: () => html`
    `,\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-button.json b/mcp/versions/1.5.0/component-docs/modus-wc-button.json new file mode 100644 index 000000000..6860a04f7 --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-button.json @@ -0,0 +1,130 @@ +{ + "description": "A customizable button component used to create buttons with different sizes, variants, and types. The component supports a `` for injecting content within the button, similar to a native HTML button.", + "properties": [ + { + "name": "color", + "type": "'primary' | 'secondary' | 'tertiary' | 'warning' | 'danger'", + "description": "The color variant of the button.", + "default": "'primary'", + "mutable": false, + "end_line": 33 + }, + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the button element.", + "default": "''", + "mutable": false, + "end_line": 36 + }, + { + "name": "disabled", + "type": "boolean", + "description": "If true, the button will be disabled.", + "default": "false", + "mutable": false, + "end_line": 39 + }, + { + "name": "fullWidth", + "type": "boolean", + "description": "If true, the button will take the full width of its container.", + "default": "false", + "mutable": false, + "end_line": 42 + }, + { + "name": "pressed", + "type": "boolean", + "description": "If true, the button will be in a pressed state (for toggle buttons).", + "default": "false", + "mutable": false, + "end_line": 45 + }, + { + "name": "shape", + "type": "'circle' | 'ellipse' | 'rectangle' | 'square'", + "description": "The shape of the button.", + "default": "'rectangle'", + "mutable": false, + "end_line": 48 + }, + { + "name": "size", + "type": "DaisySize | 'xl'", + "description": "The size of the button.", + "default": "'md'", + "mutable": false, + "end_line": 51 + }, + { + "name": "type", + "type": "'button' | 'submit' | 'reset'", + "description": "The type of the button.", + "default": "'button'", + "mutable": false, + "end_line": 54 + }, + { + "name": "variant", + "type": "'borderless' | 'filled' | 'outlined'", + "description": "The variant of the button.", + "default": "'filled'", + "mutable": false, + "end_line": 57 + } + ], + "events": [ + { + "name": "buttonClick", + "detail": "MouseEvent | KeyboardEvent", + "description": "Event emitted when the button is clicked or activated via keyboard.", + "end_line": 60 + } + ], + "methods": [], + "slots": [ + { + "name": "default", + "description": "Slot for default content" + } + ], + "examples": { + "basic": "\n Click me\n", + "variations": [], + "args": { + "color": "'primary'", + "disabled": "false", + "full-width": "false", + "pressed": "false", + "shape": "'rectangle'", + "size": "'md'", + "type": "'button'", + "variant": "'filled'" + }, + "argTypes": {}, + "usage": [], + "events": [ + "buttonClick" + ] + }, + "tag": "modus-wc-button", + "storyExample": { + "template": "\n Click me\n", + "args": { + "color": "'primary'", + "disabled": "false", + "full-width": "false", + "pressed": "false", + "shape": "'rectangle'", + "size": "'md'", + "type": "'button'", + "variant": "'filled'" + }, + "argTypes": {}, + "events": [ + "buttonClick" + ], + "fullContent": "import { withActions } from '@storybook/addon-actions/decorator';\nimport { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\nimport { DaisySize } from '../types';\n\ninterface ButtonArgs {\n color: 'primary' | 'secondary' | 'tertiary' | 'warning' | 'danger';\n 'custom-class'?: string;\n disabled: boolean;\n 'full-width': boolean;\n pressed: boolean;\n shape: 'circle' | 'ellipse' | 'rectangle' | 'square';\n size: DaisySize | 'xl';\n type: 'button' | 'submit' | 'reset';\n variant: 'borderless' | 'filled' | 'outlined';\n}\n\nconst meta: Meta = {\n title: 'Components/Button',\n component: 'modus-wc-button',\n args: {\n color: 'primary',\n disabled: false,\n 'full-width': false,\n pressed: false,\n shape: 'rectangle',\n size: 'md',\n type: 'button',\n variant: 'filled',\n },\n argTypes: {\n color: {\n control: { type: 'select' },\n options: ['primary', 'secondary', 'tertiary', 'warning', 'danger'],\n },\n shape: {\n control: { type: 'select' },\n options: ['circle', 'ellipse', 'rectangle', 'square'],\n },\n size: {\n control: { type: 'select' },\n options: ['xs', 'sm', 'md', 'lg', 'xl'],\n },\n type: {\n control: { type: 'select' },\n options: ['button', 'submit', 'reset'],\n },\n variant: {\n control: { type: 'select' },\n options: ['borderless', 'filled', 'outlined'],\n },\n },\n decorators: [withActions],\n parameters: {\n actions: {\n handles: ['buttonClick'],\n },\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nconst Template: Story = {\n render: (args) => {\n // prettier-ignore\n return html`\n\n Click me\n\n `;\n },\n};\n\nexport const Default: Story = {\n ...Template,\n};\n\nexport const ButtonShapes: Story = {\n render: () => {\n // prettier-ignore\n return html`\n \n Rectangle\n \n\n Circle\n\n\n Square\n\n\n Ellipse\n\n `;\n },\n};\n\nexport const DynamicTextUpdate: Story = {\n render: () => {\n const updateButtonText = () => {\n const btnText = document.getElementById('btn-text') as HTMLSpanElement;\n const input = document.getElementById(\n 'btn-text-input'\n ) as HTMLInputElement;\n\n btnText.textContent = input.value;\n };\n\n // prettier-ignore\n return html`\n\n\n
    \n \n Press button to update content\n \n \n\n
    \n \n
    \n
    \n `;\n },\n};\nexport const IconOnlyButton: Story = {\n render: () => {\n // prettier-ignore\n return html`\n\n \n\n `;\n },\n};\n\nexport const IconLeftButton: Story = {\n render: () => {\n // prettier-ignore\n return html`\n\n \n Download\n\n `;\n },\n};\n\nexport const IconRightButton: Story = {\n render: () => {\n // prettier-ignore\n return html`\n\n Details\n \n\n `;\n },\n};\n\nexport const IconLeftAndRightButton: Story = {\n render: () => {\n // prettier-ignore\n return html`\n\n \n Checkout\n \n\n `;\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n // Create a unique shadow host for button component\n if (!customElements.get('button-shadow-host')) {\n const ButtonShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-button',\n propsMapper: (v: ButtonArgs, el: HTMLElement) => {\n const buttonEl = el as unknown as {\n ariaLabel: string;\n color: string;\n shape: string;\n size: string;\n type: string;\n variant: string;\n customClass: string;\n disabled: boolean;\n fullWidth: boolean;\n pressed: boolean;\n };\n buttonEl.ariaLabel = 'Click me button';\n buttonEl.color = v.color;\n buttonEl.shape = v.shape;\n buttonEl.size = v.size;\n buttonEl.type = v.type;\n buttonEl.variant = v.variant;\n buttonEl.customClass = v['custom-class'] || '';\n buttonEl.disabled = Boolean(v.disabled);\n buttonEl.fullWidth = Boolean(v['full-width']);\n buttonEl.pressed = Boolean(v.pressed);\n // DO NOT set textContent - it destroys the component's internal structure!\n // Button content should be set via defaultContent in the helper config\n },\n defaultContent: 'Click me', // Set content here instead\n });\n customElements.define('button-shadow-host', ButtonShadowHost);\n }\n\n return html``;\n },\n};\nexport const Migration: Story = {\n parameters: {\n docs: {\n description: {\n story: `\n#### Breaking Changes\n\n - In 1.0 buttons had specific properties for adding icons (\\`icon-only\\`, \\`left-icon\\`, \\`right-icon\\`). In 2.0, icons are added via slots using the \\`modus-wc-icon\\` component.\n - The \\`button-style\\` property has been renamed to \\`variant\\` with similar options.\n - Size values have changed from verbose names (\\`small\\`, \\`medium\\`, \\`large\\`) to abbreviations (\\`sm\\`, \\`md\\`, \\`lg\\`).\n\n#### Prop Mapping\n\n| 1.0 Prop | 2.0 Prop | Notes |\n|-----------------|------------|-------------------------------------------------------------|\n| aria-label | aria-label | |\n| button-style | variant | \\`fill\\` → \\`filled\\`, \\`outline\\` → \\`outlined\\` |\n| color | color | \\`dark\\` and \\`special\\` removed, \\`warning\\` added |\n| critical-action | | Not carried over |\n| disabled | disabled | |\n| icon-only | | Not carried over, use \\`icon\\` slot |\n| left-icon | | Not carried over, use \\`icon\\` slot |\n| right-icon | | Not carried over, use \\`icon\\` slot |\n| show-caret | | Not carried over |\n| size | size | \\`small\\` → \\`sm\\`, \\`medium\\` → \\`md\\`, \\`large\\` → \\`lg\\` |\n| type | type | |\n\n#### Event Mapping\n\n| 1.0 Event | 2.0 Event | Notes |\n|--------------|--------------|------------------|\n| buttonClick | buttonClick | |\n `,\n },\n },\n controls: { disable: true },\n canvas: { disable: true },\n },\n render: () => html`
    `,\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-card.json b/mcp/versions/1.5.0/component-docs/modus-wc-card.json new file mode 100644 index 000000000..2a29c980a --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-card.json @@ -0,0 +1,98 @@ +{ + "description": "A customizable card component used to group and display content in a way that is easily readable. This component supports multiple `` elements including 'header' for images or custom content, 'title', 'subtitle', a default slot for main content, 'actions' for buttons or interactive elements, and 'footer'.", + "properties": [ + { + "name": "backgroundFigure", + "type": "boolean", + "description": "Makes any \\
    in the 'header' slot cover the background", + "default": "false", + "mutable": false, + "end_line": 22 + }, + { + "name": "bordered", + "type": "boolean", + "description": "Adds a hard border to the card", + "default": "false", + "mutable": false, + "end_line": 25 + }, + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply", + "default": "''", + "mutable": false, + "end_line": 28 + }, + { + "name": "layout", + "type": "'vertical' | 'horizontal'", + "description": "Determines how the card is laid out", + "default": "'vertical'", + "mutable": false, + "end_line": 31 + }, + { + "name": "padding", + "type": "'compact' | 'comfortable'", + "description": "Determines the interior padding size", + "default": "'compact'", + "mutable": false, + "end_line": 34 + } + ], + "events": [], + "methods": [], + "slots": [ + { + "name": "header", + "description": "Slot for header content" + }, + { + "name": "title", + "description": "Slot for title content" + }, + { + "name": "subtitle", + "description": "Slot for subtitle content" + }, + { + "name": "default", + "description": "Slot for default content" + }, + { + "name": "actions", + "description": "Slot for actions content" + }, + { + "name": "footer", + "description": "Slot for footer content" + } + ], + "examples": { + "basic": "\n\n Card Title\n Card Subtitle\n

    This is a sample card content. You can place any content here.

    \n
    \n Click me\n
    \n", + "variations": [], + "args": { + "background-figure": "false", + "bordered": "false", + "layout": "'vertical'", + "padding": "'compact'" + }, + "argTypes": {}, + "usage": [] + }, + "tag": "modus-wc-card", + "storyExample": { + "template": "\n\n Card Title\n Card Subtitle\n

    This is a sample card content. You can place any content here.

    \n
    \n Click me\n
    \n", + "args": { + "background-figure": "false", + "bordered": "false", + "layout": "'vertical'", + "padding": "'compact'" + }, + "argTypes": {}, + "events": [], + "fullContent": "import { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\n\ninterface CardArgs {\n 'background-figure'?: boolean;\n bordered?: boolean;\n 'custom-class'?: string;\n layout?: 'vertical' | 'horizontal';\n padding?: 'compact' | 'comfortable';\n}\n\nconst meta: Meta = {\n title: 'Components/Card',\n component: 'modus-wc-card',\n args: {\n 'background-figure': false,\n bordered: false,\n layout: 'vertical',\n padding: 'compact',\n },\n argTypes: {\n layout: {\n control: { type: 'select' },\n options: ['vertical', 'horizontal'],\n },\n padding: {\n control: { type: 'select' },\n options: ['compact', 'comfortable'],\n },\n },\n parameters: {\n layout: 'padded',\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nconst Template: Story = {\n render: (args) => {\n // prettier-ignore\n return html`\n\n\n Card Title\n Card Subtitle\n

    This is a sample card content. You can place any content here.

    \n
    \n Click me\n
    \n\n `;\n },\n};\n\nexport const Default: Story = {\n ...Template,\n};\n\nexport const SimpleCard: Story = {\n ...Template,\n // prettier-ignore\n render: () => html`\n\n Raw card content.\n\n `,\n};\n\nexport const SlotsLayout: Story = {\n ...Template,\n // prettier-ignore\n render: (args) => html`\n\n\n
    Header Slot
    \n
    Title Slot
    \n
    Subtitle Slot
    \n
    Default (Body) Slot
    \n
    Actions Slot
    \n
    Footer Slot
    \n\n `,\n};\n\nexport const ComplexCard: Story = {\n ...Template,\n // prettier-ignore\n render: (args) => html`\n\n\n
    \n \n
    \n Complex Card\n With Shadow\n

    \n This is a more of a traditional Card, featuring a header image, content,\n multiple buttons, and a larger shadow that appears on hover.\n

    \n
    \n Action 1\n Action 2\n
    \n\n `,\n};\n\nexport const HorizontalImage: Story = {\n ...Template,\n // prettier-ignore\n render: (args) => html`\n\n
    \n \"Horizontal\n
    \n

    This card uses a horizontal layout.

    \n\n `,\n};\n\nexport const BackgroundFigureImage: Story = {\n ...Template,\n // prettier-ignore\n render: (args) => html`\n\n
    \n \"Full\n
    \n Full Image Card\n

    This card has a figure image in the background.

    \n\n `,\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n if (!customElements.get('card-shadow-host')) {\n const CardShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-card',\n propsMapper: (v: CardArgs, el: HTMLElement) => {\n const cardEl = el as unknown as {\n backgroundFigure: boolean;\n bordered: boolean;\n customClass: string;\n layout: string;\n padding: string;\n };\n cardEl.backgroundFigure = Boolean(v['background-figure']);\n cardEl.bordered = Boolean(v.bordered);\n cardEl.customClass = v['custom-class'] || '';\n cardEl.layout = v.layout ?? 'vertical';\n cardEl.padding = v.padding ?? 'compact';\n if (!el.hasChildNodes()) {\n el.innerHTML =\n 'Card TitleCard Subtitle

    This is a sample card content. You can place any content here.

    Click me
    ';\n }\n },\n });\n customElements.define('card-shadow-host', CardShadowHost);\n }\n\n return html` \n `;\n },\n};\nexport const Migration: Story = {\n parameters: {\n docs: {\n description: {\n story: `\n#### Breaking Changes\n\n - In 1.0, card dimensions were controlled via direct props. In 2.0, styling should be handled through CSS.\n - Shadow effects on hover are now controlled via CSS rather than props.\n - The card component in 2.0 focuses on layout and structure rather than specific styling.\n\n#### Prop Mapping\n\n| 1.0 Prop | 2.0 Prop | Notes |\n|----------------------|---------------------|--------------------------------------|\n| border-radius | | Not carried over, use CSS instead |\n| height | | Not carried over, use CSS instead |\n| show-card-border | bordered | |\n| show-shadow-on-hover | | Not carried over, use CSS instead |\n| width | | Not carried over, use CSS instead |\n `,\n },\n },\n // To hide the actual story rendering and only show docs:\n controls: { disable: true },\n canvas: { disable: true },\n },\n // Simple render function or leave it empty\n render: () => html`
    `,\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-checkbox.json b/mcp/versions/1.5.0/component-docs/modus-wc-checkbox.json new file mode 100644 index 000000000..e6a413722 --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-checkbox.json @@ -0,0 +1,149 @@ +{ + "description": "A customizable checkbox component", + "properties": [ + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the inner div.", + "default": "''", + "mutable": false, + "end_line": 29 + }, + { + "name": "disabled", + "type": "boolean", + "description": "The disabled state of the checkbox.", + "default": "false", + "mutable": false, + "end_line": 32 + }, + { + "name": "indeterminate", + "type": "boolean", + "description": "The indeterminate state of the checkbox.", + "default": "false", + "mutable": true, + "end_line": 35 + }, + { + "name": "inputId", + "type": "string", + "description": "The ID of the input element.", + "default": null, + "mutable": false, + "end_line": 38 + }, + { + "name": "inputTabIndex", + "type": "number", + "description": "The tabindex of the input.", + "default": null, + "mutable": false, + "end_line": 41 + }, + { + "name": "label", + "type": "string", + "description": "The text to display within the label.", + "default": null, + "mutable": false, + "end_line": 44 + }, + { + "name": "name", + "type": "string", + "description": "Name of the form control. Submitted with the form as part of a name/value pair.", + "default": "''", + "mutable": false, + "end_line": 47 + }, + { + "name": "required", + "type": "boolean", + "description": "A value is required for the form to be submittable.", + "default": "false", + "mutable": false, + "end_line": 50 + }, + { + "name": "size", + "type": "ModusSize", + "description": "The size of the input.", + "default": "'md'", + "mutable": false, + "end_line": 53 + }, + { + "name": "value", + "type": "boolean", + "description": "The value of the checkbox.", + "default": "false", + "mutable": true, + "end_line": 56 + } + ], + "events": [ + { + "name": "inputBlur", + "detail": "FocusEvent", + "description": "Emitted when the input loses focus.", + "end_line": 59 + }, + { + "name": "inputChange", + "detail": "InputEvent", + "description": "Emitted when the input value changes.", + "end_line": 62 + }, + { + "name": "inputFocus", + "detail": "FocusEvent", + "description": "Emitted when the input gains focus.", + "end_line": 65 + } + ], + "methods": [], + "slots": [], + "examples": { + "basic": "", + "variations": [], + "args": { + "custom-class": "''", + "disabled": "false", + "indeterminate": "false", + "label": "'Label'", + "name": "''", + "required": "false", + "size": "'md'", + "value": "true" + }, + "argTypes": {}, + "usage": [], + "events": [ + "inputBlur", + "inputChange", + "inputFocus" + ] + }, + "tag": "modus-wc-checkbox", + "storyExample": { + "template": "", + "args": { + "custom-class": "''", + "disabled": "false", + "indeterminate": "false", + "label": "'Label'", + "name": "''", + "required": "false", + "size": "'md'", + "value": "true" + }, + "argTypes": {}, + "events": [ + "inputBlur", + "inputChange", + "inputFocus" + ], + "fullContent": "import { withActions } from '@storybook/addon-actions/decorator';\nimport { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\nimport { ModusSize } from '../types';\n\ninterface CheckboxArgs {\n 'custom-class'?: string;\n disabled?: boolean;\n indeterminate?: boolean;\n 'input-id'?: string;\n 'input-tab-index'?: number;\n label?: string;\n name?: string;\n required?: boolean;\n size?: ModusSize;\n value: boolean;\n}\n\nconst meta: Meta = {\n title: 'Components/Forms/Checkbox',\n component: 'modus-wc-checkbox',\n args: {\n 'custom-class': '',\n disabled: false,\n indeterminate: false,\n label: 'Label',\n name: '',\n required: false,\n size: 'md',\n value: true,\n },\n argTypes: {\n size: {\n control: { type: 'select' },\n options: ['sm', 'md', 'lg'],\n },\n },\n decorators: [withActions],\n parameters: {\n actions: {\n handles: ['inputBlur', 'inputChange', 'inputFocus'],\n },\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nexport const Default: Story = {\n render: (args) => {\n return html`\n \n `;\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n // Create a unique shadow host for checkbox component\n if (!customElements.get('checkbox-shadow-host')) {\n const CheckboxShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-checkbox',\n propsMapper: (v: CheckboxArgs, el: HTMLElement) => {\n const checkboxEl = el as unknown as {\n customClass: string;\n disabled: boolean;\n indeterminate: boolean;\n inputId: string;\n inputTabIndex: number;\n label: string;\n name: string;\n required: boolean;\n size: string;\n value: boolean;\n };\n checkboxEl.customClass = v['custom-class'] || '';\n checkboxEl.disabled = Boolean(v.disabled);\n checkboxEl.indeterminate = Boolean(v.indeterminate);\n checkboxEl.inputId = v['input-id'] ?? '';\n checkboxEl.inputTabIndex = v['input-tab-index'] ?? 0;\n checkboxEl.label = v.label ?? '';\n checkboxEl.name = v.name ?? '';\n checkboxEl.required = Boolean(v.required);\n checkboxEl.size = v.size ?? '';\n checkboxEl.value = Boolean(v.value);\n },\n });\n customElements.define('checkbox-shadow-host', CheckboxShadowHost);\n }\n\n return html``;\n },\n};\nexport const Migration: Story = {\n parameters: {\n docs: {\n description: {\n story: `\n#### Breaking Changes\n\n - In 1.0 input state was maintained by the component. 2.0 components encourage users to follow a controlled\n input model. See the Form Inputs [documentation]([Angular](?path=/docs/documentation-form-inputs--docs) for\n additional info and examples.\n - The \\`checked\\` prop is now \\`value\\` in 2.0.\n - The \\`checkboxClick\\` event is now \\`inputChange\\` in 2.0.\n - Size values have changed from verbose names (\\`small\\`, \\`medium\\`) to abbreviations (\\`sm\\`, \\`md\\`, \\`lg\\`).\n\n#### Prop Mapping\n\n| 1.0 Prop | 2.0 Prop | Notes |\n|------------------|---------------|-----------------------------------------|\n| aria-label | aria-label | |\n| checked | value | |\n| disabled | disabled | |\n| indeterminate | indeterminate | |\n| label | label | |\n| size | size | \\`small\\` → \\`sm\\`, \\`medium\\` → \\`md\\` |\n| stop-propagation | | Not carried over |\n\n#### Event Mapping\n\n| 1.0 Event | 2.0 Event | Notes |\n|---------------|-------------|-------------------------------------------------------|\n| checkboxClick | inputChange | Event now emits \\`InputEvent\\` instead of \\`boolean\\` |\n `,\n },\n },\n controls: { disable: true },\n canvas: { disable: true },\n },\n render: () => html`
    `,\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-chip.json b/mcp/versions/1.5.0/component-docs/modus-wc-chip.json new file mode 100644 index 000000000..2e2051c05 --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-chip.json @@ -0,0 +1,132 @@ +{ + "description": "A customizable chip component used to display information in a compact area The component supports a `` for injecting custom content such as avatar and icons.", + "properties": [ + { + "name": "active", + "type": "boolean", + "description": "Active state of chip.", + "default": "false", + "mutable": false, + "end_line": 31 + }, + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the inner div.", + "default": "''", + "mutable": false, + "end_line": 34 + }, + { + "name": "disabled", + "type": "boolean", + "description": "Whether the chip is disabled.", + "default": "false", + "mutable": false, + "end_line": 37 + }, + { + "name": "hasError", + "type": "boolean", + "description": "Whether the chip has an error.", + "default": "false", + "mutable": false, + "end_line": 40 + }, + { + "name": "label", + "type": "string", + "description": "The label to display in the chip.", + "default": "''", + "mutable": false, + "end_line": 43 + }, + { + "name": "showRemove", + "type": "boolean", + "description": "Whether to show the close icon on right side of the chip.", + "default": "false", + "mutable": false, + "end_line": 46 + }, + { + "name": "shape", + "type": "'rectangle' | 'circle'", + "description": "The shape of the chip: 'rectangle' (default) or 'circle'.", + "default": "'rectangle'", + "mutable": false, + "end_line": 49 + }, + { + "name": "size", + "type": "ModusSize", + "description": "The size of the chip.", + "default": "'md'", + "mutable": false, + "end_line": 52 + }, + { + "name": "variant", + "type": "'filled' | 'outline'", + "description": "The variant of the chip.", + "default": "'filled'", + "mutable": false, + "end_line": 55 + } + ], + "events": [ + { + "name": "chipClick", + "detail": "MouseEvent | KeyboardEvent", + "description": "Event emitted when the chip is clicked or activated via keyboard.", + "end_line": 58 + }, + { + "name": "chipRemove", + "detail": "MouseEvent | KeyboardEvent", + "description": "Event emitted when the close chip icon button is clicked.", + "end_line": 61 + } + ], + "methods": [], + "slots": [ + { + "name": "default", + "description": "Slot for default content" + } + ], + "examples": { + "basic": "", + "variations": [], + "args": { + "label": "'Chip'", + "show-remove": "true", + "shape": "'rectangle'", + "size": "'md'", + "variant": "'filled'" + }, + "argTypes": {}, + "usage": [], + "events": [ + "chipClick", + "chipRemove" + ] + }, + "tag": "modus-wc-chip", + "storyExample": { + "template": "", + "args": { + "label": "'Chip'", + "show-remove": "true", + "shape": "'rectangle'", + "size": "'md'", + "variant": "'filled'" + }, + "argTypes": {}, + "events": [ + "chipClick", + "chipRemove" + ], + "fullContent": "import { withActions } from '@storybook/addon-actions/decorator';\nimport { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\nimport { ModusSize } from '../types';\n\ninterface ChipArgs {\n active?: boolean;\n 'custom-class'?: string;\n disabled?: boolean;\n 'has-error'?: boolean;\n label: string;\n shape?: 'rectangle' | 'circle';\n 'show-remove'?: boolean;\n size: ModusSize;\n variant: 'filled' | 'outline';\n}\n\nconst meta: Meta = {\n title: 'Components/Chip',\n component: 'modus-wc-chip',\n args: {\n label: 'Chip',\n 'show-remove': true,\n shape: 'rectangle',\n size: 'md',\n variant: 'filled',\n },\n argTypes: {\n shape: {\n control: { type: 'select' },\n options: ['rectangle', 'circle'],\n },\n size: {\n control: { type: 'select' },\n options: ['sm', 'md', 'lg'],\n },\n variant: {\n control: { type: 'select' },\n options: ['filled', 'outline'],\n },\n },\n decorators: [withActions],\n parameters: {\n actions: {\n handles: ['chipClick', 'chipRemove'],\n },\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nconst Template: Story = {\n render: (args) => {\n return html` `;\n },\n};\n\nexport const Default: Story = { ...Template };\n\nexport const AvatarChip: Story = {\n render: (args) => {\n // prettier-ignore\n return html`\n\n \n\n `;\n },\n};\n\nexport const CheckIconChip: Story = {\n render: (args) => {\n // prettier-ignore\n return html`\n\n \n\n `;\n },\n};\n\n// prettier-ignore\nexport const Composable: Story = {\n render: (args) => {\n return html`\n\n \n Chip\n\n\n\n \n Chip\n\n\n\n \n Chip\n \n\n `;\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n // Create a unique shadow host for chip component\n if (!customElements.get('chip-shadow-host')) {\n const ChipShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-chip',\n propsMapper: (v: ChipArgs, el: HTMLElement) => {\n const chipEl = el as unknown as {\n ariaLabel: string;\n active: boolean;\n customClass: string;\n disabled: boolean;\n hasError: boolean;\n label: string;\n shape: string;\n showRemove: boolean;\n size: string;\n variant: string;\n };\n chipEl.ariaLabel = 'Click me chip';\n chipEl.active = Boolean(v.active);\n chipEl.shape = v.shape || 'rectangle';\n chipEl.size = v.size;\n chipEl.variant = v.variant;\n chipEl.customClass = v['custom-class'] || '';\n chipEl.disabled = Boolean(v.disabled);\n chipEl.hasError = Boolean(v['has-error']);\n chipEl.label = v.label;\n chipEl.showRemove = Boolean(v['show-remove']);\n },\n });\n customElements.define('chip-shadow-host', ChipShadowHost);\n }\n\n return html``;\n },\n};\nexport const Migration: Story = {\n parameters: {\n docs: {\n description: {\n story: `\n#### Breaking Changes\n\n - \\`chip-style\\` prop has been renamed to \\`variant\\` and values changed from \\`solid\\` to \\`filled\\`.\n - \\`closeClick\\` event has been renamed to \\`chipRemove\\`.\n - \\`show-close\\` prop has been renamed to \\`show-remove\\`.\n - Size values have changed from verbose names (\\`medium\\`, \\`small\\`) to abbreviations (\\`md\\`, \\`sm\\`).\n\n#### Prop Mapping\n\n| 1.0 Prop | 2.0 Prop | Notes |\n|----------------|-------------|---------------------------------------------------|\n| active | active | |\n| advanced-chip | | Not carried over |\n| aria-label | aria-label | |\n| chip-id | | Not carried over |\n| chip-style | variant | \\`solid\\` → \\`filled\\`, \\`outline\\` → \\`outline\\` |\n| disabled | disabled | |\n| has-error | has-error | |\n| image-url | | Not carried over, use slot instead |\n| max-width | | Not carried over, use CSS instead |\n| show-checkmark | | Not carried over, use slot instead |\n| show-close | show-remove | |\n| size | size | \\`medium\\` → \\`md\\`, \\`small\\` → \\`sm\\` |\n| value | label | |\n| | shape | New in 2.0: \\`rectangle\\` (default), \\`circle\\` |\n\n#### Event Mapping\n\n| 1.0 Event | 2.0 Event | Notes |\n|-------------|-------------|-------|\n| chipClick | chipClick | |\n| closeClick | chipRemove | |\n `,\n },\n },\n controls: { disable: true },\n canvas: { disable: true },\n },\n render: () => html`
    `,\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-collapse.json b/mcp/versions/1.5.0/component-docs/modus-wc-collapse.json new file mode 100644 index 000000000..748cfd3fb --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-collapse.json @@ -0,0 +1,100 @@ +{ + "description": "A customizable collapse component used for showing and hiding content. The component supports a 'header' and 'content' `` for injecting custom HTML.", + "properties": [ + { + "name": "bordered", + "type": "boolean", + "description": "When true, renders a border-bottom on the collapse component.", + "default": "false", + "mutable": false, + "end_line": 59 + }, + { + "name": "chevronPosition", + "type": "'left' | 'right'", + "description": "Controls chevron placement.", + "default": "'right'", + "mutable": false, + "end_line": 62 + }, + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the outer div.", + "default": "''", + "mutable": false, + "end_line": 65 + }, + { + "name": "expanded", + "type": "boolean", + "description": "Controls whether the collapse is expanded or not.", + "default": "false", + "mutable": true, + "end_line": 68 + }, + { + "name": "collapseId", + "type": "string", + "description": "A unique identifier used to set the id attributes of various elements.", + "default": null, + "mutable": true, + "end_line": 71 + }, + { + "name": "options", + "type": "ICollapseOptions", + "description": "Configuration options for rendering the pre-laid out collapse component. Do not set this prop if you intend to use the 'header' slot.", + "default": null, + "mutable": false, + "end_line": 77 + } + ], + "events": [ + { + "name": "expandedChange", + "detail": "{ expanded: boolean }", + "description": "Event emitted when the expanded prop is internally changed.", + "end_line": 80 + } + ], + "methods": [], + "slots": [ + { + "name": "header", + "description": "Slot for header content" + }, + { + "name": "content", + "description": "Slot for content content" + } + ], + "examples": { + "basic": "\n
    Collapse content
    \n\n", + "variations": [], + "args": { + "bordered": "false", + "chevron-position": "'right'", + "expanded": "false" + }, + "argTypes": {}, + "usage": [], + "events": [ + "expandedChange" + ] + }, + "tag": "modus-wc-collapse", + "storyExample": { + "template": "\n
    Collapse content
    \n\n", + "args": { + "bordered": "false", + "chevron-position": "'right'", + "expanded": "false" + }, + "argTypes": {}, + "events": [ + "expandedChange" + ], + "fullContent": "import { withActions } from '@storybook/addon-actions/decorator';\nimport { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { ICollapseOptions } from './modus-wc-collapse';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\n\ninterface CollapseArgs {\n bordered?: boolean;\n 'chevron-position'?: 'left' | 'right';\n 'custom-class'?: string;\n expanded?: boolean;\n id?: string;\n options?: ICollapseOptions;\n}\n\nconst options: ICollapseOptions = {\n title: 'Collapse Title',\n description: 'Collapse description',\n icon: 'alert',\n iconAriaLabel: 'Alert',\n};\n\nconst optionsWithStartIcon: ICollapseOptions = {\n title: 'Collapse Title',\n description: 'Collapse description',\n endIcon: 'more_vertical',\n endIconAriaLabel: 'More actions',\n icon: 'alert',\n iconAriaLabel: 'Alert',\n startIcon: 'alert',\n startIconAriaLabel: 'alert',\n};\n\nconst meta: Meta = {\n title: 'Components/Collapse',\n component: 'modus-wc-collapse',\n args: {\n bordered: false,\n 'chevron-position': 'right',\n expanded: false,\n options,\n },\n argTypes: {\n 'chevron-position': {\n control: { type: 'select' },\n options: ['left', 'right'],\n },\n options: {\n description: 'Configuration options for the collapse component',\n table: {\n type: {\n detail: `\n Interface: ICollapseOptions\n Properties:\n - description (string, optional): The description to render in the collapse header\n - endIcon (string, optional): The Modus icon name to render at the end of the header\n - endIconAriaLabel (string, optional): The end icon's aria-label\n - icon (string, optional): The Modus icon name to render in the collapse header\n - iconAriaLabel (string, optional): The icon's aria-label\n - startIcon (string, optional): The Modus icon name to render first, before the chevron\n - startIconAriaLabel (string, optional): The start icon's aria-label\n - size (DaisySize, optional): The size of the collapse header\n - title (string): The title to render in the collapse header\n `,\n },\n },\n },\n },\n decorators: [withActions],\n parameters: { actions: { handles: ['expandedChange'] }, layout: 'padded' },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nconst Template: Story = {\n render: (args) => {\n // prettier-ignore\n return html`\n\n
    Collapse content
    \n\n\n `;\n },\n};\n\nexport const Default: Story = { ...Template };\n\nexport const WithStartAndEndIcons: Story = {\n render: (args) => {\n // prettier-ignore\n return html`\n\n
    Collapse content
    \n\n\n `;\n },\n args: {\n bordered: false,\n 'chevron-position': 'left',\n expanded: false,\n options: optionsWithStartIcon,\n },\n};\n\nexport const WithCustomClickableHeader = {\n render: (args) => {\n const handleButtonClick = () => {\n window.alert('Button was clicked!');\n };\n\n // prettier-ignore\n return html`\n\n\n
    \n
    \n Alert 1\n Alert 2\n
    \n
    \n
    Collapse content
    \n\n\n `;\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n // Create a unique shadow host for collapse component\n if (!customElements.get('collapse-shadow-host')) {\n const CollapseShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-collapse',\n propsMapper: (v: CollapseArgs, el: HTMLElement) => {\n const collapseEl = el as unknown as {\n bordered: boolean;\n chevronPosition: 'left' | 'right';\n customClass: string;\n expanded: boolean;\n id: string;\n options: ICollapseOptions;\n };\n // Only set innerHTML once on initial creation\n if (!el.querySelector('[slot=\"content\"]')) {\n el.innerHTML = '
    Collapse content
    ';\n }\n collapseEl.bordered = Boolean(v.bordered);\n collapseEl.chevronPosition = v['chevron-position'] ?? 'right';\n collapseEl.customClass = v['custom-class'] || '';\n collapseEl.expanded = Boolean(v.expanded);\n collapseEl.id = v.id ?? '';\n if (v.options) {\n collapseEl.options = v.options; // Conditional assignment only if provided\n }\n },\n });\n customElements.define('collapse-shadow-host', CollapseShadowHost);\n }\n\n return html``;\n },\n};\nexport const Migration: Story = {\n parameters: {\n docs: {\n description: {\n story: `\n#### Breaking Changes\n\n - The 1.0 accordion-item component maps to the 2.0 collapse component. See the [Accordion component](?path=/docs/components-accordion--docs).\n - Size values have changed from \\`condensed\\`, \\`standard\\` in 1.0 to abbreviations (\\`xs\\`, \\`sm\\`, \\`md\\`, \\`lg\\`) in 2.0.\n\n#### Prop Mapping\n\n##### accordion-item (1.0) → collapse (2.0)\n\n| 1.0 Prop | 2.0 Prop | Notes |\n|--------------------|---------------------|------------------|\n| aria-label | aria-label | |\n| disabled | | Not carried over |\n| expand-button-type | | Not carried over |\n| expanded | expanded | |\n| header-text | options.title | |\n| icon | options.icon | |\n| size | options.size | |\n| supporting-label | options.description | |\n\n#### Event Mapping\n\n| 1.0 Event | 2.0 Event | Notes |\n|-----------|----------------|------------------|\n| closed | expandedChange | |\n| opened | expandedChange | |\n `,\n },\n },\n controls: { disable: true },\n canvas: { disable: true },\n },\n render: () => html`
    `,\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-date.json b/mcp/versions/1.5.0/component-docs/modus-wc-date.json new file mode 100644 index 000000000..c81205034 --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-date.json @@ -0,0 +1,227 @@ +{ + "description": "A customizable date picker component used to create date inputs. Adheres to WCAG 2.2 standards.", + "properties": [ + { + "name": "bordered", + "type": "boolean", + "description": "Indicates that the input should have a border.", + "default": "true", + "mutable": false, + "end_line": 79 + }, + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the input.", + "default": "''", + "mutable": false, + "end_line": 82 + }, + { + "name": "disabled", + "type": "boolean", + "description": "Whether the form control is disabled.", + "default": "false", + "mutable": false, + "end_line": 85 + }, + { + "name": "feedback", + "type": "IInputFeedbackProp", + "description": "Feedback to render below the input.", + "default": null, + "mutable": false, + "end_line": 88 + }, + { + "name": "inputId", + "type": "string", + "description": "The ID of the input element.", + "default": null, + "mutable": false, + "end_line": 91 + }, + { + "name": "inputTabIndex", + "type": "number", + "description": "Determine the control's relative ordering for sequential focus navigation (typically with the Tab key).", + "default": null, + "mutable": false, + "end_line": 94 + }, + { + "name": "label", + "type": "string", + "description": "The text to display within the label.", + "default": null, + "mutable": false, + "end_line": 97 + }, + { + "name": "max", + "type": "string", + "description": "Maximum date value.", + "default": null, + "mutable": false, + "end_line": 100 + }, + { + "name": "min", + "type": "string", + "description": "Minimum date value.", + "default": null, + "mutable": false, + "end_line": 103 + }, + { + "name": "name", + "type": "string", + "description": "Name of the form control. Submitted with the form as part of a name/value pair.", + "default": null, + "mutable": false, + "end_line": 106 + }, + { + "name": "readOnly", + "type": "boolean", + "description": "Whether the value is editable.", + "default": "false", + "mutable": false, + "end_line": 109 + }, + { + "name": "required", + "type": "boolean", + "description": "A value is required or must be checked for the form to be submittable.", + "default": "false", + "mutable": false, + "end_line": 112 + }, + { + "name": "size", + "type": "ModusSize", + "description": "The size of the input.", + "default": "'md'", + "mutable": false, + "end_line": 115 + }, + { + "name": "format", + "type": "| 'yyyy-mm-dd' | 'dd-mm-yyyy' | 'mm-dd-yyyy' | 'yyyy/mm/dd' | 'dd/mm/yyyy' | 'mm/dd/yyyy' | 'MMM DD, YYYY'", + "description": "The date format for display and input.", + "default": null, + "mutable": false, + "end_line": 125 + }, + { + "name": "value", + "type": "string", + "description": "The value of the control.", + "default": "''", + "mutable": true, + "end_line": 128 + }, + { + "name": "weekStartDay", + "type": "WeekStartDay", + "description": "The first day of the week for the calendar display", + "default": "'sunday'", + "mutable": false, + "end_line": 131 + }, + { + "name": "showWeekNumbers", + "type": "boolean", + "description": "Displays ISO 8601 week numbers in the calendar. Week numbers are calculated with Monday as the first day of the week.", + "default": "false", + "mutable": false, + "end_line": 134 + } + ], + "events": [ + { + "name": "inputBlur", + "detail": "FocusEvent", + "description": "Event emitted when the input loses focus.", + "end_line": 137 + }, + { + "name": "inputChange", + "detail": "InputEvent", + "description": "Event emitted when the input value changes. `target.value` is always ISO 8601 (YYYY-MM-DD), or empty string when incomplete or invalid.", + "end_line": 140 + }, + { + "name": "inputFocus", + "detail": "FocusEvent", + "description": "Event emitted when the input gains focus.", + "end_line": 143 + }, + { + "name": "calendarMonthChange", + "detail": "number", + "description": "Event emitted when the calendar month selection changes.", + "end_line": 146 + }, + { + "name": "calendarYearChange", + "detail": "number", + "description": "Event emitted when the calendar year selection changes.", + "end_line": 149 + } + ], + "methods": [], + "slots": [], + "examples": { + "basic": "\n ", + "variations": [], + "args": { + "bordered": "true", + "custom-class": "''", + "disabled": "false", + "label": "'Label'", + "read-only": "false", + "required": "false", + "show-week-numbers": "false", + "size": "'md'", + "value": "''", + "week-start-day": "'sunday'" + }, + "argTypes": {}, + "usage": [], + "events": [ + "inputBlur", + "inputChange", + "inputFocus", + "calendarMonthChange", + "calendarYearChange", + "" + ] + }, + "tag": "modus-wc-date", + "storyExample": { + "template": "\n ", + "args": { + "bordered": "true", + "custom-class": "''", + "disabled": "false", + "label": "'Label'", + "read-only": "false", + "required": "false", + "show-week-numbers": "false", + "size": "'md'", + "value": "''", + "week-start-day": "'sunday'" + }, + "argTypes": {}, + "events": [ + "inputBlur", + "inputChange", + "inputFocus", + "calendarMonthChange", + "calendarYearChange", + "" + ], + "fullContent": "import { withActions } from '@storybook/addon-actions/decorator';\nimport { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\nimport { IInputFeedbackProp, ModusSize, WeekStartDay } from '../types';\n\ninterface DateArgs {\n bordered?: boolean;\n 'custom-class'?: string;\n disabled?: boolean;\n feedback?: IInputFeedbackProp;\n format?:\n | 'yyyy-mm-dd'\n | 'dd-mm-yyyy'\n | 'mm-dd-yyyy'\n | 'yyyy/mm/dd'\n | 'dd/mm/yyyy'\n | 'mm/dd/yyyy'\n | 'MMM DD, YYYY';\n 'input-id'?: string;\n 'input-tab-index'?: number;\n label?: string;\n max?: string;\n min?: string;\n name?: string;\n 'read-only'?: boolean;\n required?: boolean;\n 'show-week-numbers'?: boolean;\n size?: ModusSize;\n value: string;\n 'week-start-day'?: WeekStartDay;\n}\n\nconst meta: Meta = {\n title: 'Components/Forms/Date',\n component: 'modus-wc-date',\n args: {\n bordered: true,\n 'custom-class': '',\n disabled: false,\n label: 'Label',\n 'read-only': false,\n required: false,\n 'show-week-numbers': false,\n size: 'md',\n value: '',\n 'week-start-day': 'sunday',\n },\n argTypes: {\n feedback: {\n table: {\n type: {\n detail: `\n Interface: IInputFeedbackProp\n Properties:\n - level ('error' | 'info' | 'success' | 'warning'): The feedback level\n - message (string, optional): The feedback message\n `,\n },\n },\n },\n size: {\n control: { type: 'select' },\n options: ['sm', 'md', 'lg'],\n },\n format: {\n control: { type: 'select' },\n options: [\n undefined,\n 'yyyy-mm-dd',\n 'dd-mm-yyyy',\n 'mm-dd-yyyy',\n 'yyyy/mm/dd',\n 'dd/mm/yyyy',\n 'mm/dd/yyyy',\n 'MMM DD, YYYY',\n ],\n },\n 'week-start-day': {\n control: { type: 'select' },\n options: [\n 'sunday',\n 'monday',\n 'tuesday',\n 'wednesday',\n 'thursday',\n 'friday',\n 'saturday',\n ],\n },\n },\n decorators: [withActions],\n parameters: {\n actions: {\n handles: [\n 'inputBlur',\n 'inputChange',\n 'inputFocus',\n 'calendarMonthChange',\n 'calendarYearChange',\n ],\n },\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nconst Template: Story = {\n render: (args) => {\n return html`\n \n \n `;\n },\n};\n\nexport const Default: Story = { ...Template };\n\nconst errorFeedback: IInputFeedbackProp = {\n level: 'error',\n message: 'Value is required.',\n};\n\nexport const WithErrorFeedback: Story = {\n ...Template,\n args: { feedback: errorFeedback, required: true },\n parameters: {\n docs: {\n source: {\n transform: (src) => `${src}\n`,\n },\n },\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n // Create a unique shadow host for date component\n if (!customElements.get('date-shadow-host')) {\n const DateShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-date',\n propsMapper: (v: DateArgs, el: HTMLElement) => {\n const dateEl = el as unknown as {\n bordered: boolean;\n customClass: string;\n disabled: boolean;\n feedback: IInputFeedbackProp;\n format?:\n | 'yyyy-mm-dd'\n | 'dd-mm-yyyy'\n | 'mm-dd-yyyy'\n | 'yyyy/mm/dd'\n | 'dd/mm/yyyy'\n | 'mm/dd/yyyy'\n | 'MMM DD, YYYY';\n inputId: string;\n inputTabIndex: number;\n label: string;\n max: string;\n min: string;\n name: string;\n readOnly: boolean;\n required: boolean;\n showWeekNumbers: boolean;\n size: string;\n value: string;\n weekStartDay: string;\n };\n dateEl.bordered = Boolean(v.bordered);\n dateEl.customClass = v['custom-class'] || '';\n dateEl.disabled = Boolean(v.disabled);\n dateEl.format = v.format;\n dateEl.inputId = v['input-id'] ?? '';\n dateEl.inputTabIndex = v['input-tab-index'] ?? -1;\n dateEl.label = v.label ?? '';\n dateEl.max = v.max ?? '';\n dateEl.min = v.min ?? '';\n dateEl.name = v.name ?? '';\n dateEl.readOnly = Boolean(v['read-only']);\n dateEl.required = Boolean(v.required);\n dateEl.showWeekNumbers = Boolean(v['show-week-numbers']);\n dateEl.size = v.size ?? '';\n dateEl.value = v.value ?? '';\n dateEl.weekStartDay = v['week-start-day'] ?? '';\n },\n });\n customElements.define('date-shadow-host', DateShadowHost);\n }\n\n return html``;\n },\n};\nexport const Migration: Story = {\n parameters: {\n docs: {\n description: {\n story: `\n#### Breaking Changes\n\n - In 1.0 input state was maintained by the component. 2.0 components encourage users to follow a controlled\n input model. See the Form Inputs [documentation]([Angular](?path=/docs/documentation-form-inputs--docs) for\n additional info and examples.\n - Size values have changed from verbose names (\\`medium\\`, \\`large\\`) to abbreviations (\\`sm\\`, \\`md\\`, \\`lg\\`).\n - The \\`value\\` prop now always outputs **ISO 8601 format** (\\`YYYY-MM-DD\\`), regardless of the display format.\n Previously, \\`value\\` matched the display format (e.g. \\`dd-mm-yyyy\\`).\n - The \\`format\\` prop is now automatically derived from the user's locale when not explicitly set.\n Previously, it defaulted to \\`dd-mm-yyyy\\`. The accepted values remain the same fixed union\n (\\`'yyyy-mm-dd'\\`, \\`'dd-mm-yyyy'\\`, \\`'mm-dd-yyyy'\\`, \\`'yyyy/mm/dd'\\`, \\`'dd/mm/yyyy'\\`, \\`'mm/dd/yyyy'\\`, \\`'MMM DD, YYYY'\\`).\n\n#### Prop Mapping\n\n| 1.0 Prop | 2.0 Prop | Notes |\n|--------------------|------------------|-----------------------------------------|\n| allow-chars-regex | | Not carried over |\n| alt-formats | | Not carried over |\n| aria-label | aria-label | |\n| auto-focus-input | | Not carried over |\n| disabled | disabled | |\n| disable-validation | | Not carried over |\n| error-text | feedback.message | Use \\`feedback\\` level |\n| filler-date | | Not carried over |\n| format | format | Auto-derived from locale when not set; union type unchanged |\n| helper-text | | Not carried over |\n| label | label | |\n| max | max | |\n| min | min | |\n| placeholder | | Not carried over |\n| read-only | read-only | |\n| required | required | |\n| show-calendar-icon | | Not carried over |\n| size | size | \\`medium\\` → \\`md\\`, \\`large\\` → \\`lg\\` |\n| type | | Not carried over |\n| valid-text | feedback.message | Use \\`feedback\\` level |\n| value | value | Now outputs ISO 8601 (\\`YYYY-MM-DD\\`) |\n\n#### Event Mapping\n\n| 1.0 Event | 2.0 Event | Notes |\n|---------------------|-------------|------------------|\n| calendarIconClicked | | Not carried over |\n| dateInputBlur | inputBlur | |\n| valueChange | inputChange | |\n| valueError | | Not carried over |\n `,\n },\n },\n controls: { disable: true },\n canvas: { disable: true },\n },\n render: () => html`
    `,\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-divider.json b/mcp/versions/1.5.0/component-docs/modus-wc-divider.json new file mode 100644 index 000000000..0ce9b8bcc --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-divider.json @@ -0,0 +1,85 @@ +{ + "description": "A customizable divider component used to separate content horizontally or vertically", + "properties": [ + { + "name": "color", + "type": "| 'primary' | 'secondary' | 'tertiary' | 'high-contrast' | 'success' | 'warning' | 'danger'", + "description": "The color of the divider line.", + "default": "'tertiary'", + "mutable": false, + "end_line": 28 + }, + { + "name": "content", + "type": "string", + "description": "The content to display in the divider.", + "default": "''", + "mutable": false, + "end_line": 31 + }, + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the divider element.", + "default": "''", + "mutable": false, + "end_line": 34 + }, + { + "name": "orientation", + "type": "Orientation", + "description": "The orientation of the divider. This is in reference to how content will be rendered around the divider.", + "default": "'vertical'", + "mutable": false, + "end_line": 37 + }, + { + "name": "position", + "type": "'center' | 'end' | 'start'", + "description": "The position of the divider.", + "default": "'center'", + "mutable": false, + "end_line": 40 + }, + { + "name": "responsive", + "type": "boolean", + "description": "Whether the divider is responsive or not.", + "default": "true", + "mutable": false, + "end_line": 43 + } + ], + "events": [], + "methods": [], + "slots": [], + "examples": { + "basic": "", + "variations": [], + "args": { + "color": "'tertiary'", + "content": "''", + "custom-class": "''", + "orientation": "'vertical'", + "position": "'center'", + "responsive": "true" + }, + "argTypes": {}, + "usage": [] + }, + "tag": "modus-wc-divider", + "storyExample": { + "template": "", + "args": { + "color": "'tertiary'", + "content": "''", + "custom-class": "''", + "orientation": "'vertical'", + "position": "'center'", + "responsive": "true" + }, + "argTypes": {}, + "events": [], + "fullContent": "import { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\nimport { Orientation } from '../types';\n\ninterface DividerArgs {\n color:\n | 'primary'\n | 'secondary'\n | 'tertiary'\n | 'high-contrast'\n | 'success'\n | 'warning'\n | 'danger';\n content: string;\n 'custom-class'?: string;\n orientation: Orientation;\n position: 'center' | 'end' | 'start';\n responsive: boolean;\n}\n\nconst meta: Meta = {\n title: 'Components/Divider',\n component: 'modus-wc-divider',\n args: {\n color: 'tertiary',\n content: '',\n 'custom-class': '',\n orientation: 'vertical',\n position: 'center',\n responsive: true,\n },\n argTypes: {\n color: {\n control: { type: 'select' },\n options: [\n 'primary',\n 'secondary',\n 'tertiary',\n 'high-contrast',\n 'success',\n 'warning',\n 'danger',\n ],\n },\n content: {\n control: 'text',\n },\n 'custom-class': {\n control: 'text',\n },\n orientation: {\n control: { type: 'select' },\n options: ['horizontal', 'vertical'],\n },\n position: {\n control: { type: 'select' },\n options: ['start', 'center', 'end'],\n },\n responsive: {\n control: { type: 'boolean' },\n },\n },\n parameters: {\n layout: 'padded',\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nconst Template: Story = {\n render: (args) => html`\n \n `,\n};\n\nexport const Default: Story = { ...Template };\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n if (!customElements.get('divider-shadow-host')) {\n const DividerShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-divider',\n propsMapper: (v: DividerArgs, el: HTMLElement) => {\n const dividerEl = el as unknown as {\n color: string;\n content: string;\n customClass: string;\n orientation: string;\n position: string;\n responsive: boolean;\n };\n dividerEl.color = v.color;\n dividerEl.content = v.content;\n dividerEl.customClass = v['custom-class'] || '';\n dividerEl.orientation = v.orientation;\n dividerEl.position = v.position;\n dividerEl.responsive = Boolean(v.responsive);\n // Mirror the Default story: apply display:flex + height on the\n // modus-wc-divider element itself so the inner div gets its height\n if (v.orientation === 'horizontal') {\n el.style.display = 'flex';\n el.style.height = '100px';\n } else {\n el.style.display = '';\n el.style.height = '';\n }\n },\n });\n customElements.define('divider-shadow-host', DividerShadowHost);\n }\n\n return html``;\n },\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-dropdown-menu.json b/mcp/versions/1.5.0/component-docs/modus-wc-dropdown-menu.json new file mode 100644 index 000000000..970b95422 --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-dropdown-menu.json @@ -0,0 +1,164 @@ +{ + "description": "A customizable dropdown menu component used to render a button and toggleable menu. The component supports a 'button' and 'menu' `` for injecting custom HTML content.", + "properties": [ + { + "name": "buttonAriaLabel", + "type": "string", + "description": "The aria-label for the dropdown button.", + "default": null, + "mutable": false, + "end_line": 36 + }, + { + "name": "buttonColor", + "type": "| 'primary' | 'secondary' | 'tertiary' | 'warning' | 'danger'", + "description": "The color variant of the button.", + "default": "'primary'", + "mutable": false, + "end_line": 44 + }, + { + "name": "buttonShape", + "type": "'circle' | 'ellipse' | 'rectangle' | 'square'", + "description": "The shape of the button.", + "default": "'rectangle'", + "mutable": false, + "end_line": 48 + }, + { + "name": "buttonSize", + "type": "DaisySize | 'xl'", + "description": "The size of the button.", + "default": "'md'", + "mutable": false, + "end_line": 51 + }, + { + "name": "buttonVariant", + "type": "'borderless' | 'filled' | 'outlined'", + "description": "The variant of the button.", + "default": "'filled'", + "mutable": false, + "end_line": 54 + }, + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the host element.", + "default": "''", + "mutable": false, + "end_line": 57 + }, + { + "name": "disabled", + "type": "boolean", + "description": "If true, the button will be disabled.", + "default": "false", + "mutable": false, + "end_line": 60 + }, + { + "name": "menuBordered", + "type": "boolean", + "description": "Indicates that the menu should have a border.", + "default": "true", + "mutable": false, + "end_line": 63 + }, + { + "name": "menuOffset", + "type": "number", + "description": "Distance between the button and menu in pixels.", + "default": "10", + "mutable": false, + "end_line": 66 + }, + { + "name": "menuPlacement", + "type": "PopoverPlacement", + "description": "The placement of the menu relative to the button.", + "default": "'bottom-start'", + "mutable": false, + "end_line": 69 + }, + { + "name": "menuSize", + "type": "ModusSize", + "description": "The size of the menu.", + "default": "'md'", + "mutable": false, + "end_line": 72 + }, + { + "name": "menuVisible", + "type": "boolean", + "description": "Indicates that the menu is visible.", + "default": "false", + "mutable": true, + "end_line": 75 + } + ], + "events": [ + { + "name": "menuVisibilityChange", + "detail": "{ isVisible: boolean }", + "description": "Event emitted when the menuVisible prop changes.", + "end_line": 78 + } + ], + "methods": [], + "slots": [ + { + "name": "button", + "description": "Slot for button content" + }, + { + "name": "menu", + "description": "Slot for menu content" + } + ], + "examples": { + "basic": "\n\n\n\n\n
    \n Button\n \n
    \n\n
    \n \n \n \n
    \n\n\n\n
    \n Selected Value:\n \n
    ", + "variations": [], + "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-visible": "false" + }, + "argTypes": {}, + "usage": [], + "events": [ + "menuVisibilityChange" + ] + }, + "tag": "modus-wc-dropdown-menu", + "storyExample": { + "template": "\n\n\n\n\n
    \n Button\n \n
    \n\n
    \n \n \n \n
    \n\n\n\n
    \n Selected Value:\n \n
    ", + "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-visible": "false" + }, + "argTypes": {}, + "events": [ + "menuVisibilityChange" + ], + "fullContent": "import { withActions } from '@storybook/addon-actions/decorator';\nimport { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\nimport { DaisySize, ModusSize, PopoverPlacement } from '../types';\n\ninterface DropdownMenuArgs {\n 'button-aria-label'?: string;\n 'button-color'?: 'primary' | 'secondary' | 'tertiary' | 'warning' | 'danger';\n 'button-shape'?: 'circle' | 'ellipse' | 'rectangle' | 'square';\n 'button-size'?: DaisySize | 'xl';\n 'button-variant'?: 'borderless' | 'filled' | 'outlined';\n 'custom-class'?: string;\n disabled?: boolean;\n 'menu-bordered'?: boolean;\n 'menu-offset'?: number;\n 'menu-placement'?: PopoverPlacement;\n 'menu-size'?: ModusSize;\n 'menu-visible': boolean;\n}\n\nconst meta: Meta = {\n title: 'Components/Dropdown Menu',\n component: 'modus-wc-dropdown-menu',\n args: {\n 'button-aria-label': 'Dropdown menu button',\n 'button-color': 'primary',\n 'button-shape': 'rectangle',\n 'button-size': 'md',\n 'button-variant': 'filled',\n disabled: false,\n 'menu-bordered': true,\n 'menu-offset': 14,\n 'menu-placement': 'bottom-start',\n 'menu-size': 'md',\n 'menu-visible': false,\n },\n argTypes: {\n 'button-color': {\n control: { type: 'select' },\n options: ['primary', 'secondary', 'tertiary', 'warning', 'danger'],\n },\n 'button-size': {\n control: { type: 'select' },\n options: ['xs', 'sm', 'md', 'lg', 'xl'],\n },\n 'button-shape': {\n control: { type: 'select' },\n options: ['circle', 'ellipse', 'rectangle', 'square'],\n },\n 'button-variant': {\n control: { type: 'select' },\n options: ['borderless', 'filled', 'outlined'],\n },\n 'menu-placement': {\n control: { type: 'select' },\n options: [\n 'top',\n 'top-start',\n 'top-end',\n 'bottom',\n 'bottom-start',\n 'bottom-end',\n 'left',\n 'left-start',\n 'left-end',\n 'right',\n 'right-start',\n 'right-end',\n ],\n },\n 'menu-size': {\n control: { type: 'select' },\n options: ['sm', 'md', 'lg'],\n },\n },\n decorators: [withActions],\n parameters: {\n actions: {\n handles: ['menuVisibilityChange'],\n },\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nconst Template: Story = {\n render: (args) => {\n let selectedValue = '';\n\n const handleItemSelect = (event: CustomEvent) => {\n selectedValue = event.detail.value;\n const displayElement = document.querySelector('#selected-value');\n if (displayElement) {\n displayElement.textContent = selectedValue;\n }\n\n // Close the dropdown menu when an item is selected\n const dropdownMenu = event.target as HTMLElement;\n const dropdownMenuElement = dropdownMenu.closest(\n 'modus-wc-dropdown-menu'\n );\n if (dropdownMenuElement) {\n dropdownMenuElement.menuVisible = false;\n }\n };\n\n // prettier-ignore\n return html`\n\n\n\n\n\n
    \n Button\n \n
    \n\n
    \n \n \n \n
    \n\n\n\n
    \n Selected Value:\n \n
    \n `;\n },\n};\n\nexport const Default: Story = { ...Template };\n\nexport const IconOnlyDropdownMenu: Story = {\n render: () => {\n // prettier-ignore\n return html`\n\n\n\n
    \n \n
    \n
    \n \n
    \n
    \n `;\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n if (!customElements.get('dropdown-menu-shadow-host')) {\n const DropdownMenuShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-dropdown-menu',\n propsMapper: (v: DropdownMenuArgs, el: HTMLElement) => {\n const dropdownEl = el as unknown as {\n buttonAriaLabel: string;\n buttonColor: string;\n buttonShape: string;\n buttonSize: DaisySize;\n buttonVariant: string;\n customClass: string;\n disabled: boolean;\n menuBordered: boolean;\n menuOffset: number;\n menuPlacement: PopoverPlacement;\n menuSize: ModusSize;\n menuVisible: boolean;\n };\n dropdownEl.buttonAriaLabel = v['button-aria-label'] || '';\n dropdownEl.buttonColor = v['button-color'] || 'primary';\n dropdownEl.buttonShape = v['button-shape'] || 'rectangle';\n dropdownEl.buttonSize = v['button-size'] as DaisySize;\n dropdownEl.buttonVariant = v['button-variant'] || 'filled';\n dropdownEl.customClass = v['custom-class'] || '';\n dropdownEl.disabled = Boolean(v.disabled);\n dropdownEl.menuBordered = Boolean(v['menu-bordered']);\n dropdownEl.menuOffset = v['menu-offset'] ?? 10;\n dropdownEl.menuPlacement = v['menu-placement'] as PopoverPlacement;\n dropdownEl.menuSize = v['menu-size'] as ModusSize;\n dropdownEl.menuVisible = Boolean(v['menu-visible']);\n\n // On first render: add slot content and append the Selected Value\n // display as a sibling of el inside the helper's display:contents\n // wrapper — both become direct layout children of the shadow root.\n if (!el.hasAttribute('data-layout-built')) {\n el.setAttribute('data-layout-built', '');\n\n const wrapper = el.parentElement!;\n\n const buttonSlot = document.createElement('div');\n buttonSlot.setAttribute('slot', 'button');\n buttonSlot.style.cssText =\n 'display: flex; align-items: center; gap: 4px;';\n buttonSlot.appendChild(document.createTextNode('Button'));\n const expandIcon = document.createElement('modus-wc-icon');\n expandIcon.setAttribute('name', 'expand_more');\n expandIcon.setAttribute('size', 'sm');\n buttonSlot.appendChild(expandIcon);\n\n const menuSlot = document.createElement('div');\n menuSlot.setAttribute('slot', 'menu');\n [\n { label: 'Item One', value: '1' },\n { label: 'Item Two', value: '2' },\n { label: 'Item Three', value: '3' },\n ].forEach(({ label, value }) => {\n const item = document.createElement('modus-wc-menu-item');\n item.setAttribute('label', label);\n item.setAttribute('value', value);\n menuSlot.appendChild(item);\n });\n\n el.appendChild(buttonSlot);\n el.appendChild(menuSlot);\n\n // Selected value display as sibling in wrapper\n const valueDiv = document.createElement('div');\n valueDiv.style.cssText = 'font-size: 14px; padding-top: 12px;';\n valueDiv.textContent = 'Selected Value: ';\n const valueSpan = document.createElement('span');\n valueDiv.appendChild(valueSpan);\n wrapper.appendChild(valueDiv);\n\n el.addEventListener('itemSelect', (e: Event) => {\n const custom = e as CustomEvent<{ value: string }>;\n valueSpan.textContent = custom.detail?.value ?? '';\n (el as unknown as { menuVisible: boolean }).menuVisible = false;\n });\n }\n },\n });\n customElements.define(\n 'dropdown-menu-shadow-host',\n DropdownMenuShadowHost\n );\n }\n\n return html``;\n },\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-file-dropzone.json b/mcp/versions/1.5.0/component-docs/modus-wc-file-dropzone.json new file mode 100644 index 000000000..67558988f --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-file-dropzone.json @@ -0,0 +1,155 @@ +{ + "description": "File dropzone component that allows users to drag and drop files for upload. The component supports a `` called 'dropzone' for adding custom content such as progress indicators or additional instructions within the dropzone area.", + "properties": [ + { + "name": "acceptFileTypes", + "type": "string", + "description": "Accepted file types (e.g. '.jpg,.png' or 'image/*')", + "default": null, + "mutable": false, + "end_line": 46 + }, + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the file dropzone element", + "default": "''", + "mutable": false, + "end_line": 49 + }, + { + "name": "disabled", + "type": "boolean", + "description": "Disable the file input", + "default": null, + "mutable": false, + "end_line": 52 + }, + { + "name": "fileDraggedOverInstructions", + "type": "string", + "description": "Custom instructions shown when files are dragged over the dropzone", + "default": null, + "mutable": false, + "end_line": 55 + }, + { + "name": "includeStateIcon", + "type": "boolean", + "description": "Include state icon (upload, success, error)", + "default": "true", + "mutable": false, + "end_line": 58 + }, + { + "name": "instructions", + "type": "string", + "description": "Custom instructions shown as the default dropzone message", + "default": null, + "mutable": false, + "end_line": 61 + }, + { + "name": "invalidFileTypeMessage", + "type": "string", + "description": "Custom error message displayed when an invalid file type is selected", + "default": null, + "mutable": false, + "end_line": 64 + }, + { + "name": "maxFileNameLength", + "type": "number", + "description": "Maximum allowed length of filename, will show error if exceeded", + "default": null, + "mutable": false, + "end_line": 67 + }, + { + "name": "maxFileCount", + "type": "number", + "description": "Maximum number of files allowed, will show error if exceeded", + "default": null, + "mutable": false, + "end_line": 70 + }, + { + "name": "maxTotalFileSizeBytes", + "type": "number", + "description": "Maximum total file size in bytes allowed, will show error if exceeded", + "default": null, + "mutable": false, + "end_line": 73 + }, + { + "name": "multiple", + "type": "boolean", + "description": "Allow multiple file selection", + "default": null, + "mutable": false, + "end_line": 76 + }, + { + "name": "successMessage", + "type": "string", + "description": "Success message displayed when files are uploaded successfully", + "default": null, + "mutable": false, + "end_line": 79 + } + ], + "events": [ + { + "name": "fileSelect", + "detail": "FileList", + "description": "Event emitted when files are selected", + "end_line": 82 + } + ], + "methods": [ + { + "name": "reset", + "signature": "()", + "parameters": "", + "returnType": "void", + "description": "Reset the dropzone to its initial state, clearing all error and success states", + "end_line": 93 + } + ], + "slots": [ + { + "name": "dropzone", + "description": "Slot for dropzone content" + } + ], + "examples": { + "basic": "", + "variations": [], + "args": { + "accept-file-types": "'.doc", + "disabled": "false", + "include-state-icon": "true", + "instructions": "'Drag files here or browse to upload'" + }, + "argTypes": {}, + "usage": [], + "events": [ + "fileSelect" + ] + }, + "tag": "modus-wc-file-dropzone", + "storyExample": { + "template": "", + "args": { + "accept-file-types": "'.doc", + "disabled": "false", + "include-state-icon": "true", + "instructions": "'Drag files here or browse to upload'" + }, + "argTypes": {}, + "events": [ + "fileSelect" + ], + "fullContent": "import { withActions } from '@storybook/addon-actions/decorator';\nimport { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\n\ninterface FileDropzoneArgs {\n 'accept-file-types'?: string;\n 'custom-class'?: string;\n disabled?: boolean;\n 'file-dragged-over-instructions'?: string;\n 'include-state-icon'?: boolean;\n instructions?: string;\n 'invalid-file-type-message'?: string;\n 'max-file-name-length'?: number;\n 'max-file-count'?: number;\n 'max-total-file-size-bytes'?: number;\n multiple?: boolean;\n 'success-message'?: string;\n reset?: () => Promise;\n}\n\nconst meta: Meta = {\n title: 'Components/File Dropzone',\n component: 'modus-wc-file-dropzone',\n args: {\n 'accept-file-types': '.doc, .docx, .pdf',\n disabled: false,\n 'include-state-icon': true,\n instructions: 'Drag files here or browse to upload',\n },\n argTypes: {\n 'accept-file-types': {\n control: 'text',\n description: \"Accepted file types (e.g. '.jpg,.png' or 'image/*')\",\n },\n 'custom-class': {\n control: 'text',\n description: 'Custom CSS class to apply to the file dropzone element',\n },\n disabled: {\n control: 'boolean',\n description: 'Disable the file input',\n table: {\n defaultValue: { summary: 'false' },\n },\n },\n instructions: {\n control: 'text',\n description: 'Default instructions displayed in the dropzone',\n },\n 'invalid-file-type-message': {\n control: 'text',\n description:\n 'Custom error message displayed when an invalid file type is selected',\n },\n 'max-file-name-length': {\n control: 'number',\n description:\n 'Maximum allowed length of filename, will show error if exceeded',\n },\n 'max-file-count': {\n control: 'number',\n description:\n 'Maximum number of files allowed, will show error if exceeded',\n },\n 'max-total-file-size-bytes': {\n control: 'number',\n description:\n 'Maximum total file size in bytes allowed, will show error if exceeded',\n },\n multiple: {\n control: 'boolean',\n description: 'Allow multiple file selection',\n table: {\n defaultValue: { summary: 'false' },\n },\n },\n reset: {\n description:\n 'Reset the dropzone to its initial state, clearing all error and success states',\n table: {\n category: 'Methods',\n type: { summary: '() => Promise' },\n },\n },\n },\n decorators: [withActions],\n parameters: {\n actions: {\n handles: ['fileSelect'],\n },\n },\n};\n\nexport default meta;\ntype Story = StoryObj;\n\nexport const Default: Story = {\n render: (args) => html`\n \n `,\n};\n\nexport const customContent: Story = {\n args: {\n 'accept-file-types': '.pdf, .doc, .docx',\n 'success-message': 'Files uploaded successfully!',\n },\n\n parameters: {\n docs: {\n source: {\n code: `
    \n \n
    \n \n
    \n \n\n \n Reset Dropzone\n \n
    \n\n`,\n },\n },\n },\n\n render: (args) => html`\n
    \n \n
    \n \n
    \n \n\n {\n const dropzone = document.getElementById(\n 'custom-dropzone'\n ) as HTMLElement & {\n reset?: () => Promise;\n };\n if (dropzone?.reset) {\n void dropzone.reset();\n }\n }}\n >\n Reset Dropzone\n \n
    \n `,\n};\n\nexport const fileValidations: Story = {\n args: {\n multiple: true,\n 'max-file-name-length': 20,\n 'max-file-count': 3,\n 'max-total-file-size-bytes': 10485760, // 10MB\n 'invalid-file-type-message':\n 'Invalid file format. Please upload correct file type.',\n },\n render: (args) => html`\n \n `,\n};\n\nexport const multipleFiles: Story = {\n args: {\n multiple: true,\n 'accept-file-types': 'image/*',\n },\n render: (args) => html`\n \n `,\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n if (!customElements.get('file-dropzone-shadow-host')) {\n const FileDropzoneShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-file-dropzone',\n propsMapper: (v: FileDropzoneArgs, el: HTMLElement) => {\n const dropzoneEl = el as unknown as {\n acceptFileTypes: string;\n customClass: string;\n disabled: boolean;\n fileDraggedOverInstructions: string;\n includeStateIcon: boolean;\n instructions: string;\n invalidFileTypeMessage: string;\n maxFileCount: number;\n maxFileNameLength: number;\n maxTotalFileSizeBytes: number;\n multiple: boolean;\n successMessage: string;\n };\n dropzoneEl.acceptFileTypes = v['accept-file-types'] ?? '';\n dropzoneEl.customClass = v['custom-class'] || '';\n dropzoneEl.disabled = Boolean(v.disabled);\n dropzoneEl.fileDraggedOverInstructions =\n v['file-dragged-over-instructions'] ?? '';\n dropzoneEl.includeStateIcon = Boolean(v['include-state-icon']);\n dropzoneEl.instructions = v.instructions ?? '';\n dropzoneEl.invalidFileTypeMessage =\n v['invalid-file-type-message'] ?? '';\n dropzoneEl.maxFileCount = v['max-file-count'] ?? 0;\n dropzoneEl.maxFileNameLength = v['max-file-name-length'] ?? 0;\n dropzoneEl.maxTotalFileSizeBytes =\n v['max-total-file-size-bytes'] ?? 0;\n dropzoneEl.multiple = Boolean(v.multiple);\n dropzoneEl.successMessage = v['success-message'] ?? '';\n },\n });\n customElements.define(\n 'file-dropzone-shadow-host',\n FileDropzoneShadowHost\n );\n }\n\n return html``;\n },\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-handle.json b/mcp/versions/1.5.0/component-docs/modus-wc-handle.json new file mode 100644 index 000000000..a34ed2f31 --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-handle.json @@ -0,0 +1,135 @@ +{ + "description": "A draggable handle component for resizing adjacent elements", + "properties": [ + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the handle element.", + "default": "''", + "mutable": false, + "end_line": 29 + }, + { + "name": "leftTarget", + "type": "string | HTMLElement", + "description": "The left target element to resize (CSS selector or HTMLElement)", + "default": null, + "mutable": false, + "end_line": 32 + }, + { + "name": "orientation", + "type": "Orientation", + "description": "The orientation of the handle.", + "default": "'horizontal'", + "mutable": false, + "end_line": 35 + }, + { + "name": "rightTarget", + "type": "string | HTMLElement", + "description": "The right target element to resize (CSS selector or HTMLElement)", + "default": null, + "mutable": false, + "end_line": 38 + }, + { + "name": "size", + "type": "'default' | 'lg' | 'xl' | '2xl'", + "description": "The size of the handle.", + "default": "'default'", + "mutable": false, + "end_line": 41 + }, + { + "name": "density", + "type": "'compact' | 'comfortable' | 'relaxed'", + "description": "The density/spacing of the handle container (compact: 8px, comfortable: 12px, relaxed: 16px).", + "default": "'comfortable'", + "mutable": false, + "end_line": 44 + }, + { + "name": "defaultSplit", + "type": "number", + "description": "The initial split percentage for the left/top panel (1-100). The right/bottom panel gets the remaining percentage.", + "default": "50", + "mutable": false, + "end_line": 47 + }, + { + "name": "buttonSize", + "type": "DaisySize | 'xl'", + "description": "The size of the button.", + "default": "'md'", + "mutable": false, + "end_line": 50 + }, + { + "name": "buttonColor", + "type": "| 'primary' | 'secondary' | 'tertiary' | 'warning' | 'danger'", + "description": "The color of the button.", + "default": "'tertiary'", + "mutable": false, + "end_line": 58 + }, + { + "name": "buttonVariant", + "type": "'borderless' | 'filled' | 'outlined'", + "description": "The variant of the button.", + "default": "'filled'", + "mutable": false, + "end_line": 61 + }, + { + "name": "type", + "type": "'bar' | 'button'", + "description": "The type of handle to display.", + "default": "'bar'", + "mutable": false, + "end_line": 64 + } + ], + "events": [], + "methods": [], + "slots": [], + "examples": { + "basic": "\n${keyed(orientation, html", + "variations": [], + "args": { + "button-color": "'tertiary'", + "button-size": "'md'", + "button-variant": "'filled'", + "custom-class": "''", + "default-split": "50", + "density": "'comfortable'", + "left-target": "''", + "orientation": "'vertical'", + "right-target": "''", + "size": "'default'", + "type": "'bar'" + }, + "argTypes": {}, + "usage": [] + }, + "tag": "modus-wc-handle", + "storyExample": { + "template": "\n${keyed(orientation, html", + "args": { + "button-color": "'tertiary'", + "button-size": "'md'", + "button-variant": "'filled'", + "custom-class": "''", + "default-split": "50", + "density": "'comfortable'", + "left-target": "''", + "orientation": "'vertical'", + "right-target": "''", + "size": "'default'", + "type": "'bar'" + }, + "argTypes": {}, + "events": [], + "fullContent": "import { Meta, StoryObj } from '@storybook/web-components';\nimport { html, unsafeCSS } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { keyed } from 'lit/directives/keyed.js';\nimport { DaisySize, Orientation } from '../types';\n\n// Shared styles for story demos\nconst storyStyles = unsafeCSS(`\n .handle-demo-container {\n display: flex;\n gap: 0;\n }\n\n .handle-demo-container.horizontal {\n height: 300px;\n }\n\n .handle-demo-container.vertical {\n flex-direction: column;\n height: 500px;\n }\n\n .handle-demo-panel {\n background-color: var(--modus-wc-color-base-100);\n overflow: auto;\n }\n\n .handle-demo-panel.initial-size-200 {\n width: 200px;\n }\n\n .handle-demo-panel.initial-height-200 {\n height: 200px;\n }\n\n .handle-demo-panel.initial-size-400 {\n width: 400px;\n }\n\n .handle-demo-panel.flex-fill {\n flex: 1;\n }\n\n .handle-demo-right-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 0;\n }\n`);\n\ninterface HandleArgs {\n 'custom-class'?: string;\n 'default-split'?: number;\n density?: 'compact' | 'comfortable' | 'relaxed';\n 'button-color'?: 'primary' | 'secondary' | 'tertiary' | 'warning' | 'danger';\n 'button-size'?: DaisySize | 'xl';\n 'button-variant'?: 'borderless' | 'filled' | 'outlined';\n 'left-target'?: string;\n orientation?: Orientation;\n 'right-target'?: string;\n size?: 'default' | 'lg' | 'xl' | '2xl';\n type?: 'bar' | 'button';\n}\n\nconst meta: Meta = {\n title: 'Components/Handle',\n component: 'modus-wc-handle',\n args: {\n 'button-color': 'tertiary',\n 'button-size': 'md',\n 'button-variant': 'filled',\n 'custom-class': '',\n 'default-split': 50,\n density: 'comfortable',\n 'left-target': '',\n orientation: 'vertical',\n 'right-target': '',\n size: 'default',\n type: 'bar',\n },\n argTypes: {\n 'custom-class': {\n control: 'text',\n },\n 'default-split': {\n control: { type: 'range', min: 1, max: 100, step: 1 },\n },\n density: {\n control: { type: 'select' },\n options: ['compact', 'comfortable', 'relaxed'],\n },\n 'button-size': {\n control: { type: 'select' },\n options: ['sm', 'md', 'lg', 'xl'],\n },\n 'button-color': {\n control: { type: 'select' },\n options: ['primary', 'secondary', 'tertiary', 'warning', 'danger'],\n },\n 'button-variant': {\n control: { type: 'select' },\n options: ['borderless', 'filled', 'outlined'],\n },\n 'left-target': {\n control: 'text',\n },\n orientation: {\n control: { type: 'select' },\n options: ['horizontal', 'vertical'],\n },\n 'right-target': {\n control: 'text',\n },\n size: {\n control: { type: 'select' },\n options: ['default', 'lg', 'xl', '2xl'],\n },\n type: {\n control: { type: 'select' },\n options: ['bar', 'button'],\n },\n },\n parameters: {\n layout: 'padded',\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nconst HandleTemplate = (args?: HandleArgs) =>\n // prettier-ignore\n html`\n\n `;\n\n// Helper templates for panels\nconst PanelTemplate = (\n id: string,\n title: string,\n content: string,\n className: string = ''\n) =>\n // prettier-ignore\n html`\n
    \n

    ${title}

    \n

    ${content}

    \n
    \n`;\n\nconst PanelWithKeyboardInfo = (\n id: string,\n title: string,\n content: string,\n keyboardInfo: string,\n className: string = ''\n) =>\n // prettier-ignore\n html`\n
    \n

    ${title}

    \n

    ${content}

    \n

    Keyboard: ${keyboardInfo}

    \n
    \n`;\n\n// Generate unique ID for each render to avoid conflicts on docs page\nconst generateUniqueId = () => Math.random().toString(36).substring(2, 9);\n\n// Reusable render function for demos\nconst Template = (args?: HandleArgs) => {\n const orientation = args?.orientation ?? 'horizontal';\n const type = args?.type ?? 'bar';\n const isHorizontal = orientation === 'horizontal';\n const uniqueId = generateUniqueId();\n const leftId = `panel-left-${uniqueId}`;\n const rightId = `panel-right-${uniqueId}`;\n // prettier-ignore\n return html`\n\n${keyed(orientation, html`\n
    \n ${PanelWithKeyboardInfo(\n leftId,\n isHorizontal ? 'Left Panel' : 'Top Panel',\n 'Drag the handle to resize this panel.',\n `Focus the handle and use ${isHorizontal ? 'Left/Right' : 'Up/Down'} arrow keys to resize (5px per press, 15px with Shift).`,\n isHorizontal ? 'initial-size-200' : 'initial-height-200'\n )}\n ${HandleTemplate({\n orientation: orientation,\n size: args?.size ?? 'default',\n density: args?.density ?? 'comfortable',\n type: type,\n 'default-split': args?.['default-split'] ?? 50,\n 'custom-class': args?.['custom-class'],\n 'button-size': args?.['button-size'],\n 'button-color': args?.['button-color'],\n 'button-variant': args?.['button-variant'],\n 'left-target': `#${leftId}`,\n 'right-target': `#${rightId}`,\n })}\n ${PanelTemplate(\n rightId,\n isHorizontal ? 'Right Panel' : 'Bottom Panel',\n 'This panel will resize automatically when you drag the handle.',\n 'flex-fill'\n )}\n
    \n`)}`;\n};\n\nexport const Default: Story = {\n render: (args?: HandleArgs) => Template(args),\n};\n\nexport const ButtonVariant: Story = {\n render: (args?: HandleArgs) => {\n return Template({\n ...args,\n orientation: args?.orientation ?? 'horizontal',\n size: args?.size ?? 'default',\n density: args?.density ?? 'comfortable',\n type: 'button',\n });\n },\n args: {\n type: 'button',\n },\n parameters: {\n docs: {\n description: {\n story: `\nButton type handle with customizable button properties.\n\n**Button Properties Available:**\n- \\`button-size\\`: sm, md, lg\n- \\`button-color\\`: primary, secondary, tertiary, warning, danger\n- \\`button-variant\\`: borderless, filled, outlined\n\n**Keyboard Navigation:**\n- Arrow keys: Move 5px per press\n- Shift + Arrow keys: Move 15px per press\n\nThe button handle provides a more prominent visual indicator compared to the bar type.\n `,\n },\n },\n },\n};\n\nexport const MultipleHandlesNested: Story = {\n render: () => {\n // prettier-ignore\n return html`\n\n
    \n ${PanelTemplate('panel-one', 'One', 'Large left panel', 'initial-size-400')}\n ${HandleTemplate({\n orientation: 'horizontal',\n size: 'default',\n density: 'comfortable',\n type: 'bar',\n 'left-target': '#panel-one',\n 'right-target': '#right-container',\n })}\n
    \n ${PanelTemplate('panel-two', 'Two', 'Top right panel', 'initial-height-200')}\n ${HandleTemplate({\n orientation: 'vertical',\n size: 'default',\n density: 'comfortable',\n type: 'bar',\n 'left-target': '#panel-two',\n 'right-target': '#panel-three',\n })}\n ${PanelTemplate('panel-three', 'Three', 'Bottom right panel', 'flex-fill')}\n
    \n
    \n `;\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args?: HandleArgs) => Template(args),\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-icon.json b/mcp/versions/1.5.0/component-docs/modus-wc-icon.json new file mode 100644 index 000000000..219cefe18 --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-icon.json @@ -0,0 +1,722 @@ +{ + "description": "A customizable icon component used to render Modus icons. This component requires Modus icons to be installed in the host application. See [Modus Icon Usage](/docs/documentation-modus-icon-usage--docs) for steps.", + "properties": [ + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the i element.", + "default": "''", + "mutable": false, + "end_line": 23 + }, + { + "name": "decorative", + "type": "boolean", + "description": "Indicates that the icon is decorative. When true, sets aria-hidden to hide the icon from screen readers.", + "default": "true", + "mutable": false, + "end_line": 26 + }, + { + "name": "name", + "type": "string", + "description": "The icon name, should match the CSS class in the icon font.", + "default": null, + "mutable": false, + "end_line": 29 + }, + { + "name": "size", + "type": "DaisySize", + "description": "The icon size, can be \"sm\", \"md\", \"lg\" (a custom size can be specified in CSS). This adjusts the font size for the icon.", + "default": "'md'", + "mutable": false, + "end_line": 32 + }, + { + "name": "variant", + "type": "'outlined' | 'solid'", + "description": "The icon variant, can be \"outlined\" or \"solid\".", + "default": null, + "mutable": false, + "end_line": 35 + } + ], + "events": [], + "methods": [], + "slots": [], + "examples": { + "basic": "\n", + "variations": [], + "args": { + "custom-class": "''", + "decorative": "false", + "name": "'alert'", + "size": "'md'" + }, + "argTypes": {}, + "usage": [] + }, + "tag": "modus-wc-icon", + "storyExample": { + "template": "\n", + "args": { + "custom-class": "''", + "decorative": "false", + "name": "'alert'", + "size": "'md'" + }, + "argTypes": {}, + "events": [], + "fullContent": "import { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\nimport { DaisySize } from '../types';\n\ninterface IconArgs {\n 'custom-class'?: string;\n decorative: boolean;\n name: string;\n size: DaisySize;\n variant?: 'outlined' | 'solid';\n}\n\nconst meta: Meta = {\n title: 'Components/Icon',\n component: 'modus-wc-icon',\n args: {\n 'custom-class': '',\n decorative: false,\n name: 'alert',\n size: 'md',\n },\n argTypes: {\n size: {\n control: { type: 'select' },\n options: ['xs', 'sm', 'md', 'lg'],\n },\n variant: {\n control: { type: 'select' },\n options: ['outlined', 'solid'],\n },\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nconst Template: Story = {\n render: (args) => {\n // prettier-ignore\n return html`\n\n\n `;\n },\n};\n\nexport const Default: Story = { ...Template };\n\nexport const CustomColor: Story = {\n render: (args) => {\n // prettier-ignore\n return html`\n\n\n\n `;\n },\n};\n\nexport const CustomIcons: Story = {\n args: {\n 'custom-class': 'icon-font tc-icon-cloud-queue',\n decorative: false,\n name: '',\n size: 'lg',\n },\n decorators: [\n (story) => html`\n \n ${story()}\n `,\n ],\n render: (args) => {\n // prettier-ignore\n return html`\n\n\n `;\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n // Create a unique shadow host for icon component\n if (!customElements.get('icon-shadow-host')) {\n const IconShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-icon',\n propsMapper: (v: IconArgs, el: HTMLElement) => {\n const iconEl = el as unknown as {\n customClass: string;\n decorative: boolean;\n name: string;\n size: string;\n variant: string;\n };\n iconEl.customClass = v['custom-class'] || '';\n iconEl.decorative = Boolean(v.decorative);\n iconEl.name = v.name;\n iconEl.size = v.size;\n iconEl.variant = v.variant ?? 'outlined';\n },\n });\n customElements.define('icon-shadow-host', IconShadowHost);\n }\n\n return html``;\n },\n};\nexport const Migration: Story = {\n parameters: {\n docs: {\n description: {\n story: `\n#### Breaking Changes\n\n - Requires Modus Icons to be installed in the host application see [Modus Icon Usage](/docs/documentation-modus-icon-usage--docs).\n - The \\`color\\` property has been removed in favor of using CSS for styling.\n - The \\`iconClick\\` event has been removed. Use the \\`click\\` event on the host element instead.\n - In 1.0 the \\`size\\` prop accepted any numeric string (e.g., \\`'16'\\`, \\`'24'\\`, \\`'32'\\`) to set the icon's\n width and height. 2.0 uses preset sizes: \\`sm\\`, \\`md\\`, \\`lg\\`, and can use CSS for custom sizes.\n\n#### Prop Mapping\n\n| 1.0 Prop | 2.0 Prop | Notes |\n|----------|----------|------------------------------------------------------|\n| color | | Not carried over, use CSS instead |\n| name | name | |\n| size | size | Numeric values changed to \\`sm\\`, \\`md\\`, \\`lg\\`, use CSS for custom sizes |\n\n#### Event Mapping\n\n| 1.0 Event | 2.0 Event | Notes |\n|-----------|-----------|---------------------------------------------------------------|\n| iconClick | | Not carried over, use \\`click\\` event on host element instead |\n `,\n },\n },\n controls: { disable: true },\n canvas: { disable: true },\n },\n render: () => html`
    `,\n};\n" + }, + "availableIcons": { + "total": 638, + "variants": [ + "solid", + "outlined" + ], + "note": "All icons are available in both solid and outlined variants.", + "source": "https://modus-icons.trimble.com/", + "iconNames": [ + "3d_terrain", + "accessibility", + "accessibility_circle", + "add", + "add_bold", + "add_circle", + "add_heavy", + "address", + "advanced_instructions", + "ai_stars", + "alarm_add", + "alarm_off", + "alarm_on", + "alert", + "alert_outlined", + "align_bottom", + "align_center_horiz", + "align_center_vert", + "align_left", + "align_right", + "align_top", + "angle_90", + "antenna", + "apps", + "arc", + "arrow_back", + "arrow_down", + "arrow_down_circle", + "arrow_expand_diagonal_left", + "arrow_expand_diagonal_right", + "arrow_left", + "arrow_left_circle", + "arrow_next", + "arrow_right", + "arrow_right_circle", + "arrow_up", + "arrow_up_circle", + "artificial_intelligence", + "backup_restore_cloud", + "backup_restore_file", + "bar_graph", + "bar_graph_line", + "bar_graph_square", + "barcode", + "battery_0_horizontal", + "battery_0_vertical", + "battery_25_horizontal", + "battery_25_vertical", + "battery_50_horizontal", + "battery_50_vertical", + "battery_75_horizontal", + "battery_75_vertical", + "battery_charging_horizontal", + "battery_charging_vertical", + "battery_full_horizontal", + "battery_full_vertical", + "between", + "bolt", + "bookings", + "bookings_open", + "box_select", + "briefcase", + "brightness", + "brush", + "bug", + "bug_report", + "building_corporate", + "buildings", + "calculate", + "calculator", + "calculator_symbols", + "calendar", + "calendar_add", + "calendar_and_key", + "calendar_blank", + "calendar_booking", + "calendar_cancel", + "calendar_check", + "calendar_clock", + "calendar_event", + "calendar_loading_unloading", + "calendar_loading_unloading_date", + "calendar_plus", + "calendar_rebook", + "calendar_reserve", + "calendar_show", + "calendar_time_slot", + "calendar_week", + "camera", + "camera_disabled", + "camera_photo_add", + "cancel_circle", + "cancel_square", + "cancel_square_outlined", + "caret_down", + "caret_down_bold", + "caret_left", + "caret_left_bold", + "caret_right", + "caret_right_bold", + "caret_up", + "caret_up_bold", + "cell_merge", + "cell_properties", + "cell_split", + "certificate", + "chat", + "check", + "check_bold", + "check_circle", + "check_circle_outlined", + "check_heavy", + "chevron_double_down", + "chevron_double_left", + "chevron_double_right", + "chevron_double_up", + "chevron_left", + "chevron_left_bold", + "chevron_right", + "chevron_right_bold", + "circle_notch", + "circle_outline", + "clipboard", + "clipboard_actions", + "clipboard_check", + "clipboard_empty", + "clipboard_planning", + "clock", + "clock_add", + "clock_checkmark", + "clock_delay_warning", + "clock_delayed", + "clock_locked", + "clock_question_mark", + "close", + "close_bold", + "close_heavy", + "cloud", + "cloud_connected", + "cloud_disconnected", + "cloud_download", + "cloud_upload", + "code", + "coffee_cup", + "collapse", + "collapse_bold", + "color_picker", + "column_copy", + "column_cut", + "column_delete", + "column_insert_after", + "column_insert_before", + "column_paste_after", + "column_paste_before", + "column_properties", + "columns", + "combine", + "comment", + "comment_add", + "comment_empty", + "comment_message", + "comment_message_disabled", + "compare_arrows", + "compass", + "component", + "contacts", + "contrast", + "control_compass", + "copy_content", + "costs", + "credit_card", + "crop", + "cube", + "curly_brackets", + "cursor", + "cursor_add", + "cursor_remove", + "dashboard", + "data_missing", + "data_transfer_off", + "day_mostly_cloudy", + "day_partly_cloudy", + "delete", + "delivery_truck", + "delivery_truck_allocate", + "delivery_truck_motion", + "document", + "download", + "download_line", + "download_xls", + "drag_corner", + "drag_horizontal", + "drag_indicator", + "drag_vertical", + "drivers", + "drizzle", + "drone", + "earnings_statement", + "edit_combination", + "email", + "email_add", + "envelope", + "eraser", + "exclamation_mark", + "expand", + "expand_bold", + "expand_less", + "expand_less_bold", + "expand_less_circle", + "expand_more", + "expand_more_bold", + "expand_more_circle", + "export", + "factory", + "fast_forward", + "fast_rewind", + "file", + "file_bar_graph", + "file_check_in", + "file_check_out", + "file_cloud", + "file_copy", + "file_edit", + "file_merge", + "file_missing", + "file_new", + "file_secure", + "file_table", + "file_type_bmpf", + "file_type_csv", + "file_type_cur", + "file_type_doc", + "file_type_ico", + "file_type_key", + "file_type_log", + "file_type_numbers", + "file_type_pdf", + "file_type_pem", + "file_type_rfi", + "file_type_rfq", + "file_type_rtf", + "file_type_text", + "file_type_tif", + "file_type_tmp", + "file_type_xls", + "filter", + "filter_list", + "filter_list_off", + "filter_off", + "first_page", + "flag", + "flag_finish", + "floorplan", + "flowchart", + "fog", + "folder_closed", + "folder_locked", + "folder_new", + "folder_open", + "folder_personal", + "folder_project", + "folder_public", + "folder_share", + "folder_unlocked", + "footprints", + "forestry", + "forklift", + "forklift_packages", + "form_send", + "frame", + "freight_market", + "freight_matching", + "freight_trolley", + "full_screen", + "function", + "gantt_chart", + "gavel", + "gears", + "greater_than_equal_to", + "group_items", + "hail", + "hail_heavy", + "hail_light", + "hammer", + "hand", + "hard_hat", + "headset", + "heart", + "helicopter", + "help", + "help_outlined", + "history", + "home", + "hourglass", + "ice", + "icons_shapes", + "image", + "image_add", + "image_disabled", + "image_scene", + "in_cab_device", + "in_field_device", + "info", + "info_outlined", + "info_token", + "inspect", + "invoice", + "invoice_euro", + "invoice_pound", + "invoice_yen", + "item_begins_with", + "item_contains", + "item_does_not_contain", + "item_does_not_equal", + "item_ends_with", + "item_equals", + "key", + "key_api", + "keyboard", + "keyboard_keys", + "labs", + "language", + "last_page", + "launch", + "launch_bold", + "layer", + "learn", + "less_than_equal_to", + "lightbulb_off", + "lightbulb_on", + "lightning", + "line_diagonal", + "line_graph", + "link", + "link_broken", + "link_off", + "list_bulleted", + "list_numbered", + "list_shapes", + "location", + "location_add", + "location_add_multiple", + "location_arrow", + "location_disabled", + "location_point", + "lock", + "lock_open", + "magic_wand", + "manage_accounts", + "manage_people", + "map", + "map_2d", + "map_edit", + "map_marker", + "map_marker_circle", + "map_marker_multiple", + "map_markers", + "map_poi", + "master_data", + "megaphone", + "menu", + "menu_circle", + "mic", + "mix", + "mobile_app_version", + "monetarization", + "moon", + "more_circle", + "more_horizontal", + "more_vertical", + "mouse", + "move", + "move_last_down", + "move_last_left", + "move_last_right", + "move_last_up", + "night_mostly_cloudy", + "night_partly_cloudy", + "no_package", + "no_truck", + "not_synced_bold", + "notifications", + "notifications_off", + "object_mirror", + "object_outline", + "object_rotate", + "offers", + "overcast", + "package", + "package_delivered", + "package_delivery", + "package_missing", + "package_pickup", + "package_sent", + "packages", + "pager", + "paint_bucket", + "palette", + "pan", + "paper_plane", + "paperclip", + "password", + "pause_circle", + "payment_instant", + "pen", + "pencil", + "people_add", + "people_couple", + "people_group", + "person", + "person_account", + "person_add", + "person_clock", + "person_edit", + "person_remove", + "phone", + "phone_call", + "phone_hang_up", + "phone_mobile", + "pin", + "pin_add", + "pin_straight", + "pin_straight_cancel", + "play_circle", + "point_marker_tool", + "polygon", + "polygon_area_tool", + "polygon_concave", + "polygon_cone", + "polygon_drag_rectangle", + "polygon_line_tool", + "polygon_merge", + "polygon_select", + "printer", + "pulse", + "qr_code", + "question", + "quick_login", + "rain", + "rain_heavy", + "rain_icy", + "raindrop", + "redo", + "redo_bold", + "refresh", + "refresh_bold", + "remove", + "remove_bold", + "remove_circle", + "remove_heavy", + "reply", + "reservations", + "rewind", + "row_add", + "row_copy", + "row_cut", + "row_delete", + "row_highlighted", + "row_insert_after", + "row_insert_before", + "row_paste_after", + "row_paste_before", + "row_properties", + "row_remove", + "rows_show_less", + "rows_show_more", + "rss_feed", + "ruler", + "satellite", + "save_as", + "save_disk", + "scan_barcode", + "schema", + "scissors", + "screen", + "screenshot", + "search", + "select_link_add", + "server", + "server_round", + "service_rep", + "settings", + "share", + "shield", + "shopping_cart", + "shopping_cart_minus", + "shopping_cart_plus", + "shortcut", + "shovel", + "show_less_caret", + "show_more_caret", + "sign_in", + "sign_out", + "signal", + "smiley_dissatisfied", + "smiley_dissatisfied_outlined", + "smiley_neutral", + "smiley_neutral_outlined", + "smiley_satisfied", + "smiley_satisfied_outlined", + "smiley_somewhat_dissatisfied", + "smiley_somewhat_dissatisfied_outlined", + "smiley_somewhat_satisfied", + "smiley_somewhat_satisfied_outlined", + "snow_heavy", + "snow_light", + "snow_particle", + "snowflake", + "snowflakes", + "sort", + "sort_alpha_down", + "sort_alpha_up", + "sort_arrow_down", + "sort_arrow_up", + "sort_down", + "sort_up", + "stamp", + "star", + "star_half", + "star_locked", + "star_northern", + "star_outlined", + "stars", + "stop_circle", + "street_measurement", + "submit_expense", + "sun", + "swap", + "switch_account", + "switch_left", + "switch_right", + "sync", + "sync_bold", + "sync_off", + "table", + "tablet", + "tag", + "tag_disabled", + "template", + "text_align_left", + "text_align_right", + "text_bold", + "text_centered", + "text_grow", + "text_input", + "text_input_long", + "text_input_short", + "text_italic", + "text_marker", + "text_shrink", + "text_strikethrough", + "text_truncated", + "text_underlined", + "thermometer_cold", + "thermometer_hot", + "thumbs_down", + "thumbs_up", + "thunderstorm_heavy", + "thunderstorm_light", + "ticket", + "ticket_plane", + "time_off_work", + "time_slot_not_reserved", + "time_slot_reserved", + "timer", + "timer_countdown", + "timesheet", + "timesheet_approve", + "toggle_center", + "toggle_left_panel", + "toggle_off", + "toggle_on", + "toggle_right_panel", + "traffic_cone", + "trailer", + "tree_structure", + "triangle_down", + "triangle_left", + "triangle_right", + "triangle_up", + "trimble_logo", + "trip_list", + "truck_add", + "truck_circle", + "truck_circle_eu", + "truck_warning_delay", + "tune", + "tune_circle", + "uncombine", + "undo", + "undo_bold", + "unfold_less", + "unfold_more", + "unit_selector", + "unsorted_arrows", + "update", + "upload", + "upload_xls", + "user_account", + "user_active", + "user_guide", + "user_inactive", + "user_passkey", + "user_permissions", + "video", + "video_add", + "video_disabled", + "view_grid", + "view_list", + "visibility_off", + "visibility_on", + "volume_down", + "volume_mute", + "volume_up", + "vr_xr", + "warehouse", + "warning", + "warning_outlined", + "web", + "wheelbarrow", + "widgets", + "wifi", + "wifi_no_internet", + "wifi_off", + "wind", + "window", + "window_dock_undock", + "window_fit", + "window_resize", + "window_side_panel", + "window_template", + "window_views", + "window_wireframe", + "wintery_mix", + "wrench", + "zoom_box", + "zoom_in", + "zoom_out" + ] + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-loader.json b/mcp/versions/1.5.0/component-docs/modus-wc-loader.json new file mode 100644 index 000000000..c451ccd2b --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-loader.json @@ -0,0 +1,65 @@ +{ + "description": "A customizable loader component used to indicate the loading of content", + "properties": [ + { + "name": "color", + "type": "LoaderColor", + "description": "The color of the loader.", + "default": "'primary'", + "mutable": false, + "end_line": 39 + }, + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the loader element.", + "default": "''", + "mutable": false, + "end_line": 42 + }, + { + "name": "size", + "type": "DaisySize", + "description": "The size of the loader.", + "default": "'md'", + "mutable": false, + "end_line": 45 + }, + { + "name": "variant", + "type": "LoaderVariant", + "description": "The variant of the loader.", + "default": "'spinner'", + "mutable": false, + "end_line": 48 + } + ], + "events": [], + "methods": [], + "slots": [], + "examples": { + "basic": "", + "variations": [], + "args": { + "color": "'primary'", + "custom-class": "''", + "size": "'md'", + "variant": "'spinner'" + }, + "argTypes": {}, + "usage": [] + }, + "tag": "modus-wc-loader", + "storyExample": { + "template": "", + "args": { + "color": "'primary'", + "custom-class": "''", + "size": "'md'", + "variant": "'spinner'" + }, + "argTypes": {}, + "events": [], + "fullContent": "import { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { LoaderColor, LoaderVariant } from './modus-wc-loader';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\nimport { DaisySize } from '../types';\n\ninterface LoaderArgs {\n color: LoaderColor;\n 'custom-class'?: string;\n size: DaisySize;\n variant: LoaderVariant;\n}\n\nconst meta: Meta = {\n title: 'Components/Loader',\n component: 'modus-wc-loader',\n args: {\n color: 'primary',\n 'custom-class': '',\n size: 'md',\n variant: 'spinner',\n },\n argTypes: {\n color: {\n control: { type: 'select' },\n options: [\n 'primary',\n 'secondary',\n 'accent',\n 'neutral',\n 'info',\n 'success',\n 'warning',\n 'error',\n ],\n },\n size: {\n control: { type: 'select' },\n options: ['xs', 'sm', 'md', 'lg'],\n },\n variant: {\n control: { type: 'select' },\n options: ['ball', 'bars', 'dots', 'infinity', 'ring', 'spinner'],\n },\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nexport const Default: Story = {\n render: (args) => {\n return html`\n \n `;\n },\n};\n\nexport const Ball: Story = {\n render: (args) => {\n return html`\n \n `;\n },\n};\n\nexport const Bars: Story = {\n render: (args) => {\n return html`\n \n `;\n },\n};\n\nexport const Dots: Story = {\n render: (args) => {\n return html`\n \n `;\n },\n};\n\n// eslint-disable-next-line no-shadow-restricted-names\nexport const Infinity: Story = {\n render: (args) => {\n return html`\n \n `;\n },\n};\n\nexport const Ring: Story = {\n render: (args) => {\n return html`\n \n `;\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n if (!customElements.get('loader-shadow-host')) {\n const LoaderShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-loader',\n propsMapper: (v: LoaderArgs, el: HTMLElement) => {\n const loaderEl = el as unknown as {\n color: string;\n customClass: string;\n size: string;\n variant: string;\n };\n loaderEl.color = v.color;\n loaderEl.customClass = v['custom-class'] || '';\n loaderEl.size = v.size;\n loaderEl.variant = v.variant;\n },\n });\n customElements.define('loader-shadow-host', LoaderShadowHost);\n }\n\n return html``;\n },\n};\nexport const Migration: Story = {\n parameters: {\n docs: {\n description: {\n story: `\n#### Breaking Changes\n\n - **Color value changes**: \\`dark\\` and \\`tertiary\\` values from 1.0 have been removed.\n 1.0 value \\`danger\\` has been renamed to \\`error\\` in 2.0. Values \\`accent\\`, \\`info\\`, and \\`neutral\\` are new options in 2.0.\n - In 1.0 \\`size\\` used direct CSS size value strings (e.g., \\`'12rem'\\`). In 2.0 \\`size\\` is now defined by predefined values (\\`xs\\`, \\`sm\\`, \\`md\\`, \\`lg\\`), and CSS can be used for custom sizes.\n - Added new \\`variant\\` prop to specify the loader type in 2.0.\n\n#### Prop Mapping\n\n| 1.0 Prop | 2.0 Prop | Notes |\n|----------|----------|--------------------------------------------------------------------------------------------------------|\n| color | color | \\`dark\\` and \\`tertiary\\` from version 1.0 have been removed, \\`danger\\` has been renamed to \\`error\\` |\n| size | size | Now uses predefined sizes (\\`xs\\`, \\`sm\\`, \\`md\\`, \\`lg\\`), use CSS for custom sizes. |\n `,\n },\n },\n // To hide the actual story rendering and only show docs:\n controls: { disable: true },\n canvas: { disable: true },\n },\n // Simple render function or leave it empty\n render: () => html`
    `,\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-logo.json b/mcp/versions/1.5.0/component-docs/modus-wc-logo.json new file mode 100644 index 000000000..480e0d43b --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-logo.json @@ -0,0 +1,61 @@ +{ + "description": "A component for displaying Trimble product logos with support for both fixed and scalable sizing. Provides consistent branding across applications with various product logo options. Logo colors automatically adapt to the active Modus theme via CSS variables.", + "properties": [ + { + "name": "name", + "type": "LogoName", + "description": "The name of the logo to display. Accepts values like 'trimble', 'viewpoint_field_view', etc.", + "default": null, + "mutable": false, + "end_line": 39 + }, + { + "name": "emblem", + "type": "boolean", + "description": "Show emblem version (icon only) instead of full logo", + "default": "false", + "mutable": false, + "end_line": 42 + }, + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the logo container.", + "default": "''", + "mutable": false, + "end_line": 45 + }, + { + "name": "alt", + "type": "string", + "description": "The alt text for accessibility. If not provided, defaults to the logo name.", + "default": null, + "mutable": false, + "end_line": 48 + } + ], + "events": [], + "methods": [], + "slots": [], + "examples": { + "basic": "", + "variations": [], + "args": { + "name": "'trimble'", + "emblem": "false" + }, + "argTypes": {}, + "usage": [] + }, + "tag": "modus-wc-logo", + "storyExample": { + "template": "", + "args": { + "name": "'trimble'", + "emblem": "false" + }, + "argTypes": {}, + "events": [], + "fullContent": "import { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\nimport { LogoName } from '../types';\nimport { getAvailableLogos } from './logo-constants';\n\ninterface LogoArgs {\n name: LogoName;\n emblem: boolean;\n alt?: string;\n 'custom-class'?: string;\n}\n\nconst meta: Meta = {\n title: 'Components/Logo',\n component: 'modus-wc-logo',\n args: {\n name: 'trimble',\n emblem: false,\n },\n argTypes: {\n name: {\n control: { type: 'select' },\n options: getAvailableLogos(),\n },\n emblem: {\n control: { type: 'boolean' },\n },\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nconst Template: Story = {\n render: (args) => {\n return html`\n \n `;\n },\n};\n\nexport const Default: Story = { ...Template };\n\nexport const CustomSizeWithClass: Story = {\n render: () => {\n return html`\n \n
    \n
    \n

    Full Logos - Custom Sizes

    \n \n \n \n \n
    \n
    \n\n
    \n

    Emblems - Custom Sizes

    \n \n \n \n \n
    \n
    \n \n \n `;\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n if (!customElements.get('logo-shadow-host')) {\n const LogoShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-logo',\n propsMapper: (v: LogoArgs, el: HTMLElement) => {\n const logoEl = el as unknown as {\n name: string;\n emblem: boolean;\n alt: string;\n customClass: string;\n };\n logoEl.name = v.name;\n logoEl.emblem = Boolean(v.emblem);\n logoEl.alt = v.alt ?? '';\n logoEl.customClass = v['custom-class'] || '';\n },\n });\n customElements.define('logo-shadow-host', LogoShadowHost);\n }\n\n return html``;\n },\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-menu-item.json b/mcp/versions/1.5.0/component-docs/modus-wc-menu-item.json new file mode 100644 index 000000000..1511ce060 --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-menu-item.json @@ -0,0 +1,165 @@ +{ + "description": "A customizable menu item component used to display the item portion of a menu. This component supports a 'start-icon' `` that allows for custom icons to be placed at the beginning of the item.", + "properties": [ + { + "name": "bordered", + "type": "boolean", + "description": "", + "default": null, + "mutable": false, + "end_line": 34 + }, + { + "name": "checkbox", + "type": "boolean", + "description": "If true, renders a checkbox at the start of the menu item.", + "default": null, + "mutable": false, + "end_line": 37 + }, + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the li element.", + "default": "''", + "mutable": false, + "end_line": 40 + }, + { + "name": "disabled", + "type": "boolean", + "description": "The disabled state of the menu item.", + "default": null, + "mutable": false, + "end_line": 43 + }, + { + "name": "label", + "type": "string", + "description": "The text rendered in the menu item.", + "default": "''", + "mutable": false, + "end_line": 46 + }, + { + "name": "selected", + "type": "boolean", + "description": "The selected state of the menu item.", + "default": null, + "mutable": true, + "end_line": 49 + }, + { + "name": "focused", + "type": "boolean", + "description": "The focused state of the menu item.", + "default": null, + "mutable": false, + "end_line": 52 + }, + { + "name": "size", + "type": "ModusSize", + "description": "The size of the menu item.", + "default": "'md'", + "mutable": false, + "end_line": 55 + }, + { + "name": "subLabel", + "type": "string", + "description": "The text rendered beneath the label.", + "default": null, + "mutable": false, + "end_line": 58 + }, + { + "name": "tooltipContent", + "type": "string", + "description": "The tooltip text to display when hovering over the menu item.", + "default": null, + "mutable": false, + "end_line": 61 + }, + { + "name": "tooltipPosition", + "type": "'auto' | 'top' | 'right' | 'bottom' | 'left'", + "description": "The position of the tooltip relative to the menu item.", + "default": "'auto'", + "mutable": false, + "end_line": 65 + }, + { + "name": "value", + "type": "string", + "description": "The unique identifying value of the menu item.", + "default": "''", + "mutable": false, + "end_line": 68 + }, + { + "name": "hasSubmenu", + "type": "boolean", + "description": "Whether this menu item has a collapsible submenu. When true, the item will show a caret and handle toggle behavior.", + "default": null, + "mutable": false, + "end_line": 71 + } + ], + "events": [ + { + "name": "itemSelect", + "detail": "{ value: string; selected?: boolean; }", + "description": "Event emitted when a menu item is selected.", + "end_line": 82 + } + ], + "methods": [ + { + "name": "collapseSubmenu", + "signature": "()", + "parameters": "", + "returnType": "P", + "description": "Public method to collapse the submenu if it's expanded", + "end_line": 131 + } + ], + "slots": [ + { + "name": "start-icon", + "description": "Slot for start-icon content" + }, + { + "name": "default", + "description": "Slot for default content" + } + ], + "examples": { + "basic": "\n \n", + "variations": [], + "args": { + "label": "'Menu Item'", + "size": "'md'", + "value": "'menuItem'" + }, + "argTypes": {}, + "usage": [], + "events": [ + "itemSelect" + ] + }, + "tag": "modus-wc-menu-item", + "storyExample": { + "template": "\n \n", + "args": { + "label": "'Menu Item'", + "size": "'md'", + "value": "'menuItem'" + }, + "argTypes": {}, + "events": [ + "itemSelect" + ], + "fullContent": "import { withActions } from '@storybook/addon-actions/decorator';\nimport { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\nimport { ModusSize } from '../types';\n\ninterface MenuItemArgs {\n bordered?: boolean;\n checkbox?: boolean;\n 'custom-class'?: string;\n disabled?: boolean;\n focused?: boolean;\n 'has-submenu'?: boolean;\n label: string;\n selected?: boolean;\n size?: ModusSize;\n 'sub-label'?: string;\n 'tooltip-content'?: string;\n 'tooltip-position'?: 'auto' | 'top' | 'right' | 'bottom' | 'left';\n value: string;\n}\n\nconst meta: Meta = {\n title: 'Components/Menu Item',\n component: 'modus-wc-menu-item',\n args: {\n label: 'Menu Item',\n size: 'md',\n value: 'menuItem',\n },\n argTypes: {\n size: {\n control: { type: 'select' },\n options: ['sm', 'md', 'lg'],\n },\n 'tooltip-position': {\n control: { type: 'select' },\n options: ['auto', 'top', 'right', 'bottom', 'left'],\n },\n },\n decorators: [withActions],\n parameters: {\n actions: {\n handles: ['itemSelect'],\n },\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nconst Template: Story = {\n render: (args) => {\n // prettier-ignore\n return html`\n\n \n\n `;\n },\n};\n\nexport const Default: Story = { ...Template };\n\nexport const WithIcon: Story = {\n render: (args) => {\n // prettier-ignore\n return html`\n\n \n \n \n\n `;\n },\n};\nexport const WithCheckbox: Story = {\n args: {\n checkbox: true,\n },\n render: (args) => {\n // prettier-ignore\n return html`\n\n \n\n `;\n },\n};\n\nexport const WithTooltip: Story = {\n args: {\n 'tooltip-content': 'This is a tooltip for the menu item',\n 'tooltip-position': 'top',\n },\n render: (args) => {\n // prettier-ignore\n return html`\n\n \n\n `;\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n // Create a unique shadow host for menu-item component\n if (!customElements.get('menu-item-shadow-host')) {\n const MenuItemShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-menu',\n propsMapper: (v: MenuItemArgs, el: HTMLElement) => {\n const menuEl = el as unknown as {\n ariaLabel: string;\n };\n menuEl.ariaLabel = 'Shadow DOM Menu';\n\n // Get or create menu item\n let menuItem = el.querySelector('modus-wc-menu-item');\n if (!menuItem) {\n menuItem = document.createElement('modus-wc-menu-item');\n el.innerHTML = '';\n el.appendChild(menuItem);\n }\n\n // Update properties on the existing element\n const menuItemEl = menuItem as unknown as {\n ariaLabel: string;\n bordered: boolean;\n checkbox: boolean;\n customClass: string;\n disabled: boolean;\n focused: boolean;\n label: string;\n selected: boolean;\n size: string;\n subLabel: string;\n tooltipContent: string;\n tooltipPosition: string;\n value: string;\n };\n\n menuItemEl.ariaLabel = 'Menu item in shadow DOM';\n menuItemEl.bordered = Boolean(v.bordered);\n menuItemEl.checkbox = Boolean(v.checkbox);\n menuItemEl.customClass = v['custom-class'] || '';\n menuItemEl.disabled = Boolean(v.disabled);\n menuItemEl.focused = Boolean(v.focused);\n menuItemEl.label = v.label;\n menuItemEl.selected = Boolean(v.selected);\n menuItemEl.size = v.size || 'md';\n menuItemEl.subLabel = v['sub-label'] || '';\n menuItemEl.tooltipContent = v['tooltip-content'] || '';\n menuItemEl.tooltipPosition = v['tooltip-position'] || 'auto';\n menuItemEl.value = v.value;\n },\n });\n customElements.define('menu-item-shadow-host', MenuItemShadowHost);\n }\n\n return html``;\n },\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-menu.json b/mcp/versions/1.5.0/component-docs/modus-wc-menu.json new file mode 100644 index 000000000..cf4074d43 --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-menu.json @@ -0,0 +1,106 @@ +{ + "description": "A customizable menu component used to display a list of li elements vertically or horizontally. The component supports a `` for injecting custom li elements inside the ul element.", + "properties": [ + { + "name": "bordered", + "type": "boolean", + "description": "Indicates that the menu should have a border.", + "default": null, + "mutable": false, + "end_line": 34 + }, + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the ul element.", + "default": "''", + "mutable": false, + "end_line": 37 + }, + { + "name": "orientation", + "type": "Orientation", + "description": "The orientation of the menu.", + "default": "'vertical'", + "mutable": false, + "end_line": 40 + }, + { + "name": "selectionMode", + "type": "SelectionMode", + "description": "The selection mode of the menu.", + "default": "'single'", + "mutable": false, + "end_line": 43 + }, + { + "name": "size", + "type": "ModusSize", + "description": "The size of the menu.", + "default": "'md'", + "mutable": false, + "end_line": 51 + }, + { + "name": "isSubMenu", + "type": "boolean", + "description": "Indicates that this menu is a submenu (dropdown).", + "default": null, + "mutable": false, + "end_line": 54 + } + ], + "events": [ + { + "name": "menuFocusout", + "detail": "FocusEvent", + "description": "Event emitted when the menu loses focus.", + "end_line": 57 + }, + { + "name": "menuSelectionChange", + "detail": "{ selectedItems: HTMLElement[]; }", + "description": "Event emitted when the selection changes in multiple selection mode. Emits the array of currently selected menu item elements.", + "end_line": 62 + } + ], + "methods": [], + "slots": [ + { + "name": "default", + "description": "Slot for default content" + } + ], + "examples": { + "basic": "\n \n \n \n \n \n \n \n \n \n \n", + "variations": [], + "args": { + "orientation": "'vertical'", + "selection-mode": "'single'", + "size": "'md'" + }, + "argTypes": {}, + "usage": [], + "events": [ + "menuFocusout", + "menuSelectionChange", + "itemSelect" + ] + }, + "tag": "modus-wc-menu", + "storyExample": { + "template": "\n \n \n \n \n \n \n \n \n \n \n", + "args": { + "orientation": "'vertical'", + "selection-mode": "'single'", + "size": "'md'" + }, + "argTypes": {}, + "events": [ + "menuFocusout", + "menuSelectionChange", + "itemSelect" + ], + "fullContent": "import { withActions } from '@storybook/addon-actions/decorator';\nimport { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { ref } from 'lit/directives/ref.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\nimport { ModusSize, Orientation, SelectionMode } from '../types';\n\ninterface MenuArgs {\n bordered?: boolean;\n 'custom-class'?: string;\n orientation?: Orientation;\n 'selection-mode'?: SelectionMode;\n size?: ModusSize;\n}\n\nconst meta: Meta = {\n title: 'Components/Menu',\n component: 'modus-wc-menu',\n args: {\n orientation: 'vertical',\n 'selection-mode': 'single',\n size: 'md',\n },\n argTypes: {\n orientation: {\n control: { type: 'select' },\n options: ['horizontal', 'vertical'],\n },\n 'selection-mode': {\n control: { type: 'select' },\n options: ['single', 'multiple'],\n },\n size: {\n control: { type: 'select' },\n options: ['xs', 'sm', 'md', 'lg'],\n },\n },\n decorators: [withActions],\n parameters: {\n actions: {\n handles: ['menuFocusout', 'menuSelectionChange', 'itemSelect'],\n },\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nexport const Default: Story = {\n render: (args) => {\n // prettier-ignore\n return html`\n\n \n \n \n \n \n \n \n \n \n \n\n `;\n },\n};\n\nexport const MultiSelect: Story = {\n args: {\n 'selection-mode': 'multiple',\n },\n render: (args) => {\n let outputEl: Element | undefined;\n\n const handleSelectionChange = (\n e: CustomEvent<{ selectedItems: HTMLElement[] }>\n ) => {\n if (!outputEl) return;\n const { selectedItems } = e.detail;\n outputEl.textContent =\n selectedItems.length > 0\n ? `Selected: ${selectedItems.map((i) => i.getAttribute('value')).join(', ')}`\n : 'Selected: none';\n };\n\n // prettier-ignore\n return html`\n\n \n \n \n \n \n\n

    { outputEl = el; })} style=\"font-size: 0.875rem; margin-top: 0.5rem; color: var(--modus-wc-color-gray-6);\">Selected: none

    \n `;\n },\n};\n\nexport const CustomMenu: Story = {\n render: () => {\n // prettier-ignore\n return html`\n\n\n
  • \n
    \n \n \n \n \n
    Parent
    \n
    \n \n \n \n
    \n
    \n
  • \n
  • \n
    \n \n \n \n
    \n
    Child
    \n
    \n \n \n \n
    \n
    \n
  • \n
  • \n
    \n \n \n \n
    \n
    Child
    \n
    \n \n \n \n
    \n
    \n
  • \n
  • \n
    \n \n \n \n \n
    Parent
    \n
    \n \n \n \n
    \n
    \n
  • \n\n `;\n },\n};\n\nexport const CollapsibleMenu: Story = {\n render: () => {\n return html`\n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n\n \n \n \n \n \n \n \n \n \n \n\n \n `;\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n // Create a unique shadow host for menu component\n if (!customElements.get('menu-shadow-host')) {\n const MenuShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-menu',\n propsMapper: (v: MenuArgs, el: HTMLElement) => {\n const menuEl = el as unknown as {\n ariaLabel: string;\n bordered: boolean;\n customClass: string;\n orientation: string;\n size: string;\n };\n menuEl.ariaLabel = 'Shadow DOM Menu';\n menuEl.bordered = Boolean(v.bordered);\n menuEl.customClass = v['custom-class'] || '';\n menuEl.orientation = v.orientation || 'vertical';\n menuEl.size = v.size || 'md';\n\n // Only set innerHTML once on initial creation\n if (!el.querySelector('modus-wc-menu-item')) {\n el.innerHTML = `\n \n \n \n \n \n \n \n \n \n \n `;\n }\n },\n });\n customElements.define('menu-shadow-host', MenuShadowHost);\n }\n\n return html``;\n },\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-modal.json b/mcp/versions/1.5.0/component-docs/modus-wc-modal.json new file mode 100644 index 000000000..ae4db33da --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-modal.json @@ -0,0 +1,108 @@ +{ + "description": "A customizable modal component used to display content in a dialog. This component supports 'header', 'content', and 'footer' `` elements for inserting custom HTML.", + "properties": [ + { + "name": "backdrop", + "type": "'default' | 'static'", + "description": "The modal's backdrop. Specify 'static' for a backdrop that doesn't close the modal when clicked outside the modal content.", + "default": "'default'", + "mutable": false, + "end_line": 28 + }, + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply", + "default": "''", + "mutable": false, + "end_line": 31 + }, + { + "name": "fullscreen", + "type": "boolean", + "description": "Specifies whether the modal should be displayed full-screen", + "default": "false", + "mutable": false, + "end_line": 34 + }, + { + "name": "modalId", + "type": "string", + "description": "The ID of the inner dialog element", + "default": null, + "mutable": false, + "end_line": 37 + }, + { + "name": "position", + "type": "'bottom' | 'center' | 'top'", + "description": "Specifies the position of the modal", + "default": "'center'", + "mutable": false, + "end_line": 40 + }, + { + "name": "showClose", + "type": "boolean", + "description": "Specifies whether to show the close icon button at the top right of modal", + "default": "true", + "mutable": false, + "end_line": 43 + }, + { + "name": "showFullscreenToggle", + "type": "boolean", + "description": "Specifies whether to show the fullscreen toggle icon button", + "default": "false", + "mutable": false, + "end_line": 46 + } + ], + "events": [], + "methods": [], + "slots": [ + { + "name": "header", + "description": "Slot for header content" + }, + { + "name": "content", + "description": "Slot for content content" + }, + { + "name": "footer", + "description": "Slot for footer content" + } + ], + "examples": { + "basic": " handleModalVisibility('show')}>\n Open modal\n\n\n Modal Title\n This is sample modal content. \n handleModalVisibility('hide')}>\n Close\n \n\n${illustrativeScript}", + "variations": [], + "args": { + "backdrop": "'default'", + "custom-class": "''", + "fullscreen": "false", + "modal-id": "'my_modal_1'", + "position": "'center'", + "show-close": "true", + "show-fullscreen-toggle": "false" + }, + "argTypes": {}, + "usage": [] + }, + "tag": "modus-wc-modal", + "storyExample": { + "template": " handleModalVisibility('show')}>\n Open modal\n\n\n Modal Title\n This is sample modal content. \n handleModalVisibility('hide')}>\n Close\n \n\n${illustrativeScript}", + "args": { + "backdrop": "'default'", + "custom-class": "''", + "fullscreen": "false", + "modal-id": "'my_modal_1'", + "position": "'center'", + "show-close": "true", + "show-fullscreen-toggle": "false" + }, + "argTypes": {}, + "events": [], + "fullContent": "import { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\nimport { generateRandomId } from '../utils';\n\ninterface ModalArgs {\n backdrop: 'default' | 'static';\n 'custom-class'?: string;\n fullscreen: boolean;\n 'modal-id'?: string;\n position: 'bottom' | 'center' | 'top';\n 'show-close': boolean;\n 'show-fullscreen-toggle': boolean;\n}\n\nconst meta: Meta = {\n title: 'Components/Modal',\n component: 'modus-wc-modal',\n args: {\n backdrop: 'default',\n 'custom-class': '',\n fullscreen: false,\n 'modal-id': 'my_modal_1',\n position: 'center',\n 'show-close': true,\n 'show-fullscreen-toggle': false,\n },\n argTypes: {\n backdrop: {\n control: { type: 'select' },\n options: ['default', 'static'],\n },\n position: {\n control: { type: 'select' },\n options: ['bottom', 'center', 'top'],\n },\n },\n parameters: {\n layout: 'centered',\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\nconst illustrativeScript = html`\n \n`;\n\nexport const Default: Story = {\n render: (args) => {\n const modalId = `${args['modal-id']}${generateRandomId(4)}}`;\n\n const handleModalVisibility = (action: 'show' | 'hide') => {\n const modal = document.getElementById(modalId) as HTMLDialogElement;\n if (modal) {\n if (action === 'show') {\n modal.showModal();\n } else {\n modal.close();\n }\n }\n };\n\n // prettier-ignore\n return html`\n handleModalVisibility('show')}>\n Open modal\n\n\n Modal Title\n This is sample modal content. \n handleModalVisibility('hide')}>\n Close\n \n\n${illustrativeScript}\n `;\n },\n};\n\nexport const CustomWidthAndHeight: Story = {\n render: (args) => {\n const modalId = `${args['modal-id']}${generateRandomId(4)}}`;\n\n const handleModalVisibility = (action: 'show' | 'hide') => {\n const modal = document.getElementById(modalId) as HTMLDialogElement;\n if (modal) {\n if (action === 'show') {\n modal.showModal();\n } else {\n modal.close();\n }\n }\n };\n\n // prettier-ignore\n return html`\n\n handleModalVisibility('show')}>\n Open modal\n\n\n Modal Title\n

    Sample modal content.

    \n handleModalVisibility('hide')}>\n Close\n \n\n${illustrativeScript}\n `;\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n const modalId = `shadow-dom-modal`;\n\n const handleModalVisibility = (action: 'show' | 'hide') => {\n // The dialog lives inside the shadow host's shadowRoot, not in document\n const host = document.querySelector(\n 'modal-shadow-host'\n ) as HTMLElement & {\n shadowRoot: ShadowRoot;\n };\n const modal = host?.shadowRoot?.getElementById(\n modalId\n ) as HTMLDialogElement;\n if (modal) {\n if (action === 'show') modal.showModal();\n else modal.close();\n }\n };\n\n if (!customElements.get('modal-shadow-host')) {\n const ModalShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-modal',\n propsMapper: (v: ModalArgs, el: HTMLElement) => {\n const modalEl = el as unknown as {\n backdrop: string;\n customClass: string;\n fullscreen: boolean;\n modalId: string;\n position: string;\n showClose: boolean;\n showFullscreenToggle: boolean;\n };\n modalEl.backdrop = v.backdrop;\n modalEl.customClass = v['custom-class'] || '';\n modalEl.fullscreen = Boolean(v.fullscreen);\n modalEl.modalId = modalId;\n modalEl.position = v.position;\n modalEl.showClose = Boolean(v['show-close']);\n modalEl.showFullscreenToggle = Boolean(v['show-fullscreen-toggle']);\n if (!el.hasChildNodes()) {\n el.innerHTML = `Modal TitleThis is sample modal content.Close`;\n // Wire the footer close button to close the dialog\n const closeBtn = el.querySelector('modus-wc-button[slot=\"footer\"]');\n closeBtn?.addEventListener('buttonClick', () => {\n const dialog = el.querySelector('dialog') as HTMLDialogElement;\n dialog?.close();\n });\n }\n },\n });\n customElements.define('modal-shadow-host', ModalShadowHost);\n }\n\n // prettier-ignore\n return html`\n handleModalVisibility('show')}>\n Open modal\n\n\n `;\n },\n};\nexport const Migration: Story = {\n parameters: {\n docs: {\n description: {\n story: `\n#### Breaking Changes\n\n - Modal identification is now required via the \\`modal-id\\` prop.\n - 2.0 requires the use of slots for a fully customizable \\`header\\`, \\`content\\`, and \\`footer\\`.\n Primary and secondary buttons as well as \\`header-text\\` are no longer built-in.\n - In 1.0, modals had built-in open/close state management with methods. 2.0 uses the native HTML dialog\n element with \\`modal-id\\` to target the dialog with native \\`showModal()\\` and \\`close()\\` methods.\n\n#### Prop Mapping\n\n| 1.0 Prop | 2.0 Prop | Notes |\n|------------------------------|-------------------------|-----------------------------------------------|\n| aria-label | aria-label | |\n| backdrop | backdrop | |\n| fullscreen | fullscreen | |\n| header-text | | Not carried over, use \\`header\\` slot instead |\n| primary-button-aria-label | | Not carried over, use \\`footer\\` slot instead |\n| primary-button-disabled | | Not carried over, use \\`footer\\` slot instead |\n| primary-button-text | | Not carried over, use \\`footer\\` slot instead |\n| secondary-button-aria-label | | Not carried over, use \\`footer\\` slot instead |\n| secondary-button-disabled | | Not carried over, use \\`footer\\` slot instead |\n| secondary-button-text | | Not carried over, use \\`footer\\` slot instead |\n| show-fullscreen-toggle | show-fullscreen-toggle | |\n| z-index | | Not carried over, use CSS instead |\n\n#### Event Mapping\n\n| 1.0 Event | 2.0 Event | Notes |\n|----------------------|-----------|-----------------------------------------------------------------------------------|\n| closed | | Not carried over, use dialog \\`close()\\` event instead |\n| opened | | Not carried over, use dialog \\`showModal()\\` event instead |\n| primaryButtonClick | | Not carried over, handle with events on custom buttons in \\`footer\\` slot instead |\n| secondaryButtonClick | | Not carried over, handle with events on custom buttons in \\`footer\\` slot instead |\n `,\n },\n },\n controls: { disable: true },\n canvas: { disable: true },\n },\n render: () => html`
    `,\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-navbar.json b/mcp/versions/1.5.0/component-docs/modus-wc-navbar.json new file mode 100644 index 000000000..0ac90e3f4 --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-navbar.json @@ -0,0 +1,290 @@ +{ + "description": "A customizable navbar component used for top level navigation of all Trimble applications.", + "properties": [ + { + "name": "logoName", + "type": "LogoName", + "description": "The name of the logo to display. Supports any valid 'logo-name' from the 'modus-wc-logo' component. Defaults to 'trimble'.", + "default": "'trimble'", + "mutable": false, + "end_line": 94 + }, + { + "name": "appsMenuOpen", + "type": "boolean", + "description": "The open state of the apps menu.", + "default": "false", + "mutable": true, + "end_line": 97 + }, + { + "name": "condensed", + "type": "boolean", + "description": "Applies condensed layout and styling.", + "default": "false", + "mutable": false, + "end_line": 100 + }, + { + "name": "condensedMenuOpen", + "type": "boolean", + "description": "The open state of the condensed menu.", + "default": "false", + "mutable": true, + "end_line": 103 + }, + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the host element.", + "default": "''", + "mutable": false, + "end_line": 106 + }, + { + "name": "mainMenuOpen", + "type": "boolean", + "description": "The open state of the main menu.", + "default": "false", + "mutable": true, + "end_line": 109 + }, + { + "name": "notificationsMenuOpen", + "type": "boolean", + "description": "The open state of the notifications menu.", + "default": "false", + "mutable": true, + "end_line": 112 + }, + { + "name": "searchDebounceMs", + "type": "number", + "description": "Debounce time in milliseconds for search input changes. Default is 300ms.", + "default": "300", + "mutable": false, + "end_line": 115 + }, + { + "name": "searchInputOpen", + "type": "boolean", + "description": "The open state of the search input.", + "default": "false", + "mutable": true, + "end_line": 118 + }, + { + "name": "textOverrides", + "type": "INavbarTextOverrides", + "description": "Text replacements for the navbar.", + "default": null, + "mutable": false, + "end_line": 121 + }, + { + "name": "userCard", + "type": "INavbarUserCard", + "description": "User information used to render the user card.", + "default": null, + "mutable": false, + "end_line": 124 + }, + { + "name": "userMenuOpen", + "type": "boolean", + "description": "The open state of the user menu.", + "default": "false", + "mutable": true, + "end_line": 127 + }, + { + "name": "visibility", + "type": "INavbarVisibility", + "description": "The visibility of individual navbar buttons. Default is user profile visible, others hidden.", + "default": "{ ai: false, apps: false, help: false, logo: true, mainMenu: false, notifications: false, search: false, searchInput: false, user: true, }", + "mutable": false, + "end_line": 140 + } + ], + "events": [ + { + "name": "aiClick", + "detail": "MouseEvent | KeyboardEvent", + "description": "Event emitted when the AI button is clicked or activated via keyboard.", + "end_line": 143 + }, + { + "name": "appsClick", + "detail": "MouseEvent | KeyboardEvent", + "description": "Event emitted when the apps button is clicked or activated via keyboard.", + "end_line": 146 + }, + { + "name": "appsMenuOpenChange", + "detail": "boolean", + "description": "Event emitted when the apps menu open state changes.", + "end_line": 149 + }, + { + "name": "condensedMenuOpenChange", + "detail": "boolean", + "description": "Event emitted when the condensed menu open state changes.", + "end_line": 152 + }, + { + "name": "helpClick", + "detail": "MouseEvent | KeyboardEvent", + "description": "Event emitted when the help button is clicked or activated via keyboard.", + "end_line": 155 + }, + { + "name": "mainMenuOpenChange", + "detail": "boolean", + "description": "Event emitted when the main menu open state changes.", + "end_line": 158 + }, + { + "name": "myTrimbleClick", + "detail": "MouseEvent | KeyboardEvent", + "description": "Event emitted when the user profile Access MyTrimble button is clicked or activated via keyboard.", + "end_line": 161 + }, + { + "name": "notificationsClick", + "detail": "MouseEvent | KeyboardEvent", + "description": "Event emitted when the notifications button is clicked or activated via keyboard.", + "end_line": 164 + }, + { + "name": "notificationsMenuOpenChange", + "detail": "boolean", + "description": "Event emitted when the notifications menu open state changes.", + "end_line": 167 + }, + { + "name": "searchChange", + "detail": "{ value: string }", + "description": "Event emitted when the search input value is changed.", + "end_line": 170 + }, + { + "name": "searchClick", + "detail": "MouseEvent | KeyboardEvent", + "description": "Event emitted when the search button is clicked or activated via keyboard.", + "end_line": 173 + }, + { + "name": "searchInputOpenChange", + "detail": "boolean", + "description": "Event emitted when the search input open state changes.", + "end_line": 176 + }, + { + "name": "signOutClick", + "detail": "MouseEvent | KeyboardEvent", + "description": "Event emitted when the user profile sign out button is clicked or activated via keyboard.", + "end_line": 179 + }, + { + "name": "trimbleLogoClick", + "detail": "MouseEvent | KeyboardEvent", + "description": "Event emitted when the logo button is clicked or activated via keyboard,regardless of the `logoName` prop value.", + "end_line": 183 + }, + { + "name": "userMenuOpenChange", + "detail": "boolean", + "description": "Event emitted when the user menu open state changes.", + "end_line": 186 + } + ], + "methods": [], + "slots": [ + { + "name": "main-menu", + "description": "Slot for main-menu content" + }, + { + "name": "start", + "description": "Slot for start content" + }, + { + "name": "center", + "description": "Slot for center content" + }, + { + "name": "end", + "description": "Slot for end content" + }, + { + "name": "notifications", + "description": "Slot for notifications content" + }, + { + "name": "apps", + "description": "Slot for apps content" + } + ], + "examples": { + "basic": "\n\n
    Main menu contents
    \n
    Notification contents
    \n
    App drawer contents
    \n\n", + "variations": [], + "args": { + "condensed": "false", + "search-debounce-ms": "300", + "text-overrides": "textOverrides", + "user-card": "userCard", + "logo-name": "'trimble'" + }, + "argTypes": {}, + "usage": [], + "events": [ + "aiClick", + "appsClick", + "appsMenuOpenChange", + "condensedMenuOpenChange", + "helpClick", + "mainMenuOpenChange", + "myTrimbleClick", + "notificationsMenuOpenChange", + "notificationsClick", + "searchChange", + "searchClick", + "searchInputOpenChange", + "signOutClick", + "trimbleLogoClick", + "userMenuOpenChange", + "" + ] + }, + "tag": "modus-wc-navbar", + "storyExample": { + "template": "\n\n
    Main menu contents
    \n
    Notification contents
    \n
    App drawer contents
    \n\n", + "args": { + "condensed": "false", + "search-debounce-ms": "300", + "text-overrides": "textOverrides", + "user-card": "userCard", + "logo-name": "'trimble'" + }, + "argTypes": {}, + "events": [ + "aiClick", + "appsClick", + "appsMenuOpenChange", + "condensedMenuOpenChange", + "helpClick", + "mainMenuOpenChange", + "myTrimbleClick", + "notificationsMenuOpenChange", + "notificationsClick", + "searchChange", + "searchClick", + "searchInputOpenChange", + "signOutClick", + "trimbleLogoClick", + "userMenuOpenChange", + "" + ], + "fullContent": "import { withActions } from '@storybook/addon-actions/decorator';\nimport { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport {\n INavbarTextOverrides,\n INavbarUserCard,\n INavbarVisibility,\n} from './modus-wc-navbar';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\nimport { getAvailableLogos } from '../modus-wc-logo/logo-constants';\nimport { LogoName } from '../types';\n\nconst textOverrides: INavbarTextOverrides = {\n apps: 'Apps',\n help: 'Help',\n notifications: 'Notifications',\n search: 'Search',\n};\n\nconst userCard: INavbarUserCard = {\n avatarAlt: 'Sonic',\n avatarSrc: 'https://i1.sndcdn.com/artworks-000405996468-wmh3uv-t500x500.jpg',\n email: 'sonic@trimble.com',\n name: 'Sonic the Hedgehog',\n};\n\nconst visibility: INavbarVisibility = {\n ai: true,\n apps: true,\n help: true,\n logo: true,\n mainMenu: true,\n notifications: true,\n search: true,\n searchInput: true,\n user: true,\n};\n\ninterface NavbarArgs {\n 'apps-menu-open'?: boolean;\n condensed?: boolean;\n 'condensed-menu-open'?: boolean;\n 'custom-class'?: string;\n 'main-menu-open'?: boolean;\n 'notifications-menu-open'?: boolean;\n 'search-debounce-ms'?: number;\n 'search-input-open'?: boolean;\n 'text-overrides'?: INavbarTextOverrides;\n 'user-card': INavbarUserCard;\n 'user-menu-open'?: boolean;\n visibility?: INavbarVisibility;\n 'logo-name': LogoName;\n}\n\nconst meta: Meta = {\n title: 'Components/Navbar',\n component: 'modus-wc-navbar',\n args: {\n condensed: false,\n 'search-debounce-ms': 300,\n 'text-overrides': textOverrides,\n 'user-card': userCard,\n visibility,\n 'logo-name': 'trimble',\n },\n argTypes: {\n 'text-overrides': {\n description: 'Text replacements for navbar menu items',\n table: {\n type: {\n detail: `\n Interface: INavbarTextOverrides\n Properties:\n - apps (string, optional): Replaces the text for \"Apps\" in the condensed menu\n - help (string, optional): Replaces the text for \"Help\" in the condensed menu\n - notifications (string, optional): Replaces the text for \"Notifications\" in the condensed menu\n - search (string, optional): Replaces the text for \"Search\" in the condensed menu\n `,\n },\n },\n control: {\n type: 'object',\n },\n },\n 'logo-name': {\n description: 'The name of the logo to display. Defaults to \"trimble\".',\n table: {\n type: { summary: 'LogoName' },\n defaultValue: { summary: 'trimble' },\n },\n control: { type: 'select' },\n options: getAvailableLogos(),\n },\n 'user-card': {\n table: {\n type: {\n detail: `\n Interface: IUserCard\n Properties:\n - avatarAlt (string, optional): The alt value to set on the avatar\n - avatarSrc (string, optional): The avatar image source value\n - email (string): The email address of the user\n - myTrimbleButton (string, optional): Text override for the Access MyTrimble button, allows for translation\n - name (string): The name of the user\n - signOutButton (string, optional): Text override for the Sign out button, allows for translation\n `,\n },\n },\n },\n visibility: {\n description: 'Controls visibility of individual navbar buttons',\n table: {\n type: {\n detail: `\n Interface: INavbarVisibility\n Properties:\n - ai (boolean, optional): Controls visibility of the AI button\n - apps (boolean, optional): Controls visibility of the apps button\n - help (boolean, optional): Controls visibility of the help button\n - logo (boolean, optional): Controls visibility of the logo button; omit for visible\n - mainMenu (boolean, optional): Controls visibility of the main menu button\n - notifications (boolean, optional): Controls visibility of the notifications button\n - search (boolean, optional): Controls visibility of the search button\n - searchInput (boolean, optional): Controls visibility of the search input\n - user (boolean, optional): Controls visibility of the user button\n `,\n },\n },\n },\n },\n decorators: [withActions],\n parameters: {\n actions: {\n handles: [\n 'aiClick',\n 'appsClick',\n 'appsMenuOpenChange',\n 'condensedMenuOpenChange',\n 'helpClick',\n 'mainMenuOpenChange',\n 'myTrimbleClick',\n 'notificationsMenuOpenChange',\n 'notificationsClick',\n 'searchChange',\n 'searchClick',\n 'searchInputOpenChange',\n 'signOutClick',\n 'trimbleLogoClick',\n 'userMenuOpenChange',\n ],\n },\n layout: 'padded',\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nconst Template: Story = {\n render: (args) => {\n // prettier-ignore\n return html`\n\n\n
    Main menu contents
    \n
    Notification contents
    \n
    App drawer contents
    \n\n\n\n `;\n },\n};\n\nexport const Default: Story = { ...Template };\n\nexport const CustomMenuAndSlots: Story = {\n render: (args) => {\n function toggleCustomUserMenu(e) {\n const customIcon = e.currentTarget;\n const menu = customIcon.parentElement?.querySelector('#custom-user-menu');\n menu?.classList.toggle('hidden');\n }\n return html`\n
    \n \n \n
    Main menu contents
    \n
    Notification contents
    \n
    App drawer contents
    \n\n \n
    \n
    Left slot
    \n
    \n
    \n
    Center slot
    \n
    \n
    \n
    Right slot
    \n
    \n \n
    \n\n \n \n
    \n
    Custom Menu
    \n
    custom.user@example.com
    \n
    \n
    \n \n Account Settings\n
    \n
    \n \n Profile\n
    \n
    \n \n Logout\n
    \n
    \n
    \n \n
    \n \n `;\n },\n};\nexport const CustomLogoSizes: Story = {\n render: (args) => html`\n \n\n
    \n
    \n
    \n
    \n \n
    Main menu contents
    \n
    Notification contents
    \n
    App drawer contents
    \n \n
    \n
    \n
    \n\n
    \n
    \n
    \n \n
    Main menu contents
    \n
    Notification contents
    \n
    App drawer contents
    \n \n
    \n
    \n
    \n
    \n `,\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n if (!customElements.get('navbar-shadow-host')) {\n const NavbarShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-navbar',\n propsMapper: (v: NavbarArgs, el: HTMLElement) => {\n const navbarEl = el as unknown as {\n condensed: boolean;\n customClass: string;\n logoName: LogoName;\n searchDebounceMs: number;\n textOverrides: INavbarTextOverrides;\n userCard: INavbarUserCard;\n visibility: INavbarVisibility;\n };\n navbarEl.condensed = Boolean(v.condensed);\n navbarEl.customClass = v['custom-class'] || '';\n navbarEl.logoName = v['logo-name'];\n navbarEl.searchDebounceMs = v['search-debounce-ms'] ?? 300;\n navbarEl.textOverrides = v['text-overrides'] as INavbarTextOverrides;\n navbarEl.userCard = v['user-card'];\n navbarEl.visibility = v.visibility as INavbarVisibility;\n\n if (!el.querySelector('[slot=\"main-menu\"]')) {\n const mainMenu = document.createElement('div');\n mainMenu.setAttribute('slot', 'main-menu');\n mainMenu.textContent = 'Main menu contents';\n\n const notifications = document.createElement('div');\n notifications.setAttribute('slot', 'notifications');\n notifications.textContent = 'Notification contents';\n\n const apps = document.createElement('div');\n apps.setAttribute('slot', 'apps');\n apps.textContent = 'App drawer contents';\n\n el.appendChild(mainMenu);\n el.appendChild(notifications);\n el.appendChild(apps);\n\n // Inject slot styles into shadow root (matches Default story styling)\n const shadowRoot = el.getRootNode() as ShadowRoot;\n const styleEl = document.createElement('style');\n styleEl.textContent = `\n :host {\n display: block;\n border: 1px dashed black;\n height: 360px;\n overflow: hidden;\n }\n [slot='main-menu'] {\n background-color: #0063a3;\n color: white;\n height: 400px;\n }\n `;\n shadowRoot.appendChild(styleEl);\n }\n },\n });\n customElements.define('navbar-shadow-host', NavbarShadowHost);\n }\n\n return html``;\n },\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-number-input.json b/mcp/versions/1.5.0/component-docs/modus-wc-number-input.json new file mode 100644 index 000000000..93cf061e1 --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-number-input.json @@ -0,0 +1,219 @@ +{ + "description": "A customizable input component used to create number inputs with types", + "properties": [ + { + "name": "autoComplete", + "type": "'on' | 'off'", + "description": "Hint for form autofill feature.", + "default": null, + "mutable": false, + "end_line": 29 + }, + { + "name": "bordered", + "type": "boolean", + "description": "Indicates that the input should have a border.", + "default": "true", + "mutable": false, + "end_line": 32 + }, + { + "name": "currencySymbol", + "type": "string", + "description": "The currency symbol to display.", + "default": "''", + "mutable": false, + "end_line": 35 + }, + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the input.", + "default": "''", + "mutable": false, + "end_line": 38 + }, + { + "name": "disabled", + "type": "boolean", + "description": "Whether the form control is disabled.", + "default": "false", + "mutable": false, + "end_line": 41 + }, + { + "name": "feedback", + "type": "IInputFeedbackProp", + "description": "Feedback to render below the input.", + "default": null, + "mutable": false, + "end_line": 44 + }, + { + "name": "inputId", + "type": "string", + "description": "The ID of the input element.", + "default": null, + "mutable": false, + "end_line": 47 + }, + { + "name": "inputTabIndex", + "type": "number", + "description": "Determine the control's relative ordering for sequential focus navigation (typically with the Tab key).", + "default": null, + "mutable": false, + "end_line": 50 + }, + { + "name": "label", + "type": "string", + "description": "The text to display within the label.", + "default": null, + "mutable": false, + "end_line": 53 + }, + { + "name": "max", + "type": "number", + "description": "The input's maximum value.", + "default": null, + "mutable": false, + "end_line": 56 + }, + { + "name": "min", + "type": "number", + "description": "The input's minimum value.", + "default": null, + "mutable": false, + "end_line": 59 + }, + { + "name": "name", + "type": "string", + "description": "Name of the form control. Submitted with the form as part of a name/value pair.", + "default": null, + "mutable": false, + "end_line": 62 + }, + { + "name": "placeholder", + "type": "string", + "description": "Text that appears in the form control when it has no value set.", + "default": "''", + "mutable": false, + "end_line": 65 + }, + { + "name": "readOnly", + "type": "boolean", + "description": "Whether the value is editable.", + "default": "false", + "mutable": false, + "end_line": 68 + }, + { + "name": "required", + "type": "boolean", + "description": "A value is required for the form to be submittable.", + "default": "false", + "mutable": false, + "end_line": 71 + }, + { + "name": "size", + "type": "ModusSize", + "description": "The size of the input.", + "default": "'md'", + "mutable": false, + "end_line": 74 + }, + { + "name": "step", + "type": "number", + "description": "The granularity that the value adheres to.", + "default": null, + "mutable": false, + "end_line": 77 + }, + { + "name": "type", + "type": "'number' | 'range'", + "description": "Type of form control.", + "default": "'number'", + "mutable": false, + "end_line": 80 + }, + { + "name": "value", + "type": "string", + "description": "The value of the control.", + "default": "''", + "mutable": true, + "end_line": 83 + } + ], + "events": [ + { + "name": "inputBlur", + "detail": "FocusEvent", + "description": "Event emitted when the input loses focus.", + "end_line": 86 + }, + { + "name": "inputChange", + "detail": "InputEvent", + "description": "Event emitted when the input value changes.", + "end_line": 89 + }, + { + "name": "inputFocus", + "detail": "FocusEvent", + "description": "Event emitted when the input gains focus.", + "end_line": 92 + } + ], + "methods": [], + "slots": [], + "examples": { + "basic": "", + "variations": [], + "args": { + "bordered": "true", + "disabled": "false", + "inputmode": "'numeric'", + "label": "'Label'", + "size": "'md'", + "type": "'number'", + "value": "''" + }, + "argTypes": {}, + "usage": [], + "events": [ + "inputBlur", + "inputChange", + "inputFocus" + ] + }, + "tag": "modus-wc-number-input", + "storyExample": { + "template": "", + "args": { + "bordered": "true", + "disabled": "false", + "inputmode": "'numeric'", + "label": "'Label'", + "size": "'md'", + "type": "'number'", + "value": "''" + }, + "argTypes": {}, + "events": [ + "inputBlur", + "inputChange", + "inputFocus" + ], + "fullContent": "import { withActions } from '@storybook/addon-actions/decorator';\nimport { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\nimport { IInputFeedbackProp, ModusSize } from '../types';\n\ninterface NumberInputArgs {\n 'auto-complete'?: 'on' | 'off';\n bordered?: boolean;\n 'currency-symbol'?: string;\n 'custom-class'?: string;\n disabled?: boolean;\n feedback?: IInputFeedbackProp;\n 'input-aria-invalid'?: 'true' | 'false';\n 'input-id'?: string;\n inputmode?: 'decimal' | 'none' | 'numeric';\n 'input-tab-index'?: number;\n label?: string;\n max?: number;\n min?: number;\n name?: string;\n placeholder?: string;\n 'read-only'?: boolean;\n required?: boolean;\n size?: ModusSize;\n step?: number;\n type?: 'number' | 'range';\n value: string;\n}\n\nconst meta: Meta = {\n title: 'Components/Forms/Number Input',\n component: 'modus-wc-number-input',\n args: {\n bordered: true,\n disabled: false,\n inputmode: 'numeric',\n label: 'Label',\n size: 'md',\n type: 'number',\n value: '',\n },\n argTypes: {\n 'auto-complete': {\n control: { type: 'select' },\n options: ['on', 'off'],\n },\n feedback: {\n description: 'Feedback prop for input components',\n table: {\n type: {\n detail: `\n Interface: IInputFeedbackProp\n Properties:\n - level ('error' | 'info' | 'success' | 'warning'): The feedback level\n - message (string, optional): The feedback message\n `,\n },\n },\n },\n 'input-aria-invalid': {\n control: { type: 'select' },\n options: ['true', 'false'],\n },\n inputmode: {\n control: { type: 'select' },\n options: ['decimal', 'none', 'numeric'],\n },\n size: {\n control: { type: 'select' },\n options: ['sm', 'md', 'lg'],\n },\n type: {\n control: { type: 'select' },\n options: ['number', 'range'],\n },\n },\n decorators: [withActions],\n parameters: {\n actions: {\n handles: ['inputBlur', 'inputChange', 'inputFocus'],\n },\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nconst Template: Story = {\n render: (args) => html`\n \n `,\n};\n\nexport const Default: Story = { ...Template };\n\nconst errorFeedback: IInputFeedbackProp = {\n level: 'error',\n message: 'Value is required.',\n};\n\nexport const Currency: Story = {\n ...Template,\n args: { 'currency-symbol': '$' },\n};\n\nexport const WithErrorFeedback: Story = {\n ...Template,\n args: { feedback: errorFeedback, required: true },\n parameters: {\n docs: {\n source: {\n transform: (src) => `${src}\n`,\n },\n },\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n // Create a unique shadow host for number-input component\n if (!customElements.get('number-input-shadow-host')) {\n const NumberInputShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-number-input',\n propsMapper: (v: NumberInputArgs, el: HTMLElement) => {\n const numberInputEl = el as unknown as {\n autoComplete: string;\n bordered: boolean;\n currencySymbol: string;\n customClass: string;\n disabled: boolean;\n feedback: IInputFeedbackProp;\n inputId: string;\n inputTabIndex: number;\n label: string;\n max: number;\n min: number;\n name: string;\n placeholder: string;\n readOnly: boolean;\n required: boolean;\n size: string;\n step: number;\n type: string;\n value: string;\n };\n numberInputEl.autoComplete = v['auto-complete'] ?? '';\n numberInputEl.bordered = Boolean(v.bordered);\n numberInputEl.currencySymbol = v['currency-symbol'] ?? '';\n numberInputEl.customClass = v['custom-class'] || '';\n numberInputEl.disabled = Boolean(v.disabled);\n numberInputEl.inputId = v['input-id'] ?? '';\n numberInputEl.inputTabIndex = v['input-tab-index'] ?? 0;\n numberInputEl.label = v.label ?? '';\n numberInputEl.max = v.max ?? 0;\n numberInputEl.min = v.min ?? 0;\n numberInputEl.name = v.name ?? '';\n numberInputEl.placeholder = v.placeholder ?? '';\n numberInputEl.readOnly = Boolean(v['read-only']);\n numberInputEl.required = Boolean(v.required);\n numberInputEl.size = v.size ?? '';\n numberInputEl.step = v.step ?? 1;\n numberInputEl.type = v.type ?? '';\n numberInputEl.value = v.value;\n },\n });\n customElements.define('number-input-shadow-host', NumberInputShadowHost);\n }\n\n return html``;\n },\n};\nexport const Migration: Story = {\n parameters: {\n docs: {\n description: {\n story: `\n#### Breaking Changes\n\n - In 1.0 input state was maintained by the component. 2.0 components encourage users to follow a controlled\n input model. See the Form Inputs [documentation](/docs/documentation-form-inputs--docs) for\n additional info and examples.\n - Instead of changing the internal input type for currency formatting, the component now always renders\n a number input and displays the currency symbol via the \\`currency-symbol\\` prop.\n - Size values have changed from verbose names (\\`medium\\`, \\`large\\`) to abbreviations (\\`sm\\`, \\`md\\`, \\`lg\\`).\n\n#### Prop Mapping\n\n| 1.0 Prop | 2.0 Prop | Notes |\n|--------------|---------------------|-----------------------------------------|\n| aria-label | aria-label | |\n| currency | currency-symbol | |\n| disabled | disabled | |\n| error-text | feedback.message | Use \\`feedback\\` level |\n| helper-text | | Not carried over |\n| label | label | |\n| locale | | Not carried over |\n| max-value | max | |\n| min-value | min | |\n| placeholder | placeholder | |\n| read-only | read-only | |\n| required | required | |\n| size | size | \\`medium\\` → \\`md\\`, \\`large\\` → \\`lg\\` |\n| step | step | |\n| text-align | | Not carried over, use CSS instead |\n| valid-text | feedback.message | Use \\`feedback\\` level |\n| value | value | |\n\n#### Event Mapping\n\n| 1.0 Event | 2.0 Event | Notes |\n|--------------|--------------|-------|\n| valueChange | inputChange | |\n `,\n },\n },\n // To hide the actual story rendering and only show docs:\n controls: { disable: true },\n canvas: { disable: true },\n },\n // Simple render function or leave it empty\n render: () => html`
    `,\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-pagination.json b/mcp/versions/1.5.0/component-docs/modus-wc-pagination.json new file mode 100644 index 000000000..a7226bb7e --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-pagination.json @@ -0,0 +1,103 @@ +{ + "description": "Pagination component to navigate through pages of content", + "properties": [ + { + "name": "ariaLabelValues", + "type": "IAriaLabelValues", + "description": "Aria label values for pagination buttons", + "default": null, + "mutable": false, + "end_line": 56 + }, + { + "name": "count", + "type": "number", + "description": "Total number of pages", + "default": "1", + "mutable": false, + "end_line": 59 + }, + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply", + "default": "''", + "mutable": false, + "end_line": 62 + }, + { + "name": "nextButtonText", + "type": "string", + "description": "The next page button text. If not set, an icon control will be used.", + "default": null, + "mutable": false, + "end_line": 65 + }, + { + "name": "page", + "type": "number", + "description": "The current page number", + "default": "1", + "mutable": false, + "end_line": 68 + }, + { + "name": "prevButtonText", + "type": "string", + "description": "The previous page button text. If not set, an icon control will be used.", + "default": null, + "mutable": false, + "end_line": 71 + }, + { + "name": "size", + "type": "ModusSize", + "description": "Size of the pagination buttons", + "default": "'md'", + "mutable": false, + "end_line": 74 + } + ], + "events": [ + { + "name": "pageChange", + "detail": "IPageChange", + "description": "Event emitted when page changes", + "end_line": 77 + } + ], + "methods": [], + "slots": [], + "examples": { + "basic": "", + "variations": [], + "args": { + "aria-label-values": "defaultLabelValues", + "count": "5", + "custom-class": "''", + "page": "1", + "size": "'md'" + }, + "argTypes": {}, + "usage": [], + "events": [ + "pageChange" + ] + }, + "tag": "modus-wc-pagination", + "storyExample": { + "template": "", + "args": { + "aria-label-values": "defaultLabelValues", + "count": "5", + "custom-class": "''", + "page": "1", + "size": "'md'" + }, + "argTypes": {}, + "events": [ + "pageChange" + ], + "fullContent": "import { withActions } from '@storybook/addon-actions/decorator';\nimport { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { IAriaLabelValues } from './modus-wc-pagination';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\n\nconst defaultLabelValues: IAriaLabelValues = {\n firstPage: 'First page',\n lastPage: 'Last page',\n nextPage: 'Next page',\n page: 'Page {0}',\n previousPage: 'Previous page',\n};\n\ninterface PaginationArgs {\n 'aria-label-values'?: IAriaLabelValues;\n count: number;\n 'custom-class'?: string;\n 'next-button-text'?: string;\n page: number;\n 'prev-button-text'?: string;\n size?: 'sm' | 'md' | 'lg';\n}\n\nconst meta: Meta = {\n title: 'Components/Pagination',\n component: 'modus-wc-pagination',\n args: {\n 'aria-label-values': defaultLabelValues,\n count: 5,\n 'custom-class': '',\n page: 1,\n size: 'md',\n },\n argTypes: {\n 'aria-label-values': {\n description: 'Custom aria label values for pagination buttons',\n table: {\n type: {\n detail: `\n Interface: IAriaLabelValues\n Properties:\n - firstPage (string, optional): Aria label for the first page button\n - lastPage (string, optional): Aria label for the last page button\n - nextPage (string, optional): Aria label for the next page button\n - page (string, optional): Aria label for the page number button. Use {0} as placeholder for the page number\n - previousPage (string, optional): Aria label for the previous page button\n `,\n },\n },\n },\n size: {\n control: { type: 'select' },\n options: ['sm', 'md', 'lg'],\n },\n },\n decorators: [withActions],\n parameters: {\n actions: {\n handles: ['pageChange'],\n },\n docs: {\n description: {\n component: `\n## Event Interface Documentation\n\nThe pageChange event emits an object with the following interface:\n\n\\`\\`\\`typescript\ninterface IPageChange {\n /** The number of the newly selected page */\n newPage: number;\n /** The number of the previously selected page */\n prevPage: number;\n}\n\\`\\`\\`\n `,\n },\n },\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nexport const Default: Story = {\n render: (args) => html`\n \n `,\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n if (!customElements.get('pagination-shadow-host')) {\n const PaginationShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-pagination',\n propsMapper: (v: PaginationArgs, el: HTMLElement) => {\n const paginationEl = el as unknown as {\n ariaLabelValues: IAriaLabelValues | undefined;\n count: number;\n customClass: string;\n nextButtonText: string;\n page: number;\n prevButtonText: string;\n size: string;\n };\n paginationEl.ariaLabelValues = v['aria-label-values'];\n paginationEl.count = v.count;\n paginationEl.customClass = v['custom-class'] || '';\n paginationEl.nextButtonText = v['next-button-text'] ?? '';\n paginationEl.page = v.page;\n paginationEl.prevButtonText = v['prev-button-text'] ?? '';\n paginationEl.size = v.size ?? 'md';\n },\n });\n customElements.define('pagination-shadow-host', PaginationShadowHost);\n }\n\n return html``;\n },\n};\nexport const Migration: Story = {\n parameters: {\n docs: {\n description: {\n story: `\n#### Breaking Changes\n\n - In 1.0 the pagination component incorporated ellipses to indicate page skips. In 2.0, the component\n has been simplified to only show at most 5 page buttons relative to current page with previous/next\n and first/last navigation buttons.\n - In 1.0 the \\`active-page\\` prop was used, while 2.0 uses \\`page\\` instead.\n - The \\`pageChange\\` event in 1.0 emitted just the page number value. In 2.0, it emits an object\n with \\`newPage\\` and \\`prevPage\\` properties.\n - Size values have changed from verbose names (\\`small\\`, \\`medium\\`, \\`large\\`) to abbreviations (\\`sm\\`, \\`md\\`, \\`lg\\`).\n\n#### Prop Mapping\n\n| 1.0 Prop | 2.0 Prop | Notes |\n|-----------------------|--------------------|-------------------------------------------------------------|\n| active-page | page | |\n| aria-label | aria-label | |\n| max-page | count | |\n| min-page | | Not carried over, minimum page is always 1 |\n| next-page-button-text | next-button-text | |\n| prev-page-button-text | prev-button-text | |\n| size | size | \\`small\\` → \\`sm\\`, \\`medium\\` → \\`md\\`, \\`large\\` → \\`lg\\` |\n\n#### Event Mapping\n\n| 1.0 Event | 2.0 Event | Notes |\n|-------------|-------------|---------------------------------------------------------|\n| pageChange | pageChange | Now emits an object with \\`newPage\\` and \\`prevPage\\` |\n `,\n },\n },\n controls: { disable: true },\n canvas: { disable: true },\n },\n render: () => html`
    `,\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-panel.json b/mcp/versions/1.5.0/component-docs/modus-wc-panel.json new file mode 100644 index 000000000..15486b409 --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-panel.json @@ -0,0 +1,78 @@ +{ + "description": "A customizable panel component used to organize content in a structured layout. This component provides 'header', 'body', and 'footer' `` elements for inserting custom HTML.", + "properties": [ + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the outer div.", + "default": "''", + "mutable": false, + "end_line": 22 + }, + { + "name": "width", + "type": "string", + "description": "Width of the panel in pixels.", + "default": "'350px'", + "mutable": false, + "end_line": 25 + }, + { + "name": "height", + "type": "string", + "description": "Height of the panel in pixels.", + "default": "'700px'", + "mutable": false, + "end_line": 28 + }, + { + "name": "floating", + "type": "boolean", + "description": "Enable floating mode with elevated shadow.", + "default": "false", + "mutable": false, + "end_line": 31 + } + ], + "events": [], + "methods": [], + "slots": [ + { + "name": "header", + "description": "Slot for header content" + }, + { + "name": "body", + "description": "Slot for body content" + }, + { + "name": "footer", + "description": "Slot for footer content" + } + ], + "examples": { + "basic": "\n\n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n", + "variations": [], + "args": { + "custom-class": "''", + "width": "'250px'", + "height": "'500px'", + "floating": "false" + }, + "argTypes": {}, + "usage": [] + }, + "tag": "modus-wc-panel", + "storyExample": { + "template": "\n\n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n", + "args": { + "custom-class": "''", + "width": "'250px'", + "height": "'500px'", + "floating": "false" + }, + "argTypes": {}, + "events": [], + "fullContent": "import { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\n\ninterface PanelArgs {\n 'custom-class'?: string;\n width?: string;\n height?: string;\n floating?: boolean;\n}\n\nconst meta: Meta = {\n title: 'Components/Panel',\n component: 'modus-wc-panel',\n args: {\n 'custom-class': '',\n width: '250px',\n height: '500px',\n floating: false,\n },\n parameters: {\n layout: 'padded',\n },\n argTypes: {\n floating: {\n control: { type: 'boolean' },\n },\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nexport const Default: Story = {\n args: {\n width: '250px',\n height: '500px',\n floating: false,\n },\n render: (args) => {\n // prettier-ignore\n return html`\n\n\n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n\n `;\n },\n};\n\nexport const Floating: Story = {\n args: {\n width: '250px',\n height: '500px',\n floating: true,\n },\n render: (args) => {\n // prettier-ignore\n return html`\n\n\n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n\n `;\n },\n};\n\nexport const BodyOnly: Story = {\n args: {\n width: '250px',\n height: 'auto',\n },\n render: (args) => {\n // prettier-ignore\n return html`\n\n \n \n \n \n \n \n \n \n \n \n \n\n `;\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n if (!customElements.get('panel-shadow-host')) {\n const PanelShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-panel',\n propsMapper: (v: PanelArgs, el: HTMLElement) => {\n const panelEl = el as unknown as {\n customClass: string;\n width: string;\n height: string;\n floating: boolean;\n };\n panelEl.customClass = v['custom-class'] || '';\n panelEl.width = v.width ?? '250px';\n panelEl.height = v.height ?? '500px';\n panelEl.floating = Boolean(v.floating);\n if (!el.hasChildNodes()) {\n // Inject .panel-section style into the shadow root since the\n // global \n
    \n
    \n Default size\n \n
    \n
    \n Small size\n \n
    \n
    \n Compact size\n \n
    \n
    \n `;\n },\n};\n\nexport const LabelTextColor: Story = {\n args: {\n label: 'Loading...',\n value: 50,\n },\n render: (args) => {\n // prettier-ignore\n return html`\n\n\n `;\n },\n};\n\nexport const CustomBarColor: Story = {\n render: (args) => {\n // prettier-ignore\n return html`\n\n\n `;\n },\n};\n\nexport const CustomBackgroundColor: Story = {\n render: (args) => {\n // prettier-ignore\n return html`\n\n\n `;\n },\n};\n\nexport const RadialWithSlottedContent: Story = {\n render: (args) => {\n // prettier-ignore\n return html`\n\n\n \n ${args.value}%\n\n `;\n },\n parameters: {\n layout: 'centered',\n },\n};\n\nexport const RadialWithCustomSizeAndThickness: Story = {\n render: (args) => {\n // prettier-ignore\n return html`\n\n\n ${args.value}%\n\n\n ${args.value}%\n\n `;\n },\n parameters: {\n layout: 'centered',\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n if (!customElements.get('progress-shadow-host')) {\n const ProgressShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-progress',\n propsMapper: (v: ProgressArgs, el: HTMLElement) => {\n const progressEl = el as unknown as {\n customClass: string;\n indeterminate: boolean;\n label: string;\n max: number;\n value: number;\n variant: string;\n };\n progressEl.customClass = v['custom-class'] || '';\n progressEl.indeterminate = Boolean(v.indeterminate);\n progressEl.label = v.label ?? '';\n progressEl.max = v.max ?? 100;\n progressEl.value = v.value;\n progressEl.variant = v.variant ?? 'default';\n },\n });\n customElements.define('progress-shadow-host', ProgressShadowHost);\n }\n\n return html``;\n },\n};\nexport const Migration: Story = {\n parameters: {\n docs: {\n description: {\n story: `\n#### Breaking Changes\n\n - Colors and sizes are now handled through CSS instead of direct props.\n - The \\`mode\\` prop has been replaced with an \\`indeterminate\\` boolean prop.\n\n#### Prop Mapping\n\n| 1.0 Prop | 2.0 Prop | Notes |\n|-------------------|---------------|------------------------------------------------------|\n| aria-label | aria-label | |\n| background-color | | Not carried over, use CSS instead |\n| color | | Not carried over, use CSS instead |\n| max-value | max | |\n| min-value | | Not carried over |\n| mode | indeterminate | 1.0: \\`determinate\\`/\\`indeterminate\\`, 2.0: boolean |\n| size | | Not carried over, use CSS instead |\n| text | label | |\n| text-color | | Not carried over, use CSS instead |\n| value | value | |\n `,\n },\n },\n controls: { disable: true },\n canvas: { disable: true },\n },\n render: () => html`
    `,\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-radio.json b/mcp/versions/1.5.0/component-docs/modus-wc-radio.json new file mode 100644 index 000000000..b66c6573c --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-radio.json @@ -0,0 +1,139 @@ +{ + "description": "A customizable radio button component.", + "properties": [ + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the inner div.", + "default": "''", + "mutable": false, + "end_line": 31 + }, + { + "name": "disabled", + "type": "boolean", + "description": "The disabled state of the radio.", + "default": "false", + "mutable": false, + "end_line": 34 + }, + { + "name": "inputId", + "type": "string", + "description": "The ID of the input element.", + "default": null, + "mutable": false, + "end_line": 37 + }, + { + "name": "inputTabIndex", + "type": "number", + "description": "The tabindex of the input.", + "default": null, + "mutable": false, + "end_line": 40 + }, + { + "name": "label", + "type": "string", + "description": "The text to display within the label.", + "default": null, + "mutable": false, + "end_line": 43 + }, + { + "name": "name", + "type": "string", + "description": "Name of the form control. Submitted with the form as part of a name/value pair.", + "default": "''", + "mutable": false, + "end_line": 46 + }, + { + "name": "required", + "type": "boolean", + "description": "A value is required for the form to be submittable.", + "default": "false", + "mutable": false, + "end_line": 49 + }, + { + "name": "size", + "type": "ModusSize", + "description": "The size of the input.", + "default": "'md'", + "mutable": false, + "end_line": 52 + }, + { + "name": "value", + "type": "boolean", + "description": "The value of the radio.", + "default": "false", + "mutable": true, + "end_line": 55 + } + ], + "events": [ + { + "name": "inputBlur", + "detail": "FocusEvent", + "description": "Emitted when the input loses focus.", + "end_line": 58 + }, + { + "name": "inputChange", + "detail": "InputEvent", + "description": "Emitted when the input value changes.", + "end_line": 61 + }, + { + "name": "inputFocus", + "detail": "FocusEvent", + "description": "Emitted when the input gains focus.", + "end_line": 64 + } + ], + "methods": [], + "slots": [], + "examples": { + "basic": "", + "variations": [], + "args": { + "custom-class": "''", + "disabled": "false", + "label": "'Label'", + "name": "''", + "required": "false", + "size": "'md'", + "value": "true" + }, + "argTypes": {}, + "usage": [], + "events": [ + "inputBlur", + "inputChange", + "inputFocus" + ] + }, + "tag": "modus-wc-radio", + "storyExample": { + "template": "", + "args": { + "custom-class": "''", + "disabled": "false", + "label": "'Label'", + "name": "''", + "required": "false", + "size": "'md'", + "value": "true" + }, + "argTypes": {}, + "events": [ + "inputBlur", + "inputChange", + "inputFocus" + ], + "fullContent": "import { withActions } from '@storybook/addon-actions/decorator';\nimport { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\nimport { ModusSize } from '../types';\n\ninterface RadioArgs {\n 'custom-class'?: string;\n disabled?: boolean;\n 'input-id'?: string;\n 'input-tab-index'?: number;\n label?: string;\n name?: string;\n required?: boolean;\n size?: ModusSize;\n value: boolean;\n}\n\nconst meta: Meta = {\n title: 'Components/Forms/Radio',\n component: 'modus-wc-radio',\n args: {\n 'custom-class': '',\n disabled: false,\n label: 'Label',\n name: '',\n required: false,\n size: 'md',\n value: true,\n },\n argTypes: {\n size: {\n control: { type: 'select' },\n options: ['xs', 'sm', 'md', 'lg'],\n },\n },\n decorators: [withActions],\n parameters: {\n actions: {\n handles: ['inputBlur', 'inputChange', 'inputFocus'],\n },\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nexport const Default: Story = {\n render: (args) => {\n return html`\n \n `;\n },\n};\n\nexport const RadioGroup: Story = {\n // prettier-ignore\n render: (args) => {\n return html`\n\n\n
    \n
    \n Select an option:\n\n {\n // In a real app, you would update your state management here\n console.log('Selected:', e.target);\n }}\n >\n\n {\n console.log('Selected:', e.target);\n }}\n >\n\n {\n console.log('Selected:', e.target);\n }}\n >\n
    \n
    \n `;\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n // Create a unique shadow host for radio component\n if (!customElements.get('radio-shadow-host')) {\n const RadioShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-radio',\n propsMapper: (v: RadioArgs, el: HTMLElement) => {\n const radioEl = el as unknown as {\n customClass: string;\n disabled: boolean;\n inputId: string;\n inputTabIndex: number;\n label: string;\n name: string;\n required: boolean;\n size: string;\n value: boolean;\n };\n radioEl.customClass = v['custom-class'] || '';\n radioEl.disabled = Boolean(v.disabled);\n radioEl.inputId = v['input-id'] || '';\n radioEl.inputTabIndex = v['input-tab-index'] || 0;\n radioEl.label = v.label || '';\n radioEl.name = v.name || '';\n radioEl.required = Boolean(v.required);\n radioEl.size = v.size || 'md';\n radioEl.value = Boolean(v.value);\n },\n });\n customElements.define('radio-shadow-host', RadioShadowHost);\n }\n\n return html``;\n },\n};\nexport const Migration: Story = {\n parameters: {\n docs: {\n description: {\n story: `\n#### Breaking Changes\n\n - In 1.0 input state was maintained by the component. 2.0 components encourage users to follow a controlled\n input model. See the Form Inputs [documentation](/docs/documentation-form-inputs--docs) for\n additional info and examples.\n - Size values have changed from verbose names (\\`small\\`, \\`medium\\`) to abbreviations (\\`xs\\`, \\`sm\\`, \\`md\\`, \\`lg\\`).\n\n#### Prop Mapping\n\n| 1.0 Prop | 2.0 Prop | Notes |\n|---------------------|-------------|----------------------------------------------------- |\n| checked | value | |\n| disabled | disabled | |\n| handle-button-click | | Not carried over |\n| handle-keydown | | Not carried over |\n| id | input-id | |\n| label | label | |\n| name | name | |\n| ref | | Not carried over |\n| size | size | \\`small\\` → \\`sm\\`, \\`medium\\` → \\`md\\` |\n\n#### Event Mapping\n\n| 1.0 Event | 2.0 Event | Notes |\n|-------------|-------------|-----------------------------------------------------|\n| buttonClick | inputChange | Now emits an \\`InputEvent\\` instead of a \\`string\\` |\n `,\n },\n },\n // To hide the actual story rendering and only show docs:\n controls: { disable: true },\n canvas: { disable: true },\n },\n // Simple render function or leave it empty\n render: () => html`
    `,\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-rating.json b/mcp/versions/1.5.0/component-docs/modus-wc-rating.json new file mode 100644 index 000000000..dff3acfd6 --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-rating.json @@ -0,0 +1,117 @@ +{ + "description": "A rating component that allows users to choose a rating from predefined options", + "properties": [ + { + "name": "allowHalf", + "type": "boolean", + "description": "Whether to allow half-ratings. Only applies to star and heart variants.", + "default": "false", + "mutable": false, + "end_line": 43 + }, + { + "name": "count", + "type": "number", + "description": "The number of rating items to display", + "default": "5", + "mutable": false, + "end_line": 46 + }, + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply", + "default": "''", + "mutable": false, + "end_line": 49 + }, + { + "name": "disabled", + "type": "boolean", + "description": "Whether the rating component is disabled", + "default": "false", + "mutable": false, + "end_line": 52 + }, + { + "name": "getAriaLabelText", + "type": "(ratingValue: number)", + "description": "Function to provide aria-label text for a given rating-item index", + "default": "> string = (ratingValue) => `Rating item ${ratingValue}`", + "mutable": false, + "end_line": 56 + }, + { + "name": "size", + "type": "ModusSize", + "description": "The size of the rating component", + "default": "'md'", + "mutable": false, + "end_line": 59 + }, + { + "name": "variant", + "type": "ModusWcRatingVariant", + "description": "The variant of the rating scale", + "default": "'smiley'", + "mutable": false, + "end_line": 62 + }, + { + "name": "value", + "type": "number", + "description": "The current value of the rating", + "default": "0", + "mutable": true, + "end_line": 65 + } + ], + "events": [ + { + "name": "ratingChange", + "detail": "IRatingChange", + "description": "Event emitted when the rating changes", + "end_line": 68 + } + ], + "methods": [], + "slots": [], + "examples": { + "basic": "\n", + "variations": [], + "args": { + "allow-half": "false", + "count": "5", + "custom-class": "''", + "disabled": "false", + "getAriaLabelText": "(index: number) => `$", + "size": "'md'", + "value": "0", + "variant": "'smiley'" + }, + "argTypes": {}, + "usage": [], + "events": [ + "ratingChange" + ] + }, + "tag": "modus-wc-rating", + "storyExample": { + "template": "\n", + "args": { + "allow-half": "false", + "count": "5", + "custom-class": "''", + "disabled": "false", + "getAriaLabelText": "(index: number) => `$", + "size": "'md'", + "value": "0", + "variant": "'smiley'" + }, + "argTypes": {}, + "events": [ + "ratingChange" + ], + "fullContent": "import { withActions } from '@storybook/addon-actions/decorator';\nimport { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { ModusWcRatingVariant } from './modus-wc-rating';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\n\ninterface RatingArgs {\n 'allow-half'?: boolean;\n count: number;\n 'custom-class'?: string;\n disabled?: boolean;\n getAriaLabelText?: (index: number) => string;\n size?: 'sm' | 'md' | 'lg';\n value?: number;\n variant: ModusWcRatingVariant;\n}\n\nconst meta: Meta = {\n title: 'Components/Forms/Rating',\n component: 'modus-wc-rating',\n args: {\n 'allow-half': false,\n count: 5,\n 'custom-class': '',\n disabled: false,\n getAriaLabelText: (index: number) => `${index} rating`,\n size: 'md',\n value: 0,\n variant: 'smiley',\n },\n argTypes: {\n size: {\n control: { type: 'select' },\n options: ['sm', 'md', 'lg'],\n },\n variant: {\n control: { type: 'select' },\n options: ['heart', 'smiley', 'star', 'thumb'],\n },\n },\n decorators: [withActions],\n parameters: {\n actions: {\n handles: ['ratingChange'],\n },\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nexport const Default: Story = {\n render: (args) => html`\n \n `,\n};\n\nexport const CustomAriaLabels: Story = {\n render: (args) => {\n const ariaLabelText = (index: number) =>\n `Custom label for rating item ${index}`;\n\n // prettier-ignore\n return html`\n\n\n `;\n },\n};\n\nexport const CustomColors: Story = {\n // prettier-ignore\n render: (args) => html`\n\n\n `,\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n // Create a unique shadow host for rating component\n if (!customElements.get('rating-shadow-host')) {\n const RatingShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-rating',\n propsMapper: (v: RatingArgs, el: HTMLElement) => {\n const ratingEl = el as unknown as {\n allowHalf: boolean;\n count: number;\n customClass: string;\n disabled: boolean;\n getAriaLabelText: (index: number) => string;\n size: string;\n value: number;\n variant: string;\n };\n ratingEl.allowHalf = Boolean(v['allow-half']);\n ratingEl.count = v.count;\n ratingEl.customClass = v['custom-class'] || '';\n ratingEl.disabled = Boolean(v.disabled);\n if (v.getAriaLabelText) {\n ratingEl.getAriaLabelText = v.getAriaLabelText; // Conditional assignment only if provided\n }\n ratingEl.size = v.size || 'md';\n ratingEl.value = v.value || 0;\n ratingEl.variant = v.variant;\n },\n });\n customElements.define('rating-shadow-host', RatingShadowHost);\n }\n\n return html``;\n },\n};\nexport const Migration: Story = {\n parameters: {\n docs: {\n description: {\n story: `\n#### Breaking Changes\n\n - In 1.0 input state was maintained by the component. 2.0 components encourage users to follow a controlled\n input model. See the Form Inputs [documentation](/docs/documentation-form-inputs--docs) for\n additional info and examples.\n - Type/variant values have changed from \\`smileys\\` to \\`smiley\\` and \\`thumbs\\` to \\`thumb\\`.\n\n#### Prop Mapping\n\n| 1.0 Prop | 2.0 Prop | Notes |\n|-------------|-------------|------------------------------------------------------|\n| aria-label | aria-label | |\n| disabled | disabled | |\n| type | variant | \\`smileys\\` → \\`smiley\\`, \\`thumbs\\` → \\`thumb\\` |\n\n#### Event Mapping\n\n| 1.0 Event | 2.0 Event | Notes |\n|--------------------|--------------|-----------------------------------------------|\n| sentimentSelection | ratingChange | |\n `,\n },\n },\n controls: { disable: true },\n canvas: { disable: true },\n },\n render: () => html`
    `,\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-select.json b/mcp/versions/1.5.0/component-docs/modus-wc-select.json new file mode 100644 index 000000000..89c4623c6 --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-select.json @@ -0,0 +1,159 @@ +{ + "description": "A customizable select component used to pick a value from a list of options", + "properties": [ + { + "name": "bordered", + "type": "boolean", + "description": "Indicates that the input should have a border.", + "default": "true", + "mutable": false, + "end_line": 39 + }, + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the inner div.", + "default": "''", + "mutable": false, + "end_line": 42 + }, + { + "name": "disabled", + "type": "boolean", + "description": "Whether the form control is disabled.", + "default": "false", + "mutable": false, + "end_line": 45 + }, + { + "name": "feedback", + "type": "IInputFeedbackProp", + "description": "Feedback to render below the input.", + "default": null, + "mutable": false, + "end_line": 48 + }, + { + "name": "inputId", + "type": "string", + "description": "The ID of the input element.", + "default": null, + "mutable": false, + "end_line": 51 + }, + { + "name": "inputTabIndex", + "type": "number", + "description": "Determine the control's relative ordering for sequential focus navigation (typically with the Tab key).", + "default": null, + "mutable": false, + "end_line": 54 + }, + { + "name": "label", + "type": "string", + "description": "The text to display within the label.", + "default": null, + "mutable": false, + "end_line": 57 + }, + { + "name": "name", + "type": "string", + "description": "Name of the form control. Submitted with the form as part of a name/value pair.", + "default": null, + "mutable": false, + "end_line": 60 + }, + { + "name": "options", + "type": "ISelectOption[]", + "description": "The options to display in the select dropdown.", + "default": "[]", + "mutable": true, + "end_line": 63 + }, + { + "name": "required", + "type": "boolean", + "description": "A value is required for the form to be submittable.", + "default": "false", + "mutable": false, + "end_line": 66 + }, + { + "name": "size", + "type": "ModusSize", + "description": "The size of the input.", + "default": "'md'", + "mutable": false, + "end_line": 69 + }, + { + "name": "value", + "type": "string", + "description": "The value of the control.", + "default": "''", + "mutable": true, + "end_line": 72 + } + ], + "events": [ + { + "name": "inputBlur", + "detail": "FocusEvent", + "description": "Event emitted when the input loses focus.", + "end_line": 75 + }, + { + "name": "inputChange", + "detail": "InputEvent", + "description": "Event emitted when the input value changes.", + "end_line": 78 + }, + { + "name": "inputFocus", + "detail": "FocusEvent", + "description": "Event emitted when the input gains focus.", + "end_line": 81 + } + ], + "methods": [], + "slots": [], + "examples": { + "basic": "", + "variations": [], + "args": { + "bordered": "true", + "disabled": "false", + "label": "'Label'", + "size": "'md'", + "value": "''" + }, + "argTypes": {}, + "usage": [], + "events": [ + "inputBlur", + "inputChange", + "inputFocus" + ] + }, + "tag": "modus-wc-select", + "storyExample": { + "template": "", + "args": { + "bordered": "true", + "disabled": "false", + "label": "'Label'", + "size": "'md'", + "value": "''" + }, + "argTypes": {}, + "events": [ + "inputBlur", + "inputChange", + "inputFocus" + ], + "fullContent": "import { withActions } from '@storybook/addon-actions/decorator';\nimport { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { ISelectOption } from './modus-wc-select';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\nimport { IInputFeedbackProp, ModusSize } from '../types';\n\nconst options: ISelectOption[] = [\n { label: 'Option 1', value: '1' },\n { label: 'Option 2', value: '2' },\n { label: 'Option 3', value: '3' },\n];\n\ninterface SelectArgs {\n bordered?: boolean;\n 'custom-class'?: string;\n disabled?: boolean;\n feedback?: IInputFeedbackProp;\n 'input-aria-invalid'?: 'true' | 'false';\n 'input-id'?: string;\n 'input-tab-index'?: number;\n label?: string;\n name?: string;\n options: ISelectOption[];\n required?: boolean;\n size?: ModusSize;\n value: string;\n}\n\nconst meta: Meta = {\n title: 'Components/Forms/Select',\n component: 'modus-wc-select',\n args: {\n bordered: true,\n disabled: false,\n label: 'Label',\n options,\n size: 'md',\n value: '',\n },\n argTypes: {\n feedback: {\n description: 'Feedback prop for input components',\n table: {\n type: {\n detail: `\n Interface: IInputFeedbackProp\n Properties:\n - level ('error' | 'info' | 'success' | 'warning'): The feedback level\n - message (string, optional): The feedback message\n `,\n },\n },\n },\n 'input-aria-invalid': {\n control: { type: 'select' },\n options: ['true', 'false'],\n },\n options: {\n description: 'Array of option objects for the select dropdown',\n table: {\n type: {\n detail: `\n Interface: ISelectOption\n Properties:\n - disabled (boolean, optional): Whether the option is disabled and cannot be selected\n - label (string): Display text for the option\n - value (string): The value of the option\n `,\n },\n },\n },\n size: {\n control: { type: 'select' },\n options: ['sm', 'md', 'lg'],\n },\n },\n decorators: [withActions],\n parameters: {\n actions: {\n handles: ['inputBlur', 'inputChange', 'inputFocus'],\n },\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nexport const Default: Story = {\n render: (args) => html`\n \n \n `,\n};\n\nconst errorFeedback: IInputFeedbackProp = {\n level: 'error',\n message: 'Value is required.',\n};\n\nexport const WithErrorFeedback: Story = {\n render: (args) => html`\n \n \n `,\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n // Create a unique shadow host for select component\n if (!customElements.get('select-shadow-host')) {\n const SelectShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-select',\n propsMapper: (v: SelectArgs, el: HTMLElement) => {\n const selectEl = el as unknown as {\n bordered: boolean;\n customClass: string;\n disabled: boolean;\n feedback: IInputFeedbackProp;\n inputId: string;\n inputTabIndex: number;\n label: string;\n name: string;\n options: ISelectOption[];\n required: boolean;\n size: string;\n value: string;\n };\n selectEl.bordered = Boolean(v.bordered);\n selectEl.customClass = v['custom-class'] || '';\n selectEl.disabled = Boolean(v.disabled);\n selectEl.inputId = v['input-id'] || '';\n selectEl.inputTabIndex = v['input-tab-index'] || 0;\n selectEl.label = v.label || '';\n selectEl.name = v.name || '';\n selectEl.options = v.options;\n selectEl.required = Boolean(v.required);\n selectEl.size = v.size || 'md';\n selectEl.value = v.value;\n },\n });\n customElements.define('select-shadow-host', SelectShadowHost);\n }\n\n return html``;\n },\n};\nexport const Migration: Story = {\n parameters: {\n docs: {\n description: {\n story: `\n#### Breaking Changes\n\n - In 1.0 input state was maintained by the component. 2.0 components encourage users to follow a controlled\n input model. See the Form Inputs [documentation](/docs/documentation-form-inputs--docs) for\n additional info and examples.\n - The options format has changed to use a standardized \\`ISelectOption\\` object array.\n - Size values have changed from verbose names (\\`medium\\`, \\`large\\`) to abbreviations (\\`sm\\`, \\`md\\`, \\`lg\\`).\n\n#### Prop Mapping\n\n| 1.0 Prop | 2.0 Prop | Notes |\n|-----------------------|---------------------|------------------------------------------------------|\n| aria-label | aria-label | |\n| disabled | disabled | |\n| error-text | feedback.message | Use \\`feedback\\` level |\n| helper-text | | Not carried over |\n| label | label | |\n| options | options | Format changed to require array of \\`ISelectOption\\` objects |\n| options-display-prop | | Not carried over |\n| placeholder | | Not carried over |\n| required | required | |\n| size | size | \\`medium\\` → \\`md\\`, \\`large\\` → \\`lg\\` |\n| valid-text | feedback.message | Use \\`feedback\\` level |\n| value | value | |\n\n#### Event Mapping\n\n| 1.0 Event | 2.0 Event | Notes |\n|--------------|-------------|------------------|\n| valueChange | inputChange | |\n| inputBlur | inputBlur | |\n `,\n },\n },\n controls: { disable: true },\n canvas: { disable: true },\n },\n render: () => html`
    `,\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-side-navigation.json b/mcp/versions/1.5.0/component-docs/modus-wc-side-navigation.json new file mode 100644 index 000000000..b440acd25 --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-side-navigation.json @@ -0,0 +1,102 @@ +{ + "description": "A customizable side navigation component for organizing primary navigation and content areas in an application. The component supports a `` for injecting custom content inside the side navigation panel.", + "properties": [ + { + "name": "collapseOnClickOutside", + "type": "boolean", + "description": "Whether the side navigation should collapse when clicking outside of it.", + "default": "true", + "mutable": false, + "end_line": 33 + }, + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the inner div.", + "default": "''", + "mutable": false, + "end_line": 36 + }, + { + "name": "expanded", + "type": "boolean", + "description": "Whether the side navigation is expanded.", + "default": "false", + "mutable": true, + "end_line": 39 + }, + { + "name": "maxWidth", + "type": "string", + "description": "Maximum width of the side navigation panel in an expanded state.", + "default": "'256px'", + "mutable": false, + "end_line": 42 + }, + { + "name": "mode", + "type": "'overlay' | 'push'", + "description": "Mode to make side navigation either overlay or push the content for the selector specified in targetContent", + "default": "'overlay'", + "mutable": false, + "end_line": 45 + }, + { + "name": "targetContent", + "type": "string", + "description": "(optional) Specify the selector for the page's content for which paddings and margins will be set by side navigation based on the mode.", + "default": "''", + "mutable": false, + "end_line": 48 + } + ], + "events": [ + { + "name": "expandedChange", + "detail": "boolean", + "description": "Event emitted when the expanded state changes (expanded/collapsed).", + "end_line": 51 + } + ], + "methods": [], + "slots": [ + { + "name": "default", + "description": "Slot for default content" + } + ], + "examples": { + "basic": "\n
    \n \n
    \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    \n
    \n

    \n The side navigation of an application provides context through\n accessible menu options and positions a consistent component to\n connect to various pages in the application.\n

    \n

    \n The side navigation is a collapsible side content of the site’s\n pages. It is located alongside the page’s primary content. The\n component is designed to add side content to a fullscreen\n application. It is activated through the “hamburger” menu in the\n Navbar.\n

    \n
    \n
    \n
    \n
    \n ", + "variations": [], + "args": { + "collapse-on-click-outside": "true", + "expanded": "false", + "max-width": "'256px'", + "mode": "'push'", + "target-content": "'.panel-content'" + }, + "argTypes": {}, + "usage": [], + "events": [ + "expandedChange", + "itemSelect" + ] + }, + "tag": "modus-wc-side-navigation", + "storyExample": { + "template": "\n
    \n \n
    \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    \n
    \n

    \n The side navigation of an application provides context through\n accessible menu options and positions a consistent component to\n connect to various pages in the application.\n

    \n

    \n The side navigation is a collapsible side content of the site’s\n pages. It is located alongside the page’s primary content. The\n component is designed to add side content to a fullscreen\n application. It is activated through the “hamburger” menu in the\n Navbar.\n

    \n
    \n
    \n
    \n
    \n ", + "args": { + "collapse-on-click-outside": "true", + "expanded": "false", + "max-width": "'256px'", + "mode": "'push'", + "target-content": "'.panel-content'" + }, + "argTypes": {}, + "events": [ + "expandedChange", + "itemSelect" + ], + "fullContent": "import { withActions } from '@storybook/addon-actions/decorator';\nimport { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\n\ninterface SideNavigationArgs {\n 'custom-class'?: string;\n expanded: boolean;\n 'max-width': string;\n 'collapse-on-click-outside'?: boolean;\n mode: 'overlay' | 'push';\n 'target-content'?: string;\n}\n\nconst meta: Meta = {\n title: 'Components/Side Navigation',\n component: 'modus-wc-side-navigation',\n args: {\n 'collapse-on-click-outside': true,\n expanded: false,\n 'max-width': '256px',\n mode: 'push',\n 'target-content': '.panel-content',\n },\n argTypes: {\n 'max-width': {\n control: { type: 'text' },\n description:\n 'Maximum width of the side navigation panel in an expanded state.',\n },\n mode: {\n control: { type: 'select' },\n options: ['overlay', 'push'],\n description: 'Display mode of the side navigation (overlay or push).',\n },\n },\n decorators: [withActions],\n parameters: {\n layout: 'padded',\n actions: {\n handles: ['expandedChange', 'itemSelect'],\n },\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nexport const Default: Story = {\n render: (args) => {\n const handleMenuOpenChange = (e: CustomEvent) => {\n const eventSource = e.target as HTMLElement;\n const storyContainer = eventSource?.closest('.layout-with-navbar');\n let sideNav: Element | null;\n\n if (storyContainer) {\n sideNav = storyContainer.querySelector('modus-wc-side-navigation');\n } else {\n sideNav = document.querySelector('modus-wc-side-navigation');\n }\n\n if (sideNav) {\n (sideNav as HTMLElement & { expanded: boolean }).expanded = e.detail;\n }\n };\n\n return html`\n \n
    \n \n
    \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    \n
    \n

    \n The side navigation of an application provides context through\n accessible menu options and positions a consistent component to\n connect to various pages in the application.\n

    \n

    \n The side navigation is a collapsible side content of the site’s\n pages. It is located alongside the page’s primary content. The\n component is designed to add side content to a fullscreen\n application. It is activated through the “hamburger” menu in the\n Navbar.\n

    \n
    \n
    \n
    \n
    \n \n `;\n },\n};\n\nexport const WithSubmenu: Story = {\n render: (args) => {\n const handleMenuOpenChange = (e: CustomEvent) => {\n const eventSource = e.target as HTMLElement;\n const storyContainer = eventSource?.closest('.layout-with-navbar');\n let sideNav: HTMLElement | null;\n\n if (storyContainer) {\n sideNav = storyContainer.querySelector(\n 'modus-wc-side-navigation'\n ) as HTMLElement;\n\n if (sideNav) {\n // Toggle the side nav state (navbar and side nav can be out of sync)\n const sideNavEl = sideNav as HTMLElement & { expanded: boolean };\n sideNavEl.expanded = e.detail;\n }\n }\n };\n\n const handleExpandedChange = (e: CustomEvent) => {\n // Collapse all menu items when side nav closes\n if (!e.detail) {\n const eventSource = e.target as HTMLElement;\n const menuItems = eventSource.querySelectorAll('modus-wc-menu-item');\n menuItems.forEach((menuItem) => {\n const item = menuItem as unknown as {\n hasSubmenu?: boolean;\n collapseSubmenu?: () => Promise;\n };\n if (item.hasSubmenu && typeof item.collapseSubmenu === 'function') {\n void item.collapseSubmenu();\n }\n });\n }\n };\n\n return html`\n \n\n
    \n \n
    \n \n \n
  • \n
    \n \n \n \n \n \n \n \n \n \n
    \n
  • \n \n \n \n \n \n \n \n \n \n\n \n \n \n\n \n \n \n \n \n \n \n \n \n
    \n \n
    \n
    \n

    Side Navigation with Submenu

    \n

    \n This example demonstrates the side navigation component with\n submenus, allowing for a more organized and hierarchical\n navigation structure.\n

    \n

    \n When the side navigation closes, the expandedChange event is\n used to call the collapseSubmenu() method on each menu item.\n This keeps the side navigation component generic while allowing\n the story to coordinate behavior between components.\n

    \n

    \n Menu items inside a collapsed side nav cannot expand their\n submenus, ensuring a consistent user experience.\n

    \n
    \n
    \n
    \n
    \n \n `;\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n if (!customElements.get('side-navigation-shadow-host')) {\n const SideNavigationShadowHost =\n createShadowHostClass({\n componentTag: 'modus-wc-side-navigation',\n propsMapper: (v: SideNavigationArgs, el: HTMLElement) => {\n const navEl = el as unknown as {\n customClass: string;\n expanded: boolean;\n maxWidth: string;\n collapseOnClickOutside: boolean;\n mode: string;\n targetContent: string;\n };\n navEl.customClass = v['custom-class'] || '';\n navEl.expanded = Boolean(v.expanded);\n navEl.maxWidth = v['max-width'] || '256px';\n navEl.collapseOnClickOutside = Boolean(\n v['collapse-on-click-outside']\n );\n navEl.mode = v.mode || 'overlay';\n navEl.targetContent = v['target-content'] || '';\n\n // Build full layout on first render: move el into a navbar + content\n // layout inside the wrapper. The helper's wrapper has display:contents\n // so the layout becomes a direct layout child of the shadow root.\n if (!el.hasAttribute('data-layout-built')) {\n el.setAttribute('data-layout-built', '');\n\n const shadowRoot = el.getRootNode() as ShadowRoot;\n const wrapper = el.parentElement!;\n\n el.className = 'side-navigation';\n el.style.cssText =\n 'height: 500px; align-self: flex-start; position: relative;';\n\n // Navbar\n const navbar = document.createElement('modus-wc-navbar');\n navbar.setAttribute('style', 'z-index: 2;');\n navbar.className = 'navbar';\n (navbar as unknown as { userCard: object }).userCard = {\n avatarAlt: 'User Avatar',\n avatarSrc:\n 'https://i1.sndcdn.com/artworks-000405996468-wmh3uv-t500x500.jpg',\n email: 'user@trimble.com',\n name: 'Sonic the Hedgehog',\n };\n (navbar as unknown as { visibility: object }).visibility = {\n ai: true,\n apps: true,\n help: true,\n mainMenu: true,\n notifications: true,\n search: true,\n searchInput: false,\n user: true,\n };\n\n // Menu items\n const menu = document.createElement('modus-wc-menu');\n menu.setAttribute('size', 'lg');\n [\n { label: 'home', icon: 'home', selected: true },\n { label: 'profile', icon: 'person', selected: false },\n { label: 'settings', icon: 'gears', selected: false },\n ].forEach(({ label, icon, selected }) => {\n const item = document.createElement('modus-wc-menu-item');\n item.setAttribute('label', label);\n if (selected) item.setAttribute('selected', '');\n const ic = document.createElement('modus-wc-icon');\n ic.setAttribute('slot', 'start-icon');\n ic.setAttribute('name', icon);\n item.appendChild(ic);\n menu.appendChild(item);\n });\n el.appendChild(menu);\n\n // Wire navbar's mainMenuOpenChange to toggle side nav.\n // Now that navbar's handleClickOutside uses composedPath(), it\n // correctly identifies hamburger clicks inside shadow DOM and emits\n // the right true/false detail value on each click.\n navbar.addEventListener('mainMenuOpenChange', (e: Event) => {\n const custom = e as CustomEvent;\n (el as unknown as { expanded: boolean }).expanded =\n custom.detail;\n });\n\n // Panel content\n const panelContent = document.createElement('div');\n panelContent.className = 'panel-content';\n const po1 = document.createElement('p');\n po1.textContent =\n 'The side navigation of an application provides context through accessible menu options and positions a consistent component to connect to various pages in the application.';\n const po2 = document.createElement('p');\n po2.textContent =\n 'The side navigation is a collapsible side content of the site\\'s pages. It is located alongside the page\\'s primary content. The component is designed to add side content to a fullscreen application. It is activated through the \"hamburger\" menu in the Navbar.';\n panelContent.appendChild(po1);\n panelContent.appendChild(po2);\n\n // Move el into layout (appendChild moves an existing node)\n const mainContentRow = document.createElement('div');\n mainContentRow.className = 'main-content-row';\n mainContentRow.appendChild(el);\n mainContentRow.appendChild(panelContent);\n\n const layout = document.createElement('div');\n layout.className = 'layout-with-navbar';\n layout.appendChild(navbar);\n layout.appendChild(mainContentRow);\n\n wrapper.appendChild(layout);\n\n // Layout styles\n const styleEl = document.createElement('style');\n styleEl.textContent = `\n .layout-with-navbar {\n display: flex;\n flex-direction: column;\n height: 100%;\n }\n .main-content-row {\n display: flex;\n flex: 1;\n overflow: hidden;\n }\n .navbar {\n box-shadow: none;\n }\n .panel-content {\n margin-left: 4rem;\n padding: 10px;\n }\n .side-navigation {\n height: 500px;\n align-self: flex-start;\n position: relative;\n }\n `;\n shadowRoot.appendChild(styleEl);\n }\n },\n });\n customElements.define(\n 'side-navigation-shadow-host',\n SideNavigationShadowHost\n );\n }\n\n return html``;\n },\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-skeleton.json b/mcp/versions/1.5.0/component-docs/modus-wc-skeleton.json new file mode 100644 index 000000000..abd25ba32 --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-skeleton.json @@ -0,0 +1,65 @@ +{ + "description": "A customizable skeleton component used to create skeletons of various sizes and shapes", + "properties": [ + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the inner div.", + "default": "''", + "mutable": false, + "end_line": 20 + }, + { + "name": "height", + "type": "string", + "description": "The height of the skeleton.", + "default": "'var(--modus-wc-line-height-md)'", + "mutable": false, + "end_line": 23 + }, + { + "name": "shape", + "type": "'circle' | 'rectangle'", + "description": "The shape of the skeleton.", + "default": "'rectangle'", + "mutable": false, + "end_line": 26 + }, + { + "name": "width", + "type": "string", + "description": "The width of the skeleton.", + "default": "'100%'", + "mutable": false, + "end_line": 29 + } + ], + "events": [], + "methods": [], + "slots": [], + "examples": { + "basic": "", + "variations": [], + "args": { + "custom-class": "''", + "height": "'1.5rem'", + "shape": "'rectangle'", + "width": "'100%'" + }, + "argTypes": {}, + "usage": [] + }, + "tag": "modus-wc-skeleton", + "storyExample": { + "template": "", + "args": { + "custom-class": "''", + "height": "'1.5rem'", + "shape": "'rectangle'", + "width": "'100%'" + }, + "argTypes": {}, + "events": [], + "fullContent": "import { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\n\ninterface SkeletonArgs {\n 'custom-class'?: string;\n height?: string;\n shape?: 'circle' | 'rectangle';\n width?: string;\n}\n\nconst meta: Meta = {\n title: 'Components/Skeleton',\n component: 'modus-wc-skeleton',\n args: {\n 'custom-class': '',\n height: '1.5rem',\n shape: 'rectangle',\n width: '100%',\n },\n argTypes: {\n shape: {\n control: {\n type: 'select',\n },\n options: ['circle', 'rectangle'],\n },\n },\n parameters: {\n layout: 'padded',\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nexport const Default: Story = {\n render: (args) => {\n return html`\n \n `;\n },\n};\n\nexport const Circle: Story = {\n render: () => {\n return html`\n \n `;\n },\n};\n\nexport const Square: Story = {\n render: () => {\n return html`\n \n `;\n },\n};\n\nexport const Composed: Story = {\n render: () => {\n // prettier-ignore\n return html`\n\n
    \n
    \n \n
    \n \n \n
    \n
    \n \n
    \n `;\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n if (!customElements.get('skeleton-shadow-host')) {\n const SkeletonShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-skeleton',\n propsMapper: (v: SkeletonArgs, el: HTMLElement) => {\n const skeletonEl = el as unknown as {\n customClass: string;\n height: string;\n shape: string;\n width: string;\n };\n skeletonEl.customClass = v['custom-class'] || '';\n skeletonEl.height = v.height ?? '1.5rem';\n skeletonEl.shape = v.shape ?? 'rectangle';\n skeletonEl.width = v.width ?? '100%';\n },\n });\n customElements.define('skeleton-shadow-host', SkeletonShadowHost);\n }\n\n return html``;\n },\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-slider.json b/mcp/versions/1.5.0/component-docs/modus-wc-slider.json new file mode 100644 index 000000000..6567aa698 --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-slider.json @@ -0,0 +1,163 @@ +{ + "description": "A customizable slider component", + "properties": [ + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the inner div.", + "default": "''", + "mutable": false, + "end_line": 29 + }, + { + "name": "disabled", + "type": "boolean", + "description": "The disabled state of the slider.", + "default": "false", + "mutable": false, + "end_line": 32 + }, + { + "name": "inputId", + "type": "string", + "description": "The ID of the input element.", + "default": null, + "mutable": false, + "end_line": 35 + }, + { + "name": "inputTabIndex", + "type": "number", + "description": "The tabindex of the input.", + "default": null, + "mutable": false, + "end_line": 38 + }, + { + "name": "label", + "type": "string", + "description": "The text to display within the label.", + "default": null, + "mutable": false, + "end_line": 41 + }, + { + "name": "max", + "type": "number", + "description": "The maximum slider value.", + "default": null, + "mutable": false, + "end_line": 44 + }, + { + "name": "min", + "type": "number", + "description": "The minimum slider value.", + "default": null, + "mutable": false, + "end_line": 47 + }, + { + "name": "name", + "type": "string", + "description": "Name of the form control. Submitted with the form as part of a name/value pair.", + "default": "''", + "mutable": false, + "end_line": 50 + }, + { + "name": "required", + "type": "boolean", + "description": "A value is required for the form to be submittable.", + "default": "false", + "mutable": false, + "end_line": 53 + }, + { + "name": "size", + "type": "ModusSize", + "description": "The size of the input.", + "default": "'md'", + "mutable": false, + "end_line": 56 + }, + { + "name": "step", + "type": "number", + "description": "The increment of the slider.", + "default": null, + "mutable": false, + "end_line": 59 + }, + { + "name": "value", + "type": "number", + "description": "The value of the slider.", + "default": "0", + "mutable": true, + "end_line": 62 + } + ], + "events": [ + { + "name": "inputBlur", + "detail": "FocusEvent", + "description": "Emitted when the input loses focus.", + "end_line": 65 + }, + { + "name": "inputChange", + "detail": "InputEvent", + "description": "Emitted when the input value changes.", + "end_line": 68 + }, + { + "name": "inputFocus", + "detail": "FocusEvent", + "description": "Emitted when the input gains focus.", + "end_line": 71 + } + ], + "methods": [], + "slots": [], + "examples": { + "basic": "", + "variations": [], + "args": { + "custom-class": "''", + "disabled": "false", + "label": "'Label'", + "name": "''", + "required": "false", + "size": "'md'", + "value": "true" + }, + "argTypes": {}, + "usage": [], + "events": [ + "inputBlur", + "inputChange", + "inputFocus" + ] + }, + "tag": "modus-wc-slider", + "storyExample": { + "template": "", + "args": { + "custom-class": "''", + "disabled": "false", + "label": "'Label'", + "name": "''", + "required": "false", + "size": "'md'", + "value": "true" + }, + "argTypes": {}, + "events": [ + "inputBlur", + "inputChange", + "inputFocus" + ], + "fullContent": "import { withActions } from '@storybook/addon-actions/decorator';\nimport { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\nimport { ModusSize } from '../types';\n\ninterface SliderArgs {\n 'custom-class'?: string;\n disabled?: boolean;\n 'input-id'?: string;\n 'input-tab-index'?: number;\n label?: string;\n max?: number;\n min?: number;\n name?: string;\n required?: boolean;\n size?: ModusSize;\n step?: number;\n value: boolean;\n}\n\nconst meta: Meta = {\n title: 'Components/Forms/Slider',\n component: 'modus-wc-slider',\n args: {\n 'custom-class': '',\n disabled: false,\n label: 'Label',\n name: '',\n required: false,\n size: 'md',\n value: true,\n },\n argTypes: {\n size: {\n control: { type: 'select' },\n options: ['sm', 'md', 'lg'],\n },\n },\n decorators: [withActions],\n parameters: {\n actions: {\n handles: ['inputBlur', 'inputChange', 'inputFocus'],\n },\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nexport const Default: Story = {\n render: (args) => {\n return html`\n \n `;\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n // Create a unique shadow host for slider component\n if (!customElements.get('slider-shadow-host')) {\n const SliderShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-slider',\n propsMapper: (v: SliderArgs, el: HTMLElement) => {\n const sliderEl = el as unknown as {\n customClass: string;\n disabled: boolean;\n inputId: string;\n inputTabIndex: number;\n label: string;\n max: number;\n min: number;\n name: string;\n required: boolean;\n size: string;\n step: number;\n value: number;\n };\n sliderEl.customClass = v['custom-class'] || '';\n sliderEl.disabled = Boolean(v.disabled);\n sliderEl.inputId = v['input-id'] ?? '';\n sliderEl.inputTabIndex = v['input-tab-index'] ?? 0;\n sliderEl.label = v.label ?? '';\n sliderEl.max = v.max ?? 100;\n sliderEl.min = v.min ?? 0;\n sliderEl.name = v.name ?? '';\n sliderEl.required = Boolean(v.required);\n sliderEl.size = v.size ?? 'md';\n sliderEl.step = v.step ?? 1;\n sliderEl.value = typeof v.value === 'number' ? v.value : 0;\n },\n });\n customElements.define('slider-shadow-host', SliderShadowHost);\n }\n\n return html``;\n },\n};\nexport const Migration: Story = {\n parameters: {\n docs: {\n description: {\n story: `\n#### Breaking Changes\n\n - In 1.0 input state was maintained by the component. 2.0 components encourage users to follow a controlled\n input model. See the Form Inputs [documentation](/docs/documentation-form-inputs--docs) for additional info and examples.\n - Property names have changed: \\`max-value\\` → \\`max\\`, \\`min-value\\` → \\`min\\`.\n\n#### Prop Mapping\n\n| 1.0 Prop | 2.0 Prop | Notes |\n|-------------|--------------|-------|\n| aria-label | aria-label | |\n| disabled | disabled | |\n| label | label | |\n| max-value | max | |\n| min-value | min | |\n| value | value | |\n\n#### Event Mapping\n\n| 1.0 Event | 2.0 Event | Notes |\n|-------------|-------------|------------------|\n| valueChange | | Not carried over |\n| valueInput | inputChange | |\n `,\n },\n },\n controls: { disable: true },\n canvas: { disable: true },\n },\n render: () => html`
    `,\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-stepper.json b/mcp/versions/1.5.0/component-docs/modus-wc-stepper.json new file mode 100644 index 000000000..a74764bcd --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-stepper.json @@ -0,0 +1,47 @@ +{ + "description": "Used to show a list of steps in a process.", + "properties": [ + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the steps element.", + "default": "''", + "mutable": false, + "end_line": 40 + }, + { + "name": "orientation", + "type": "Orientation", + "description": "The orientation of the steps.", + "default": null, + "mutable": false, + "end_line": 43 + }, + { + "name": "steps", + "type": "IStepperItem[]", + "description": "The steps to display.", + "default": "[]", + "mutable": false, + "end_line": 46 + } + ], + "events": [], + "methods": [], + "slots": [], + "examples": { + "basic": "", + "variations": [], + "args": {}, + "argTypes": {}, + "usage": [] + }, + "tag": "modus-wc-stepper", + "storyExample": { + "template": "", + "args": {}, + "argTypes": {}, + "events": [], + "fullContent": "import { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\nimport { Orientation } from '../types';\n\ninterface StepperArgs {\n 'custom-class'?: string;\n orientation: Orientation;\n steps?: IStepperItem[];\n}\n\ninterface IStepperItem {\n color?:\n | 'primary'\n | 'secondary'\n | 'accent'\n | 'info'\n | 'success'\n | 'warning'\n | 'error'\n | 'neutral';\n content?: string;\n customClass?: string;\n label?: string;\n}\n\nconst meta: Meta = {\n title: 'Components/Stepper',\n component: 'modus-wc-stepper',\n args: {\n steps: [\n { label: 'Scale', color: 'primary' },\n { label: 'Belong', color: 'primary' },\n { label: 'Grow', color: 'warning' },\n { label: 'Innovate', content: '🚀' },\n ],\n },\n argTypes: {\n 'custom-class': {\n control: 'text',\n },\n orientation: {\n control: { type: 'select' },\n options: ['horizontal', 'vertical'],\n },\n steps: {\n description: 'Array of step objects defining the steps to display',\n table: {\n type: {\n detail: `\n Interface: IStepperItem\n Properties:\n - color ('primary' | 'secondary' | 'accent' | 'info' | 'success' | 'warning' | 'error' | 'neutral', optional): The color theme of the step\n - content (string, optional): Custom content to display in the step indicator\n - customClass (string, optional): Custom CSS class to apply to the step\n - label (string, optional): Text label for the step\n `,\n },\n },\n },\n },\n parameters: {\n layout: 'padded',\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nconst Template: Story = {\n // prettier-ignore\n render: (args) => html`\n\n\n\n\n `,\n};\n\nexport const Default: Story = { ...Template };\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n if (!customElements.get('stepper-shadow-host')) {\n const StepperShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-stepper',\n propsMapper: (v: StepperArgs, el: HTMLElement) => {\n const stepperEl = el as unknown as {\n customClass: string;\n orientation: string;\n steps: IStepperItem[];\n };\n stepperEl.customClass = v['custom-class'] || '';\n stepperEl.orientation = v.orientation ?? 'horizontal';\n stepperEl.steps = v.steps ?? [];\n },\n });\n customElements.define('stepper-shadow-host', StepperShadowHost);\n }\n\n return html``;\n },\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-switch.json b/mcp/versions/1.5.0/component-docs/modus-wc-switch.json new file mode 100644 index 000000000..40047fec7 --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-switch.json @@ -0,0 +1,149 @@ +{ + "description": "A customizable switch component", + "properties": [ + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the inner div.", + "default": "''", + "mutable": false, + "end_line": 30 + }, + { + "name": "disabled", + "type": "boolean", + "description": "The disabled state of the switch.", + "default": "false", + "mutable": false, + "end_line": 33 + }, + { + "name": "indeterminate", + "type": "boolean", + "description": "The indeterminate state of the switch.", + "default": "false", + "mutable": true, + "end_line": 36 + }, + { + "name": "inputId", + "type": "string", + "description": "The ID of the input element.", + "default": null, + "mutable": false, + "end_line": 39 + }, + { + "name": "inputTabIndex", + "type": "number", + "description": "The tabindex of the input.", + "default": null, + "mutable": false, + "end_line": 42 + }, + { + "name": "label", + "type": "string", + "description": "The text to display within the label.", + "default": null, + "mutable": false, + "end_line": 45 + }, + { + "name": "name", + "type": "string", + "description": "Name of the form control. Submitted with the form as part of a name/value pair.", + "default": "''", + "mutable": false, + "end_line": 48 + }, + { + "name": "required", + "type": "boolean", + "description": "A value is required for the form to be submittable.", + "default": "false", + "mutable": false, + "end_line": 51 + }, + { + "name": "size", + "type": "ModusSize | 'xs'", + "description": "The size of the input.", + "default": "'md'", + "mutable": false, + "end_line": 54 + }, + { + "name": "value", + "type": "boolean", + "description": "The value of the switch.", + "default": "false", + "mutable": true, + "end_line": 57 + } + ], + "events": [ + { + "name": "inputBlur", + "detail": "FocusEvent", + "description": "Emitted when the input loses focus.", + "end_line": 60 + }, + { + "name": "inputChange", + "detail": "InputEvent", + "description": "Emitted when the input value changes.", + "end_line": 63 + }, + { + "name": "inputFocus", + "detail": "FocusEvent", + "description": "Emitted when the input gains focus.", + "end_line": 66 + } + ], + "methods": [], + "slots": [], + "examples": { + "basic": "", + "variations": [], + "args": { + "custom-class": "''", + "disabled": "false", + "indeterminate": "false", + "label": "'Label'", + "name": "''", + "required": "false", + "size": "'md'", + "value": "true" + }, + "argTypes": {}, + "usage": [], + "events": [ + "inputBlur", + "inputChange", + "inputFocus" + ] + }, + "tag": "modus-wc-switch", + "storyExample": { + "template": "", + "args": { + "custom-class": "''", + "disabled": "false", + "indeterminate": "false", + "label": "'Label'", + "name": "''", + "required": "false", + "size": "'md'", + "value": "true" + }, + "argTypes": {}, + "events": [ + "inputBlur", + "inputChange", + "inputFocus" + ], + "fullContent": "import { withActions } from '@storybook/addon-actions/decorator';\nimport { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\nimport { ModusSize } from '../types';\n\ninterface SwitchArgs {\n 'custom-class'?: string;\n disabled?: boolean;\n indeterminate?: boolean;\n 'input-id'?: string;\n 'input-tab-index'?: number;\n label?: string;\n name?: string;\n required?: boolean;\n size?: ModusSize | 'xs';\n value: boolean;\n}\n\nconst meta: Meta = {\n title: 'Components/Forms/Switch',\n component: 'modus-wc-switch',\n args: {\n 'custom-class': '',\n disabled: false,\n indeterminate: false,\n label: 'Label',\n name: '',\n required: false,\n size: 'md',\n value: true,\n },\n argTypes: {\n size: {\n control: { type: 'select' },\n options: ['xs', 'sm', 'md', 'lg'],\n },\n },\n decorators: [withActions],\n parameters: {\n actions: {\n handles: ['inputBlur', 'inputChange', 'inputFocus'],\n },\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nexport const Default: Story = {\n render: (args) => {\n return html`\n \n `;\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n // Create a unique shadow host for switch component\n if (!customElements.get('switch-shadow-host')) {\n const SwitchShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-switch',\n propsMapper: (v: SwitchArgs, el: HTMLElement) => {\n const switchEl = el as unknown as {\n customClass: string;\n disabled: boolean;\n indeterminate: boolean;\n inputId: string;\n inputTabIndex: number;\n label: string;\n name: string;\n required: boolean;\n size: string;\n value: boolean;\n };\n switchEl.customClass = v['custom-class'] || '';\n switchEl.disabled = Boolean(v.disabled);\n switchEl.indeterminate = Boolean(v.indeterminate);\n switchEl.inputId = v['input-id'] || '';\n switchEl.inputTabIndex = v['input-tab-index'] || 0;\n switchEl.label = v.label || '';\n switchEl.name = v.name || '';\n switchEl.required = Boolean(v.required);\n switchEl.size = v.size || 'md';\n switchEl.value = Boolean(v.value);\n },\n });\n customElements.define('switch-shadow-host', SwitchShadowHost);\n }\n\n return html``;\n },\n};\nexport const Migration: Story = {\n parameters: {\n docs: {\n description: {\n story: `\n#### Breaking Changes\n\n - In 1.0 input state was maintained by the component. 2.0 components encourage users to follow a controlled\n input model. See the Form Inputs documentation for additional info and examples.\n - \\`checked\\` prop has been renamed to \\`value\\` to maintain consistency across form components.\n - Size values have changed from verbose names (\\`small\\`, \\`medium\\`) to abbreviations (\\`sm\\`, \\`md\\`, \\`lg\\`).\n\n#### Prop Mapping\n\n| 1.0 Prop | 2.0 Prop | Notes |\n|------------------|---------------------|-------------------------------------------------------------|\n| aria-label | aria-label | |\n| checked | value | |\n| disabled | disabled | |\n| label | label | |\n| size | size | \\`small\\` → \\`sm\\`, \\`medium\\` → \\`md\\` |\n\n#### Event Mapping\n\n| 1.0 Event | 2.0 Event | Notes |\n|----------------|----------------|-------------------------------------------------------|\n| switchClick | inputChange | |\n `,\n },\n },\n controls: { disable: true },\n canvas: { disable: true },\n },\n render: () => html`
    `,\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-table.json b/mcp/versions/1.5.0/component-docs/modus-wc-table.json new file mode 100644 index 000000000..e58cc74b1 --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-table.json @@ -0,0 +1,202 @@ +{ + "description": "", + "properties": [ + { + "name": "editable", + "type": "boolean | ((row: Record)", + "description": "Enable cell editing. Either a boolean (all rows) or a predicate per row.", + "default": "> boolean) = false", + "mutable": false, + "end_line": 95 + }, + { + "name": "columns", + "type": "ITableColumn[]", + "description": "An array of column definitions.", + "default": null, + "mutable": false, + "end_line": 98 + }, + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the inner div.", + "default": "''", + "mutable": false, + "end_line": 101 + }, + { + "name": "data", + "type": "Record[]", + "description": "An array of data objects.", + "default": null, + "mutable": false, + "end_line": 104 + }, + { + "name": "density", + "type": "Density", + "description": "The density of the table, used to save space or increase readability.", + "default": "'comfortable'", + "mutable": false, + "end_line": 107 + }, + { + "name": "hover", + "type": "boolean", + "description": "Enable hover effect on table rows.", + "default": "true", + "mutable": false, + "end_line": 110 + }, + { + "name": "currentPage", + "type": "number", + "description": "The current page number in pagination (1-based index).", + "default": "1", + "mutable": false, + "end_line": 113 + }, + { + "name": "paginated", + "type": "boolean", + "description": "Enable pagination for the table.", + "default": "false", + "mutable": false, + "end_line": 116 + }, + { + "name": "pageSizeOptions", + "type": "number[]", + "description": "Available options for the number of rows per page.", + "default": "[5, 10, 15]", + "mutable": false, + "end_line": 119 + }, + { + "name": "showPageSizeSelector", + "type": "boolean", + "description": "Show/hide the page size selector in pagination.", + "default": "true", + "mutable": false, + "end_line": 122 + }, + { + "name": "sortable", + "type": "boolean", + "description": "Enable sorting functionality for sortable columns.", + "default": "true", + "mutable": false, + "end_line": 125 + }, + { + "name": "selectable", + "type": "'none' | 'single' | 'multi'", + "description": "Row selection mode: 'none' for no selection, 'single' for single row, 'multi' for multiple rows.", + "default": "'none'", + "mutable": false, + "end_line": 128 + }, + { + "name": "selectedRowIds", + "type": "string[]", + "description": "Array of selected row IDs. Used for controlled selection state.", + "default": null, + "mutable": false, + "end_line": 131 + }, + { + "name": "zebra", + "type": "boolean", + "description": "Zebra striped tables differentiate rows by styling them in an alternating fashion.", + "default": "false", + "mutable": false, + "end_line": 134 + }, + { + "name": "caption", + "type": "string", + "description": "Accessibility caption for the table (visually hidden but available to screen readers).", + "default": null, + "mutable": false, + "end_line": 137 + } + ], + "events": [ + { + "name": "cellEditStart", + "detail": "{ rowIndex: number; colId: string; }", + "description": "Emits when cell editing starts.", + "end_line": 146 + }, + { + "name": "cellEditCommit", + "detail": "{ rowIndex: number; colId: string; newValue: unknown; updatedRow: Record\n \n `;\n },\n args: {\n density: 'comfortable',\n hover: false,\n sortable: true,\n paginated: false,\n 'show-page-size-selector': true,\n 'custom-class': '',\n selectable: 'none',\n zebra: false,\n 'current-page': 1,\n 'page-size-options': [5, 10, 15],\n 'selected-row-ids': [],\n editable: false,\n },\n};\n\nexport const Hover: Story = {\n render: (args) => {\n const columns = args.columns || createDemoColumns();\n const data = args.data || createDemoData();\n return html`\n \n \n `;\n },\n args: {\n density: 'comfortable',\n hover: true,\n },\n};\n\nexport const Sorting: Story = {\n render: (args) => {\n const columns = args.columns || createSortableColumns();\n const data = args.data || createDemoData();\n return html`\n \n \n `;\n },\n args: {\n density: 'comfortable',\n sortable: true,\n },\n};\n\nexport const Pagination: Story = {\n render: (args) => {\n const columns = args.columns || createDemoColumns();\n const data = args.data || createDemoData(15);\n return html`\n \n \n `;\n },\n args: {\n density: 'comfortable',\n paginated: true,\n 'show-page-size-selector': true,\n },\n};\n\nexport const CheckBoxRowSelection: Story = {\n render: (args) => {\n const columns = args.columns || createDemoColumns();\n const data = args.data || createDemoData();\n return html`\n \n \n `;\n },\n args: {\n density: 'comfortable',\n selectable: 'multi',\n },\n};\n\nexport const InlineEditing: Story = {\n render: (args) => {\n const columns: ITableColumn[] = [\n {\n id: 'id',\n header: 'ID',\n accessor: 'id',\n width: '20px',\n },\n {\n id: 'name',\n header: 'Name',\n accessor: 'name',\n },\n {\n id: 'status',\n header: 'Status',\n accessor: 'status',\n editor: 'custom',\n customEditorRenderer: (value, onCommit) => {\n const container = document.createElement('div');\n container.style.width = '100%';\n\n const autocomplete = document.createElement('modus-wc-autocomplete');\n autocomplete.items = [\n { label: 'Active', value: 'Active', visibleInMenu: true },\n { label: 'Inactive', value: 'Inactive', visibleInMenu: true },\n { label: 'Pending', value: 'Pending', visibleInMenu: true },\n ];\n autocomplete.value = value as string;\n autocomplete.style.width = '100%';\n\n const handleItemSelect = (e: CustomEvent) => {\n onCommit(e.detail.value);\n };\n\n autocomplete.addEventListener(\n 'itemSelect',\n handleItemSelect as EventListener\n );\n container.appendChild(autocomplete);\n\n setTimeout(() => {\n const input = autocomplete.querySelector('input');\n input?.focus();\n }, 0);\n\n return container;\n },\n cellRenderer: (value) => {\n const statusColors = {\n Active: 'green',\n Inactive: 'gray',\n Pending: 'blue',\n };\n const color = statusColors[value as string] || 'black';\n const span = document.createElement('span');\n span.textContent = value as string;\n span.style.color = color;\n span.style.fontWeight = 'bold';\n return span;\n },\n },\n {\n id: 'dueDate',\n header: 'Due Date',\n accessor: 'dueDate',\n editor: 'custom',\n customEditorRenderer: (value, onCommit) => {\n const container = document.createElement('div');\n container.style.width = '100%';\n\n const datePicker = document.createElement('modus-wc-date');\n datePicker.value = value as string;\n datePicker.style.width = '100%';\n datePicker.bordered = false;\n\n let isCommitting = false;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n const input = datePicker.querySelector('input');\n if (input && input.value && !isCommitting) {\n isCommitting = true;\n onCommit(input.value);\n }\n } else if (e.key === 'Escape') {\n e.preventDefault();\n if (!isCommitting) {\n isCommitting = true;\n onCommit(value || '');\n }\n } else if (e.key === 'Tab') {\n // Allow Tab to commit and move to next cell\n const input = datePicker.querySelector('input');\n if (input && input.value && !isCommitting) {\n isCommitting = true;\n onCommit(input.value);\n }\n }\n };\n\n // Only commit when focus leaves the entire container (not just the input)\n const handleContainerBlur = (e: FocusEvent) => {\n const relatedTarget = e.relatedTarget as HTMLElement;\n\n // If focus is moving within the container or date picker, don't commit\n if (\n relatedTarget &&\n (container.contains(relatedTarget) ||\n datePicker.shadowRoot?.contains(relatedTarget))\n ) {\n return;\n }\n\n const calendar = datePicker.shadowRoot?.querySelector(\n '[class*=\"calendar\"]'\n );\n if (calendar) {\n return;\n }\n\n const input = datePicker.querySelector('input');\n if (input && input.value && !isCommitting) {\n isCommitting = true;\n setTimeout(() => onCommit(input.value), 50);\n }\n };\n\n container.addEventListener('keydown', handleKeyDown);\n container.addEventListener('focusout', handleContainerBlur);\n container.appendChild(datePicker);\n\n setTimeout(() => {\n const input = datePicker.querySelector('input');\n input?.focus();\n }, 0);\n\n return container;\n },\n cellRenderer: (value): string => {\n if (!value) return '-';\n\n // Parse dd-mm-yyyy format from date picker\n const dateString = value as string;\n const parts = dateString.split(/[-/]/);\n\n let date: Date;\n if (parts.length === 3 && parts[0].length <= 2) {\n // Assume dd-mm-yyyy or dd/mm/yyyy format\n const day = parseInt(parts[0], 10);\n const month = parseInt(parts[1], 10) - 1; // Month is 0-indexed\n const year = parseInt(parts[2], 10);\n date = new Date(year, month, day);\n } else {\n // Fallback to default parsing\n date = new Date(dateString);\n }\n\n // Check if date is valid\n if (isNaN(date.getTime())) {\n return dateString; // Return original value if parsing fails\n }\n\n // Format date with dashes: dd-mm-yyyy\n const formattedDay = date.getDate().toString().padStart(2, '0');\n const formattedMonth = (date.getMonth() + 1)\n .toString()\n .padStart(2, '0');\n const formattedYear = date.getFullYear();\n\n return `${formattedDay}-${formattedMonth}-${formattedYear}`;\n },\n },\n ];\n\n const data = [\n {\n id: '1',\n name: 'John Doe',\n status: 'Active',\n dueDate: '15-10-2025',\n },\n {\n id: '2',\n name: 'Jane Smith',\n status: 'Inactive',\n dueDate: '20-11-2025',\n },\n {\n id: '3',\n name: 'Bob Johnson',\n status: 'Pending',\n dueDate: '05-12-2025',\n },\n ];\n\n return html`\n \n \n `;\n },\n args: {\n density: 'comfortable',\n hover: false,\n sortable: true,\n paginated: false,\n 'show-page-size-selector': true,\n 'custom-class': '',\n selectable: 'none',\n zebra: false,\n 'current-page': 1,\n 'page-size-options': [5, 10, 15],\n 'selected-row-ids': [],\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n if (!customElements.get('table-shadow-host')) {\n const TableShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-table',\n propsMapper: (v: TableStoryArgs, el: HTMLElement) => {\n const tableEl = el as unknown as {\n caption: string;\n columns: ITableColumn[];\n currentPage: number;\n data: Record[];\n customClass: string;\n density: string;\n editable: boolean;\n hover: boolean;\n paginated: boolean;\n pageSizeOptions: number[];\n selectable: string;\n selectedRowIds: string[];\n showPageSizeSelector: boolean;\n sortable: boolean;\n zebra: boolean;\n };\n tableEl.caption = v.caption ?? '';\n tableEl.columns = v.columns ?? createDemoColumns();\n tableEl.currentPage = v['current-page'] ?? 1;\n tableEl.data = v.data ?? createDemoData();\n tableEl.customClass = v['custom-class'] || '';\n tableEl.density = v.density ?? 'comfortable';\n tableEl.editable = Boolean(v.editable);\n tableEl.hover = Boolean(v.hover);\n tableEl.paginated = Boolean(v.paginated);\n tableEl.pageSizeOptions = v['page-size-options'] ?? [5, 10, 15];\n tableEl.selectable = v.selectable ?? 'none';\n tableEl.selectedRowIds = v['selected-row-ids'] ?? [];\n tableEl.showPageSizeSelector = v['show-page-size-selector'] !== false;\n tableEl.sortable = Boolean(v.sortable);\n tableEl.zebra = Boolean(v.zebra);\n // Wire events once — Stencil events don't bubble out of shadow root\n if (!el.dataset['eventsWired']) {\n el.addEventListener('rowClick', action('rowClick'));\n el.addEventListener('sortChange', action('sortChange'));\n el.addEventListener('paginationChange', action('paginationChange'));\n el.addEventListener(\n 'rowSelectionChange',\n action('rowSelectionChange')\n );\n el.addEventListener('cellEditStart', action('cellEditStart'));\n el.addEventListener('cellEditCommit', action('cellEditCommit'));\n el.dataset['eventsWired'] = 'true';\n }\n },\n });\n customElements.define('table-shadow-host', TableShadowHost);\n }\n\n return html``;\n },\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-tabs.json b/mcp/versions/1.5.0/component-docs/modus-wc-tabs.json new file mode 100644 index 000000000..47b14a61c --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-tabs.json @@ -0,0 +1,86 @@ +{ + "description": "A customizable tabs component used to create groups of tabs. The component supports a `` for injecting custom tab content.", + "properties": [ + { + "name": "activeTabIndex", + "type": "number", + "description": "The current active tab", + "default": "0", + "mutable": true, + "end_line": 55 + }, + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the inner div.", + "default": "''", + "mutable": false, + "end_line": 58 + }, + { + "name": "size", + "type": "ModusSize", + "description": "The size of the tabs.", + "default": "'md'", + "mutable": false, + "end_line": 61 + }, + { + "name": "tabs", + "type": "ITab[]", + "description": "The tabs to display.", + "default": "[]", + "mutable": false, + "end_line": 64 + }, + { + "name": "tabStyle", + "type": "'boxed' | 'bordered' | 'lifted' | 'none'", + "description": "Additional styling for the tabs.", + "default": "'bordered'", + "mutable": false, + "end_line": 67 + } + ], + "events": [ + { + "name": "tabChange", + "detail": "{ previousTab: number; newTab: number; }", + "description": "When a tab is switched to, this event outputs the relevant indices", + "end_line": 73 + } + ], + "methods": [], + "slots": [ + { + "name": "default", + "description": "Slot for default content" + } + ], + "examples": { + "basic": "\n", + "variations": [], + "args": { + "size": "'md'", + "tab-style": "'bordered'" + }, + "argTypes": {}, + "usage": [], + "events": [ + "tabChange" + ] + }, + "tag": "modus-wc-tabs", + "storyExample": { + "template": "\n", + "args": { + "size": "'md'", + "tab-style": "'bordered'" + }, + "argTypes": {}, + "events": [ + "tabChange" + ], + "fullContent": "import { withActions } from '@storybook/addon-actions/decorator';\nimport { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { ITab } from './modus-wc-tabs';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\nimport { DaisySize } from '../types';\n\ninterface TabsArgs {\n activeTabIndex?: number;\n 'custom-class'?: string;\n size?: DaisySize;\n tabs: ITab[];\n 'tab-style': 'boxed' | 'bordered' | 'lifted' | 'none';\n}\n\nconst meta: Meta = {\n title: 'Components/Tabs',\n component: 'modus-wc-tabs',\n args: {\n size: 'md',\n tabs: [\n { label: 'Tab 1' },\n { label: 'Tab 2' },\n { label: 'Tab 3', disabled: true },\n { icon: 'home' },\n ],\n 'tab-style': 'bordered',\n },\n argTypes: {\n tabs: {\n description: 'Array of tab objects defining the tabs to display',\n table: {\n type: {\n detail: `\n Interface: ITab\n Properties:\n - customClass (string, optional): Custom CSS class for the inner button\n - disabled (boolean, optional): Whether the tab is disabled\n - icon (string, optional): A Modus Icon name to display\n - iconPosition ('left' | 'right', optional): The position of the icon\n - label (string, optional): The content to display in the tab\n - slotName (string, optional): The slot name to use for custom tab header content\n `,\n },\n },\n },\n 'tab-style': {\n control: { type: 'select' },\n options: ['boxed', 'bordered', 'lifted', 'none'],\n },\n size: {\n control: { type: 'select' },\n options: ['xs', 'sm', 'md', 'lg'],\n },\n },\n decorators: [\n withActions,\n (story, context) => {\n // Normalize tabs before rendering the story\n const { tabs } = context.args ?? {};\n\n if (Array.isArray(tabs)) {\n context.args.tabs = normalizeTabs(tabs);\n }\n\n return story();\n },\n ],\n parameters: {\n actions: {\n handles: ['tabChange'],\n },\n },\n};\n\nexport default meta;\n\n// Shared normalization function used by both decorator and source code\nconst normalizeTabs = (tabs: ITab[]) => {\n return tabs.map((tab) => {\n const normalized = { ...tab };\n // Only keep disabled when it is explicitly true\n if (normalized.disabled !== true) {\n delete normalized.disabled;\n }\n return normalized;\n });\n};\n\nconst getSourceCode = (tabs: ITab[]) => {\n const normalizedTabs = normalizeTabs(tabs);\n return `\n`;\n};\n\ntype Story = StoryObj;\n\nconst Template: Story = {\n parameters: {\n docs: {\n source: {\n transform: (_src, { args }) => `\n${getSourceCode(args.tabs as ITab[])}`,\n },\n },\n },\n render: (args) => {\n // prettier-ignore\n return html`\n\n\n `;\n },\n};\n\nexport const Default: Story = { ...Template };\n\nexport const CustomContent: Story = {\n args: {\n tabs: [\n {\n icon: 'home',\n iconPosition: 'left',\n label: 'Home',\n slotName: 'home-tab-content',\n },\n {\n icon: 'clipboard',\n iconPosition: 'right',\n label: 'Tasks',\n },\n {\n slotName: 'actions-tab-content',\n },\n {\n slotName: 'notifications-tab-content',\n },\n ],\n },\n parameters: {\n docs: {\n description: {\n story:\n 'Tabs now include slots, offering a flexible approach for users to add relevant components within the tab for more complex use cases.',\n },\n source: {\n transform: (_src, { args }) => `\n\n \n \n \n Home\n \n \n \n Actions\n \n \n \n Notifications\n 5\n \n${getSourceCode(args.tabs as ITab[])}`,\n },\n },\n },\n\n // prettier-ignore\n render: (args) => {\n return html`\n \n \n \n \n \n Home\n \n \n \n Actions\n \n \n \n Notifications\n 5\n \n \n \n `;\n },\n};\n\nexport const ActiveAndDisabled: Story = {\n ...Template,\n args: {\n activeTabIndex: 1,\n tabs: [\n { label: 'Normal' },\n { label: 'Active' },\n { label: 'Disabled', disabled: true },\n ],\n },\n};\n\nexport const Icons: Story = {\n ...Template,\n args: {\n tabs: [\n { icon: 'home' },\n { icon: 'settings', iconPosition: 'left', label: 'Settings' },\n {\n icon: 'alert',\n iconPosition: 'right',\n label: 'Alerts',\n },\n ],\n },\n};\n\nexport const TabsWithPanel: Story = {\n parameters: {\n docs: {\n source: {\n transform: (_src, { args }) => `\n

    \n Modus (noun) : a mode of procedure : a way of doing something\n

    \n

    \n input (noun) : information fed into a data processing system or computer\n

    \n

    \n secret (noun) : kept from knowledge or view : hidden\n

    \n

    \n snapshot (noun) : an impression or view of something brief or transitory\n

    \n${getSourceCode(args.tabs as ITab[])}`,\n },\n },\n },\n render: (args) => {\n // prettier-ignore\n return html`\n\n

    \n Modus (noun) : a mode of procedure : a way of doing something\n

    \n

    \n input (noun) : information fed into a data processing system or computer\n

    \n

    \n secret (noun) : kept from knowledge or view : hidden\n

    \n

    \n snapshot (noun) : an impression or view of something brief or transitory\n

    \n\n `;\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n if (!customElements.get('tabs-shadow-host')) {\n const TabsShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-tabs',\n propsMapper: (v: TabsArgs, el: HTMLElement) => {\n const tabsEl = el as unknown as {\n activeTabIndex: number;\n customClass: string;\n size: string;\n tabs: ITab[];\n tabStyle: string;\n };\n tabsEl.activeTabIndex = v.activeTabIndex ?? 0;\n tabsEl.customClass = v['custom-class'] || '';\n tabsEl.size = v.size ?? 'md';\n tabsEl.tabs = v.tabs;\n tabsEl.tabStyle = v['tab-style'] ?? 'bordered';\n },\n });\n customElements.define('tabs-shadow-host', TabsShadowHost);\n }\n\n return html``;\n },\n};\nexport const Migration: Story = {\n parameters: {\n docs: {\n description: {\n story: `\n#### Breaking Changes\n\n - In 2.0 tabs use the \\`ITab\\` interface, see details of interface changes below.\n - Size values have changed from verbose names (\\`small\\`, \\`medium\\`) to abbreviations (\\`xs\\`, \\`sm\\`, \\`md\\`, \\`lg\\`).\n - The \\`tabChange\\` event now emits an object with both previous and new tab indices, rather than just the tab ID.\n\n#### Prop Mapping\n\n| 1.0 Prop | 2.0 Prop | Notes |\n|--------------|--------------------|----------------------------------------------------------------|\n| aria-label | aria-label | |\n| full-width | | Not carried over, use CSS instead |\n| size | size | \\`small\\` → \\`sm\\`, \\`medium\\` → \\`md\\` |\n| tabs | tabs | Tab object structure has changed. See Interface changes below. |\n\n#### Event Mapping\n\n| 1.0 Event | 2.0 Event | Notes |\n|-------------|-----------|-------------------------------------------------------|\n| tabChange | tabChange | Now emits \\`{ previousTab: number; newTab: number }\\` |\n\n#### Interfaces\n\n##### 1.0\n\n\\`\\`\\`typescript\nexport interface Tab {\n active?: boolean;\n iconOnly?: string;\n id: string;\n label?: string;\n leftIcon?: string;\n rightIcon?: string;\n}\n\\`\\`\\`\n\n##### 2.0\n\n\\`\\`\\`typescript\nexport interface ITab {\n customClass?: string;\n disabled?: boolean;\n icon?: string;\n iconPosition?: 'left' | 'right';\n label?: string;\n}\n\\`\\`\\`\n `,\n },\n },\n controls: { disable: true },\n canvas: { disable: true },\n },\n render: () => html`
    `,\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-text-input.json b/mcp/versions/1.5.0/component-docs/modus-wc-text-input.json new file mode 100644 index 000000000..5631ee95b --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-text-input.json @@ -0,0 +1,278 @@ +{ + "description": "A customizable input component used to create text inputs with types. The component supports a `` for injecting additional custom content inside the input, such as icons or formatted text.", + "properties": [ + { + "name": "autoCapitalize", + "type": "| 'off' | 'none' | 'on' | 'sentences' | 'words' | 'characters'", + "description": "Controls automatic capitalization in inputted text.", + "default": null, + "mutable": false, + "end_line": 50 + }, + { + "name": "autoComplete", + "type": "AutocompleteTypes", + "description": "Hint for form autofill feature.", + "default": null, + "mutable": false, + "end_line": 53 + }, + { + "name": "autoCorrect", + "type": "'on' | 'off'", + "description": "Controls automatic correction in inputted text. Support by browser varies.", + "default": null, + "mutable": false, + "end_line": 56 + }, + { + "name": "bordered", + "type": "boolean", + "description": "Indicates that the input should have a border.", + "default": "true", + "mutable": false, + "end_line": 59 + }, + { + "name": "clearAriaLabel", + "type": "string", + "description": "Aria label for the clear icon button.", + "default": "'Clear text'", + "mutable": false, + "end_line": 62 + }, + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the input.", + "default": "''", + "mutable": false, + "end_line": 65 + }, + { + "name": "disabled", + "type": "boolean", + "description": "Whether the form control is disabled.", + "default": "false", + "mutable": false, + "end_line": 68 + }, + { + "name": "enterkeyhint", + "type": "| 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send'", + "description": "A hint to the browser for which enter key to display.", + "default": null, + "mutable": false, + "end_line": 78 + }, + { + "name": "feedback", + "type": "IInputFeedbackProp", + "description": "Feedback to render below the input.", + "default": null, + "mutable": false, + "end_line": 81 + }, + { + "name": "includeClear", + "type": "boolean", + "description": "Show the clear button within the input field.", + "default": "false", + "mutable": false, + "end_line": 84 + }, + { + "name": "includeSearch", + "type": "boolean", + "description": "Show the search icon within the input field.", + "default": "false", + "mutable": false, + "end_line": 87 + }, + { + "name": "inputId", + "type": "string", + "description": "The ID of the input element.", + "default": null, + "mutable": false, + "end_line": 90 + }, + { + "name": "inputTabIndex", + "type": "number", + "description": "Determine the control's relative ordering for sequential focus navigation (typically with the Tab key).", + "default": null, + "mutable": false, + "end_line": 93 + }, + { + "name": "label", + "type": "string", + "description": "The text to display within the label.", + "default": null, + "mutable": false, + "end_line": 96 + }, + { + "name": "maxLength", + "type": "number", + "description": "Maximum length (number of characters) of value.", + "default": null, + "mutable": false, + "end_line": 99 + }, + { + "name": "minLength", + "type": "number", + "description": "Minimum length (number of characters) of value.", + "default": null, + "mutable": false, + "end_line": 102 + }, + { + "name": "name", + "type": "string", + "description": "Name of the form control. Submitted with the form as part of a name/value pair.", + "default": null, + "mutable": false, + "end_line": 105 + }, + { + "name": "pattern", + "type": "string", + "description": "Pattern the value must match to be valid", + "default": null, + "mutable": false, + "end_line": 108 + }, + { + "name": "placeholder", + "type": "string", + "description": "Text that appears in the form control when it has no value set.", + "default": "''", + "mutable": false, + "end_line": 111 + }, + { + "name": "readOnly", + "type": "boolean", + "description": "Whether the value is editable.", + "default": "false", + "mutable": false, + "end_line": 114 + }, + { + "name": "required", + "type": "boolean", + "description": "A value is required for the form to be submittable.", + "default": "false", + "mutable": false, + "end_line": 117 + }, + { + "name": "size", + "type": "ModusSize", + "description": "The size of the input.", + "default": "'md'", + "mutable": false, + "end_line": 120 + }, + { + "name": "type", + "type": "TextFieldTypes", + "description": "Type of form control.", + "default": "'text'", + "mutable": false, + "end_line": 123 + }, + { + "name": "value", + "type": "string", + "description": "The value of the control.", + "default": "''", + "mutable": true, + "end_line": 126 + } + ], + "events": [ + { + "name": "clearClick", + "detail": "void", + "description": "Event emitted when the clear button is clicked.", + "end_line": 129 + }, + { + "name": "inputBlur", + "detail": "FocusEvent", + "description": "Event emitted when the input loses focus.", + "end_line": 132 + }, + { + "name": "inputChange", + "detail": "InputEvent", + "description": "Event emitted when the input value changes.", + "end_line": 135 + }, + { + "name": "inputFocus", + "detail": "FocusEvent", + "description": "Event emitted when the input gains focus.", + "end_line": 138 + } + ], + "methods": [], + "slots": [ + { + "name": "custom-icon", + "description": "Slot for custom-icon content" + } + ], + "examples": { + "basic": "", + "variations": [], + "args": { + "bordered": "true", + "disabled": "false", + "include-clear": "false", + "include-search": "false", + "inputmode": "'text'", + "label": "'Label'", + "size": "'md'", + "spellcheck": "false", + "type": "'text'", + "value": "''" + }, + "argTypes": {}, + "usage": [], + "events": [ + "clearClick", + "inputBlur", + "inputChange", + "inputFocus" + ] + }, + "tag": "modus-wc-text-input", + "storyExample": { + "template": "", + "args": { + "bordered": "true", + "disabled": "false", + "include-clear": "false", + "include-search": "false", + "inputmode": "'text'", + "label": "'Label'", + "size": "'md'", + "spellcheck": "false", + "type": "'text'", + "value": "''" + }, + "argTypes": {}, + "events": [ + "clearClick", + "inputBlur", + "inputChange", + "inputFocus" + ], + "fullContent": "import { withActions } from '@storybook/addon-actions/decorator';\nimport { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\nimport { AutocompleteTypes, IInputFeedbackProp, ModusSize } from '../types';\n\ninterface TextInputArgs {\n 'auto-capitalize'?:\n | 'off'\n | 'none'\n | 'on'\n | 'sentences'\n | 'words'\n | 'characters';\n 'auto-complete'?: AutocompleteTypes;\n 'auto-correct'?: 'on' | 'off';\n bordered?: boolean;\n 'clear-aria-label'?: string;\n 'custom-class'?: string;\n disabled?: boolean;\n enterkeyhint?:\n | 'enter'\n | 'done'\n | 'go'\n | 'next'\n | 'previous'\n | 'search'\n | 'send';\n feedback?: IInputFeedbackProp;\n 'include-clear'?: boolean;\n 'include-search'?: boolean;\n 'input-id'?: string;\n inputmode?:\n | 'decimal'\n | 'email'\n | 'none'\n | 'numeric'\n | 'search'\n | 'tel'\n | 'text'\n | 'url';\n 'input-tab-index'?: number;\n label?: string;\n 'max-length': number;\n 'min-length': number;\n name?: string;\n pattern?: string;\n placeholder?: string;\n 'read-only'?: boolean;\n required?: boolean;\n size?: ModusSize;\n spellcheck?: boolean;\n type?: 'email' | 'password' | 'search' | 'tel' | 'text' | 'url';\n value: string;\n}\n\nconst meta: Meta = {\n title: 'Components/Forms/Text Input',\n component: 'modus-wc-text-input',\n args: {\n bordered: true,\n disabled: false,\n 'include-clear': false,\n 'include-search': false,\n inputmode: 'text',\n label: 'Label',\n size: 'md',\n spellcheck: false,\n type: 'text',\n value: '',\n },\n argTypes: {\n 'auto-capitalize': {\n options: ['off', 'none', 'on', 'sentences', 'words', 'characters'],\n },\n 'auto-complete': {\n control: { type: 'text' },\n },\n 'auto-correct': {\n options: ['on', 'off'],\n },\n enterkeyhint: {\n options: ['enter', 'done', 'go', 'next', 'previous', 'search', 'send'],\n },\n feedback: {\n description: 'Feedback prop for input components',\n table: {\n type: {\n detail: `\n Interface: IInputFeedbackProp\n Properties:\n - level ('error' | 'info' | 'success' | 'warning'): The feedback level\n - message (string, optional): The feedback message\n `,\n },\n },\n },\n inputmode: {\n control: { type: 'select' },\n options: [\n 'decimal',\n 'email',\n 'none',\n 'numeric',\n 'search',\n 'tel',\n 'text',\n 'url',\n ],\n },\n size: {\n options: ['sm', 'md', 'lg'],\n },\n spellcheck: {\n description:\n 'Whether the element may be checked for spelling errors. A hint for the browser, not a guarantee.',\n table: {\n category: 'attributes',\n defaultValue: { summary: 'false' },\n },\n },\n type: {\n options: ['email', 'password', 'search', 'tel', 'text', 'url'],\n },\n },\n decorators: [withActions],\n parameters: {\n actions: {\n handles: ['clearClick', 'inputBlur', 'inputChange', 'inputFocus'],\n },\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nconst Template: Story = {\n render: (args) => html`\n \n `,\n};\n\nexport const Default: Story = { ...Template };\n\nconst errorFeedback: IInputFeedbackProp = {\n level: 'error',\n message: 'Value is required.',\n};\n\nexport const WithErrorFeedback: Story = {\n // prettier-ignore\n render: (args) => html`\n \n \n `,\n};\n\nexport const WithCustomIconSlot: Story = {\n // prettier-ignore\n render: (args) => html`\n\n \n\n `,\n args: {\n placeholder: 'Enter text here...',\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n // Create a unique shadow host for text-input component\n if (!customElements.get('text-input-shadow-host')) {\n const TextInputShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-text-input',\n propsMapper: (v: TextInputArgs, el: HTMLElement) => {\n const textInputEl = el as unknown as {\n autoCapitalize: string;\n autoComplete: string;\n autoCorrect: string;\n bordered: boolean;\n clearAriaLabel: string;\n customClass: string;\n disabled: boolean;\n enterkeyhint: string;\n feedback: IInputFeedbackProp;\n includeClear: boolean;\n includeSearch: boolean;\n inputId: string;\n inputTabIndex: number;\n label: string;\n maxLength: number;\n minLength: number;\n name: string;\n pattern: string;\n placeholder: string;\n readOnly: boolean;\n required: boolean;\n size: string;\n type: string;\n value: string;\n };\n textInputEl.autoCapitalize = v['auto-capitalize'] || '';\n textInputEl.autoComplete = v['auto-complete'] || '';\n textInputEl.autoCorrect = v['auto-correct'] || '';\n textInputEl.bordered = Boolean(v.bordered);\n textInputEl.clearAriaLabel =\n v['clear-aria-label'] || '' || 'Clear text';\n textInputEl.customClass = v['custom-class'] || '';\n textInputEl.disabled = Boolean(v.disabled);\n textInputEl.enterkeyhint = v.enterkeyhint || '';\n textInputEl.includeClear = Boolean(v['include-clear']);\n textInputEl.includeSearch = Boolean(v['include-search']);\n textInputEl.inputId = v['input-id'] || '';\n textInputEl.inputTabIndex = v['input-tab-index'] || 0;\n textInputEl.label = v.label || '';\n textInputEl.maxLength = v['max-length'];\n textInputEl.minLength = v['min-length'];\n textInputEl.name = v.name || '';\n textInputEl.pattern = v.pattern || '';\n textInputEl.placeholder = v.placeholder || '';\n textInputEl.readOnly = Boolean(v['read-only']);\n textInputEl.required = Boolean(v.required);\n textInputEl.size = v.size || '';\n textInputEl.type = v.type || '';\n textInputEl.value = v.value;\n },\n });\n customElements.define('text-input-shadow-host', TextInputShadowHost);\n }\n\n return html``;\n },\n};\nexport const Migration: Story = {\n parameters: {\n docs: {\n description: {\n story: `\n#### Breaking Changes\n\n - In 1.0 input state was maintained by the component. 2.0 components encourage users to follow a controlled\n input model. See the Form Inputs [documentation]([Angular](?path=/docs/documentation-form-inputs--docs) for\n additional info and examples.\n - Size values have changed from verbose names (\\`small\\`, \\`medium\\`, \\`large\\`) to abbreviations (\\`sm\\`, \\`md\\`, \\`lg\\`).\n\n#### Prop Mapping\n\n| 1.0 Prop | 2.0 Prop | Notes |\n|------------------------------|---------------------|-------------------------------------------------------------|\n| aria-label | aria-label | |\n| autocapitalize | auto-capitalize | |\n| autocorrect | auto-correct | |\n| autocomplete | autocomplete | |\n| auto-focus-input | autofocus | |\n| clearable | include-clear | |\n| disabled | disabled | |\n| enter-key-hint | enterkeyhint | |\n| error-text | feedback.message | Use \\`feedback\\` level |\n| helper-text | | Not carried over |\n| include-error-icon | | Not carried over |\n| include-search-icon | include-search | |\n| include-password-text-toggle | | Not carried over |\n| inputmode | inputmode | |\n| label | label | |\n| max-length | max-length | |\n| pattern | pattern | |\n| placeholder | placeholder | |\n| read-only | read-only | |\n| required | required | |\n| size | size | \\`small\\` → \\`sm\\`, \\`medium\\` → \\`md\\`, \\`large\\` → \\`lg\\` |\n| spellcheck | spellcheck | |\n| text-align | | Not carried over, use CSS instead |\n| type | type | |\n| valid-text | feedback.message | Use \\`feedback\\` level |\n| value | value | |\n\n#### Event Mapping\n\n| 1.0 Event | 2.0 Event | Notes |\n|-------------|-------------|------------------|\n| valueChange | inputChange | |\n `,\n },\n },\n // To hide the actual story rendering and only show docs:\n controls: { disable: true },\n canvas: { disable: true },\n },\n // Simple render function or leave it empty\n render: () => html`
    `,\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-textarea.json b/mcp/versions/1.5.0/component-docs/modus-wc-textarea.json new file mode 100644 index 000000000..4ba73b038 --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-textarea.json @@ -0,0 +1,215 @@ +{ + "description": "A customizable textarea component.", + "properties": [ + { + "name": "autoCorrect", + "type": "'on' | 'off'", + "description": "Controls automatic correction in inputted text. Support by browser varies.", + "default": null, + "mutable": false, + "end_line": 29 + }, + { + "name": "bordered", + "type": "boolean", + "description": "Indicates that the input should have a border.", + "default": "true", + "mutable": false, + "end_line": 32 + }, + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the textarea (supports DaisyUI).", + "default": "''", + "mutable": false, + "end_line": 35 + }, + { + "name": "disabled", + "type": "boolean", + "description": "The disabled state of the textarea.", + "default": "false", + "mutable": false, + "end_line": 38 + }, + { + "name": "enterkeyhint", + "type": "| 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send'", + "description": "A hint to the browser for which enter key to display.", + "default": null, + "mutable": false, + "end_line": 48 + }, + { + "name": "feedback", + "type": "IInputFeedbackProp", + "description": "Feedback to render below the input.", + "default": null, + "mutable": false, + "end_line": 51 + }, + { + "name": "inputId", + "type": "string", + "description": "The ID of the input element.", + "default": null, + "mutable": false, + "end_line": 54 + }, + { + "name": "inputTabIndex", + "type": "number", + "description": "The tabindex of the input.", + "default": null, + "mutable": false, + "end_line": 57 + }, + { + "name": "label", + "type": "string", + "description": "The text to display within the label.", + "default": null, + "mutable": false, + "end_line": 60 + }, + { + "name": "maxLength", + "type": "number", + "description": "The maximum number of characters allowed in the textarea.", + "default": null, + "mutable": false, + "end_line": 63 + }, + { + "name": "minLength", + "type": "number", + "description": "The minimum number of characters required in the textarea.", + "default": null, + "mutable": false, + "end_line": 66 + }, + { + "name": "name", + "type": "string", + "description": "Name of the form control. Submitted with the form as part of a name/value pair.", + "default": null, + "mutable": false, + "end_line": 69 + }, + { + "name": "placeholder", + "type": "string", + "description": "The placeholder text for the textarea.", + "default": "''", + "mutable": false, + "end_line": 72 + }, + { + "name": "readonly", + "type": "boolean", + "description": "The readonly state of the textarea.", + "default": "false", + "mutable": false, + "end_line": 75 + }, + { + "name": "required", + "type": "boolean", + "description": "A value is required for the form to be submittable.", + "default": "false", + "mutable": false, + "end_line": 78 + }, + { + "name": "rows", + "type": "number", + "description": "The number of visible text lines for the textarea.", + "default": null, + "mutable": false, + "end_line": 81 + }, + { + "name": "size", + "type": "ModusSize", + "description": "The size of the input.", + "default": "'md'", + "mutable": false, + "end_line": 84 + }, + { + "name": "value", + "type": "string", + "description": "The value of the textarea.", + "default": "''", + "mutable": true, + "end_line": 87 + } + ], + "events": [ + { + "name": "inputBlur", + "detail": "FocusEvent", + "description": "Emitted when the input loses focus.", + "end_line": 90 + }, + { + "name": "inputChange", + "detail": "InputEvent", + "description": "Emitted when the input value changes.", + "end_line": 93 + }, + { + "name": "inputFocus", + "detail": "FocusEvent", + "description": "Emitted when the input gains focus.", + "end_line": 96 + } + ], + "methods": [], + "slots": [], + "examples": { + "basic": "", + "variations": [], + "args": { + "bordered": "true", + "custom-class": "''", + "disabled": "false", + "label": "'Label'", + "readonly": "false", + "required": "false", + "size": "'md'", + "spellcheck": "false", + "value": "''" + }, + "argTypes": {}, + "usage": [], + "events": [ + "inputBlur", + "inputChange", + "inputFocus" + ] + }, + "tag": "modus-wc-textarea", + "storyExample": { + "template": "", + "args": { + "bordered": "true", + "custom-class": "''", + "disabled": "false", + "label": "'Label'", + "readonly": "false", + "required": "false", + "size": "'md'", + "spellcheck": "false", + "value": "''" + }, + "argTypes": {}, + "events": [ + "inputBlur", + "inputChange", + "inputFocus" + ], + "fullContent": "import { withActions } from '@storybook/addon-actions/decorator';\nimport { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\nimport { IInputFeedbackProp, ModusSize } from '../types';\n\ninterface TextAreaArgs {\n 'auto-correct': 'on' | 'off';\n bordered?: boolean;\n 'custom-class'?: string;\n disabled?: boolean;\n enterkeyhint?:\n | 'enter'\n | 'done'\n | 'go'\n | 'next'\n | 'previous'\n | 'search'\n | 'send';\n feedback?: IInputFeedbackProp;\n 'input-aria-invalid'?: 'grammar' | 'spelling' | 'true' | 'false';\n 'input-id'?: string;\n 'input-tab-index'?: number;\n label?: string;\n 'max-length'?: number;\n 'min-length'?: number;\n name?: string;\n placeholder?: string;\n readonly?: boolean;\n required?: boolean;\n rows?: number;\n size?: ModusSize;\n spellcheck?: boolean;\n value: string;\n}\n\nconst meta: Meta = {\n title: 'Components/Forms/Textarea',\n component: 'modus-wc-textarea',\n args: {\n bordered: true,\n 'custom-class': '',\n disabled: false,\n label: 'Label',\n readonly: false,\n required: false,\n size: 'md',\n spellcheck: false,\n value: '',\n },\n argTypes: {\n 'auto-correct': {\n options: ['on', 'off'],\n },\n enterkeyhint: {\n options: ['enter', 'done', 'go', 'next', 'previous', 'search', 'send'],\n },\n feedback: {\n description: 'Feedback prop for input components',\n table: {\n type: {\n detail: `\n Interface: IInputFeedbackProp\n Properties:\n - level ('error' | 'info' | 'success' | 'warning'): The feedback level\n - message (string, optional): The feedback message\n `,\n },\n },\n },\n 'input-aria-invalid': {\n control: {\n type: 'select',\n },\n options: ['grammar', 'spelling', 'true', 'false'],\n },\n size: {\n control: { type: 'select' },\n options: ['sm', 'md', 'lg'],\n },\n spellcheck: {\n description:\n 'Whether the element may be checked for spelling errors. A hint for the browser, not a guarantee.',\n table: {\n category: 'attributes',\n defaultValue: { summary: 'false' },\n },\n },\n },\n decorators: [withActions],\n parameters: {\n actions: {\n handles: ['inputBlur', 'inputChange', 'inputFocus'],\n },\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nexport const Default: Story = {\n render: (args) => {\n return html`\n \n `;\n },\n};\n\nconst errorFeedback: IInputFeedbackProp = {\n level: 'error',\n message: 'Value is required.',\n};\n\nexport const WithErrorFeedback: Story = {\n render: (args) => html`\n \n \n `,\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n // Create a unique shadow host for textarea component\n if (!customElements.get('textarea-shadow-host')) {\n const TextareaShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-textarea',\n propsMapper: (v: TextAreaArgs, el: HTMLElement) => {\n const textareaEl = el as unknown as {\n autoCorrect: string;\n bordered: boolean;\n customClass: string;\n disabled: boolean;\n enterkeyhint: string;\n feedback: IInputFeedbackProp;\n inputId: string;\n inputTabIndex: number;\n label: string;\n maxLength: number;\n minLength: number;\n name: string;\n placeholder: string;\n readonly: boolean;\n required: boolean;\n rows: number;\n size: string;\n };\n textareaEl.autoCorrect = v['auto-correct'];\n textareaEl.bordered = Boolean(v.bordered);\n textareaEl.customClass = v['custom-class'] || '';\n textareaEl.disabled = Boolean(v.disabled);\n textareaEl.enterkeyhint = v.enterkeyhint || '';\n\n textareaEl.inputId = v['input-id'] || '';\n textareaEl.inputTabIndex = v['input-tab-index'] || 0;\n textareaEl.label = v.label || '';\n textareaEl.maxLength = v['max-length'] || 100;\n textareaEl.minLength = v['min-length'] || 0;\n textareaEl.name = v.name || '';\n textareaEl.placeholder = v.placeholder || '';\n textareaEl.readonly = Boolean(v.readonly);\n textareaEl.required = Boolean(v.required);\n textareaEl.rows = typeof v.rows === 'number' ? v.rows : 2;\n textareaEl.size = v.size || '';\n },\n });\n customElements.define('textarea-shadow-host', TextareaShadowHost);\n }\n\n return html``;\n },\n};\nexport const Migration: Story = {\n parameters: {\n docs: {\n description: {\n story: `\n#### Breaking Changes\n\n - In 1.0 input state was maintained by the component. 2.0 components encourage users to follow a controlled\n input model. See the Form Inputs [documentation](/docs/documentation-form-inputs--docs) for\n additional info and examples.\n - Size values have changed from verbose names (\\`medium\\`, \\`large\\`) to abbreviations (\\`sm\\`, \\`md\\`, \\`lg\\`).\n\n#### Prop Mapping\n\n| 1.0 Prop | 2.0 Prop | Notes |\n|------------------------------|---------------------|-------------------------------------------------------------|\n| aria-label | aria-label | |\n| autocorrect | auto-correct | |\n| auto-focus-input | | Not carried over |\n| clearable | | Not carried over |\n| disabled | disabled | |\n| enterkeyhint | enterkeyhint | |\n| error-text | feedback.message | Use \\`feedback\\` level |\n| helper-text | | Not carried over |\n| label | label | |\n| max-length | max-length | |\n| min-length | min-length | |\n| placeholder | placeholder | |\n| read-only | readonly | |\n| rows | rows | |\n| required | required | |\n| size | size | \\`medium\\` → \\`md\\`, \\`large\\` → \\`lg\\` |\n| spellcheck | spellcheck | |\n| text-align | | Not carried over, use CSS instead |\n| valid-text | feedback.message | Use \\`feedback\\` level |\n| value | value | |\n\n#### Event Mapping\n\n| 1.0 Event | 2.0 Event | Notes |\n|-------------|-------------|------------------|\n| valueChange | inputChange | |\n `,\n },\n },\n controls: { disable: true },\n canvas: { disable: true },\n },\n render: () => html`
    `,\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-theme-switcher.json b/mcp/versions/1.5.0/component-docs/modus-wc-theme-switcher.json new file mode 100644 index 000000000..1cf6f34cd --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-theme-switcher.json @@ -0,0 +1,49 @@ +{ + "description": "A theme switcher component used to toggle the application theme and/or mode. Allows consumers to set the initial theme (Modus Classic, Modus Modern, etc.) and end-users to toggle modes (Light, Dark).", + "properties": [ + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the theme switcher element.", + "default": "''", + "mutable": false, + "end_line": 34 + } + ], + "events": [ + { + "name": "themeChange", + "detail": "IThemeConfig", + "description": "An event that fires when the theme is changed.", + "end_line": 37 + } + ], + "methods": [], + "slots": [], + "examples": { + "basic": "\n \n ", + "variations": [], + "args": { + "custom-class": "undefined", + "initial-theme": "undefined" + }, + "argTypes": {}, + "usage": [], + "events": [ + "themeChange" + ] + }, + "tag": "modus-wc-theme-switcher", + "storyExample": { + "template": "\n \n ", + "args": { + "custom-class": "undefined", + "initial-theme": "undefined" + }, + "argTypes": {}, + "events": [ + "themeChange" + ], + "fullContent": "import { withActions } from '@storybook/addon-actions/decorator';\nimport { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { IThemeConfig } from '../../providers/theme/theme.types';\n\ninterface ThemeSwitcherArgs {\n 'custom-class'?: string;\n 'initial-theme'?: Partial;\n}\n\nconst meta: Meta = {\n title: 'Components/ThemeSwitcher',\n component: 'modus-wc-theme-switcher',\n args: {\n 'custom-class': undefined,\n 'initial-theme': undefined,\n },\n decorators: [withActions],\n parameters: {\n actions: {\n handles: ['themeChange'],\n },\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n// When no data-theme is set on the root element, default to the design system's base theme \"modus-modern-light\"\nconst getCurrentTheme = () => {\n const dataTheme =\n document.documentElement.getAttribute('data-theme') || 'modus-modern-light';\n const mode = dataTheme.endsWith('-dark') ? 'dark' : 'light';\n const theme = dataTheme.replace(`-${mode}`, '');\n return { theme, mode };\n};\n\nconst Template: Story = {\n // prettier-ignore\n render: (args) => {\n const currentTheme = getCurrentTheme();\n return html`\n \n \n \n `;\n },\n};\n\nexport const Default: Story = { ...Template };\n\n// New story for testing theme configurations\nconst ThemeTestTemplate: Story = {\n render: () => {\n const currentTheme = getCurrentTheme();\n\n return html`\n \n
    \n

    \n Theme Configuration Test\n

    \n \n This story matches the global Storybook theme\n

    \n\n \n Primary Button\n Secondary Button\n Tertiary Button\n Danger Button\n
    \n\n \n Badge\n Badge 2\n \n \n \n \n \n
    \n `;\n },\n};\n\nexport const ThemeTest: Story = {\n ...ThemeTestTemplate,\n parameters: {\n docs: {\n description: {\n story:\n 'This example syncs with the active Storybook theme. Toggle between light and dark modes using the theme switcher on the default story to see the components adapt accordingly.',\n },\n },\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n if (!customElements.get('theme-switcher-shadow-host')) {\n class ThemeSwitcherShadowHost extends HTMLElement {\n private sr: ShadowRoot;\n private _props?: ThemeSwitcherArgs;\n private themeObserver: MutationObserver | null = null;\n\n constructor() {\n super();\n this.sr = this.attachShadow({ mode: 'open' });\n }\n\n connectedCallback() {\n if (this.sr.childElementCount) return;\n\n // Sync data-theme so CSS variables work inside shadow root\n const wrapper = document.createElement('div');\n wrapper.style.display = 'contents';\n const syncTheme = () => {\n const theme = document.documentElement.getAttribute('data-theme');\n if (theme) wrapper.setAttribute('data-theme', theme);\n };\n syncTheme();\n this.themeObserver = new MutationObserver(syncTheme);\n this.themeObserver.observe(document.documentElement, {\n attributes: true,\n attributeFilter: ['data-theme'],\n });\n\n // Create provider + switcher together here (not in constructor) so\n // provider connects first and initializes themeStore before the\n // switcher reads themeStore.state.mode for its isDarkMode field\n const provider = document.createElement(\n 'modus-wc-theme-provider'\n ) as HTMLElement & {\n initialTheme: { theme: string; mode: string };\n };\n provider.initialTheme = getCurrentTheme();\n\n const switcher = document.createElement(\n 'modus-wc-theme-switcher'\n ) as HTMLElement & { customClass: string };\n switcher.setAttribute('aria-label', 'Theme toggle');\n\n provider.appendChild(switcher);\n wrapper.appendChild(provider);\n this.sr.appendChild(wrapper);\n\n if (this._props) this.applyProps();\n }\n\n disconnectedCallback() {\n this.themeObserver?.disconnect();\n this.themeObserver = null;\n }\n\n set props(v: ThemeSwitcherArgs) {\n this._props = v;\n this.applyProps();\n }\n\n get props(): ThemeSwitcherArgs | undefined {\n return this._props;\n }\n\n private applyProps() {\n const switcher = this.sr.querySelector('modus-wc-theme-switcher') as\n | (HTMLElement & { customClass: string })\n | null;\n if (!switcher || !this._props) return;\n switcher.customClass = this._props['custom-class'] || '';\n }\n }\n customElements.define(\n 'theme-switcher-shadow-host',\n ThemeSwitcherShadowHost\n );\n }\n\n return html``;\n },\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-time-input.json b/mcp/versions/1.5.0/component-docs/modus-wc-time-input.json new file mode 100644 index 000000000..7dfd7d582 --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-time-input.json @@ -0,0 +1,211 @@ +{ + "description": "A customizable input component used to create time inputs.", + "properties": [ + { + "name": "autoComplete", + "type": "'on' | 'off'", + "description": "Hint for form autofill feature.", + "default": null, + "mutable": false, + "end_line": 30 + }, + { + "name": "bordered", + "type": "boolean", + "description": "Indicates that the input should have a border.", + "default": "true", + "mutable": false, + "end_line": 33 + }, + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the input.", + "default": "''", + "mutable": false, + "end_line": 36 + }, + { + "name": "datalistOptions", + "type": "string[]", + "description": "The options to display in the time input dropdown. Options must be in `HH:mm` or `HH:mm:ss` format.", + "default": "[]", + "mutable": false, + "end_line": 39 + }, + { + "name": "disabled", + "type": "boolean", + "description": "Whether the form control is disabled.", + "default": "false", + "mutable": false, + "end_line": 42 + }, + { + "name": "feedback", + "type": "IInputFeedbackProp", + "description": "Feedback to render below the input.", + "default": null, + "mutable": false, + "end_line": 45 + }, + { + "name": "inputId", + "type": "string", + "description": "The ID of the input element.", + "default": null, + "mutable": false, + "end_line": 48 + }, + { + "name": "inputTabIndex", + "type": "number", + "description": "Determine the control's relative ordering for sequential focus navigation (typically with the Tab key).", + "default": null, + "mutable": false, + "end_line": 51 + }, + { + "name": "datalistId", + "type": "string", + "description": "ID of a `` element that contains pre-defined time options. The value must be the ID of a `` element in the same document.", + "default": null, + "mutable": true, + "end_line": 57 + }, + { + "name": "label", + "type": "string", + "description": "The text to display within the label.", + "default": null, + "mutable": false, + "end_line": 60 + }, + { + "name": "max", + "type": "string", + "description": "Maximum value. Format: `HH:mm`, `HH:mm:ss`.", + "default": null, + "mutable": false, + "end_line": 63 + }, + { + "name": "min", + "type": "string", + "description": "Minimum value. Format: `HH:mm`, `HH:mm:ss.`", + "default": null, + "mutable": false, + "end_line": 66 + }, + { + "name": "name", + "type": "string", + "description": "Name of the form control. Submitted with the form as part of a name/value pair.", + "default": null, + "mutable": false, + "end_line": 69 + }, + { + "name": "readOnly", + "type": "boolean", + "description": "Whether the value is editable.", + "default": "false", + "mutable": false, + "end_line": 72 + }, + { + "name": "required", + "type": "boolean", + "description": "A value is required for the form to be submittable.", + "default": "false", + "mutable": false, + "end_line": 75 + }, + { + "name": "showSeconds", + "type": "boolean", + "description": "Displays the time input format as `HH:mm:ss` if `true`. Internally sets the `step` to 1 second. If a `step` value is provided, it will override this attribute.", + "default": "false", + "mutable": false, + "end_line": 82 + }, + { + "name": "size", + "type": "ModusSize", + "description": "The size of the input.", + "default": "'md'", + "mutable": false, + "end_line": 85 + }, + { + "name": "step", + "type": "number", + "description": "Specifies the granularity that the `value` must adhere to. Value of step given in seconds. Default value is 60 seconds. Overrides the `seconds` attribute if both are provided.", + "default": null, + "mutable": false, + "end_line": 92 + }, + { + "name": "value", + "type": "string", + "description": "The value of the time input. Always in 24-hour format that includes leading zeros: `HH:mm` or `HH:mm:ss`, regardless of input format which is likely to be selected based on user's locale (or by the user agent). If time includes seconds the format is always `HH:mm:ss`.", + "default": "''", + "mutable": true, + "end_line": 101 + } + ], + "events": [ + { + "name": "inputBlur", + "detail": "FocusEvent", + "description": "Event emitted when the input loses focus.", + "end_line": 104 + }, + { + "name": "inputChange", + "detail": "Event", + "description": "Event emitted when the input value changes.", + "end_line": 107 + }, + { + "name": "inputFocus", + "detail": "FocusEvent", + "description": "Event emitted when the input gains focus.", + "end_line": 110 + } + ], + "methods": [], + "slots": [], + "examples": { + "basic": "\n\n \n \n \n", + "variations": [], + "args": { + "disabled": "false", + "label": "'Label'", + "size": "'md'" + }, + "argTypes": {}, + "usage": [], + "events": [ + "inputBlur", + "inputChange", + "inputFocus" + ] + }, + "tag": "modus-wc-time-input", + "storyExample": { + "template": "\n\n \n \n \n", + "args": { + "disabled": "false", + "label": "'Label'", + "size": "'md'" + }, + "argTypes": {}, + "events": [ + "inputBlur", + "inputChange", + "inputFocus" + ], + "fullContent": "import { withActions } from '@storybook/addon-actions/decorator';\nimport { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\nimport { IInputFeedbackProp, ModusSize } from '../types';\n\n// const timeOptions = ['08:00', '12:00', '17:00'];\n\ninterface TimeInputArgs {\n 'auto-complete'?: 'on' | 'off';\n bordered?: boolean;\n 'custom-class'?: string;\n 'datalist-id'?: string;\n 'datalist-options'?: string[];\n disabled?: boolean;\n feedback?: IInputFeedbackProp;\n 'input-id'?: string;\n 'input-tab-index'?: number;\n label?: string;\n max?: string;\n min?: string;\n name?: string;\n 'read-only'?: boolean;\n required?: boolean;\n 'show-seconds'?: boolean;\n size?: ModusSize;\n step?: number;\n value: string;\n}\n\nconst meta: Meta = {\n title: 'Components/Forms/Time Input',\n component: 'modus-wc-time-input',\n args: {\n disabled: false,\n label: 'Label',\n size: 'md',\n },\n argTypes: {\n 'auto-complete': {\n control: { type: 'select' },\n options: ['on', 'off'],\n },\n feedback: {\n description: 'Feedback prop for input components',\n table: {\n type: {\n detail: `\n Interface: IInputFeedbackProp\n Properties:\n - level ('error' | 'info' | 'success' | 'warning'): The feedback level\n - message (string, optional): The feedback message\n `,\n },\n },\n },\n size: {\n control: { type: 'select' },\n options: ['sm', 'md', 'lg'],\n },\n },\n decorators: [withActions],\n parameters: {\n actions: {\n handles: ['inputBlur', 'inputChange', 'inputFocus'],\n },\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nconst Template: Story = {\n render: (args) => html`\n \n `,\n};\n\nexport const Default: Story = { ...Template };\n\nexport const WithSeconds: Story = {\n ...Template,\n args: {\n 'show-seconds': true,\n },\n};\n\nexport const WithDatalist: Story = {\n render: () => {\n // prettier-ignore\n return html`\n\n\n \n \n \n\n `;\n },\n};\n\nexport const WithDatalistOptions: Story = {\n render: () => {\n // prettier-ignore\n return html`\n\n\n `;\n },\n};\n\nconst errorFeedback: IInputFeedbackProp = {\n level: 'error',\n message: 'Value is required.',\n};\n\nexport const WithErrorFeedback: Story = {\n ...Template,\n args: { feedback: errorFeedback, required: true },\n parameters: {\n docs: {\n source: {\n transform: (src) => `${src}\n`,\n },\n },\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n // Create a unique shadow host for time-input component\n if (!customElements.get('time-input-shadow-host')) {\n const TimeInputShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-time-input',\n propsMapper: (v: TimeInputArgs, el: HTMLElement) => {\n const timeInputEl = el as unknown as {\n autoComplete: string;\n bordered: boolean;\n customClass: string;\n datalistId: string;\n datalistOptions: string[];\n disabled: boolean;\n feedback: IInputFeedbackProp;\n inputId: string;\n inputTabIndex: number;\n label: string;\n max: string;\n min: string;\n name: string;\n readOnly: boolean;\n required: boolean;\n showSeconds: boolean;\n size: string;\n step: number;\n value: string;\n };\n timeInputEl.autoComplete = v['auto-complete'] ?? '';\n timeInputEl.bordered = v['bordered'] ?? true;\n timeInputEl.customClass = v['custom-class'] || '';\n timeInputEl.datalistId = v['datalist-id'] ?? '';\n if (v['datalist-options']) {\n timeInputEl.datalistOptions = v['datalist-options']; // Conditional assignment only if provided\n }\n timeInputEl.disabled = Boolean(v.disabled);\n\n timeInputEl.inputId = v['input-id'] ?? '';\n timeInputEl.inputTabIndex = v['input-tab-index'] ?? 0;\n timeInputEl.label = v.label ?? '';\n timeInputEl.max = v.max ?? '';\n timeInputEl.min = v.min ?? '';\n timeInputEl.name = v.name ?? '';\n timeInputEl.readOnly = Boolean(v['read-only']);\n timeInputEl.required = Boolean(v.required);\n timeInputEl.showSeconds = Boolean(v['show-seconds']);\n timeInputEl.size = v.size ?? 'md';\n // Only set step if explicitly provided, otherwise let component calculate from showSeconds\n if (v.step !== undefined) {\n timeInputEl.step = v.step;\n }\n timeInputEl.value = v.value ?? '';\n },\n });\n customElements.define('time-input-shadow-host', TimeInputShadowHost);\n }\n\n return html``;\n },\n};\nexport const Migration: Story = {\n parameters: {\n docs: {\n description: {\n story: `\n#### Breaking Changes\n\n - In 1.0 input state was maintained by the component. 2.0 components encourage users to follow a controlled\n input model. See the Form Inputs [documentation](/docs/documentation-form-inputs--docs) for\n additional info and examples.\n - Size values have changed from verbose names (\\`medium\\`, \\`large\\`) to abbreviations (\\`sm\\`, \\`md\\`, \\`lg\\`).\n\n#### Prop Mapping\n\n| 1.0 Prop | 2.0 Prop | Notes |\n|-------------------------|---------------------|-----------------------------------------|\n| allowed-chars-regex | | Not carried over |\n| ampm | | Not carried over |\n| aria-label | aria-label | |\n| auto-focus-input | autofocus | |\n| auto-format | | Not carried over |\n| disable-validation | | Not carried over |\n| disabled | disabled | |\n| error-text | feedback.message | Use \\`feedback\\` level |\n| helper-text | | Not carried over |\n| label | label | |\n| max | max | |\n| min | min | |\n| placeholder | | Not carried over |\n| read-only | read-only | |\n| required | required | |\n| size | size | \\`medium\\` → \\`md\\`, \\`large\\` → \\`lg\\` |\n| valid-text | feedback.message | Use \\`feedback\\` level |\n| value | value | |\n\n#### Event Mapping\n\n| 1.0 Event | 2.0 Event | Notes |\n|----------------|-------------|------------------------------------------------------|\n| timeInputBlur | inputBlur | |\n| valueChange | inputChange | |\n `,\n },\n },\n // To hide the actual story rendering and only show docs:\n controls: { disable: true },\n canvas: { disable: true },\n },\n // Simple render function or leave it empty\n render: () => html`
    `,\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-toast.json b/mcp/versions/1.5.0/component-docs/modus-wc-toast.json new file mode 100644 index 000000000..98449348b --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-toast.json @@ -0,0 +1,56 @@ +{ + "description": "A customizable toast component used to stack elements, positioned on the corner of a page. The component supports a `` for injecting additional custom content inside the toast.", + "properties": [ + { + "name": "customClass", + "type": "string", + "description": "Additional classes for custom styling.", + "default": "''", + "mutable": false, + "end_line": 33 + }, + { + "name": "delay", + "type": "number", + "description": "Time taken to dismiss the toast in milliseconds", + "default": null, + "mutable": false, + "end_line": 36 + }, + { + "name": "position", + "type": "ToastPosition", + "description": "The position of the toast in the parent container.", + "default": "'top-end'", + "mutable": false, + "end_line": 39 + } + ], + "events": [], + "methods": [], + "slots": [ + { + "name": "default", + "description": "Slot for default content" + } + ], + "examples": { + "basic": "
    \n \n
    ", + "variations": [], + "args": { + "position": "'top-end'" + }, + "argTypes": {}, + "usage": [] + }, + "tag": "modus-wc-toast", + "storyExample": { + "template": "
    \n \n
    ", + "args": { + "position": "'top-end'" + }, + "argTypes": {}, + "events": [], + "fullContent": "import { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { ToastPosition } from './modus-wc-toast';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\n\ninterface ToastArgs {\n 'custom-class'?: string;\n delay?: number;\n position?: ToastPosition;\n}\n\nconst meta: Meta = {\n title: 'Components/Toast',\n component: 'modus-wc-toast',\n args: {\n position: 'top-end',\n },\n argTypes: {\n position: {\n control: { type: 'select' },\n options: [\n 'top-start',\n 'top-center',\n 'top-end',\n 'middle-start',\n 'middle-center',\n 'middle-end',\n 'bottom-start',\n 'bottom-center',\n 'bottom-end',\n ],\n },\n },\n parameters: {\n layout: 'padded',\n viewport: 'responsive',\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nconst Template: Story = {\n // prettier-ignore\n render: (args) => html`\n
    \n \n \n \n
    \n `,\n};\n\nexport const Default: Story = { ...Template };\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n if (!customElements.get('toast-shadow-host')) {\n const ToastShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-toast',\n propsMapper: (v: ToastArgs, el: HTMLElement) => {\n const toastEl = el as unknown as {\n customClass: string;\n delay: number;\n position: string;\n };\n toastEl.customClass = v['custom-class'] || '';\n toastEl.delay = v.delay ?? 0;\n toastEl.position = v.position ?? 'top-end';\n if (!el.hasChildNodes()) {\n el.innerHTML =\n '';\n }\n },\n });\n customElements.define('toast-shadow-host', ToastShadowHost);\n }\n\n return html`\n
    \n \n
    \n `;\n },\n};\nexport const Migration: Story = {\n parameters: {\n docs: {\n description: {\n story: `\n#### Breaking Changes\n\n - In 1.0 toast included built-in dismiss functionality with delay timer and dismiss button. 2.0 components focus on positioning only.\n - In 1.0 toast included built-in icons. 2.0 components rely on slotted content for visual elements.\n - 2.0 toast components no longer support built-in types/variants, use slotted \\`modus-wc-alert\\` components instead.\n\n#### Prop Mapping\n\n| 1.0 Prop | 2.0 Prop | Notes |\n|-----------------|-------------|--------------------------------------------|\n| aria-label | aria-label | |\n| delay | | Not carried over |\n| dismissible | | Not carried over |\n| retain-element | | Not carried over |\n| role | | Not carried over |\n| show-icon | | Not carried over |\n| type | | Not carried over, use slotted content |\n\n#### Event Mapping\n\n| 1.0 Event | 2.0 Event | Notes |\n|---------------|-----------|------------------|\n| dismissClick | | Not carried over |\n `,\n },\n },\n // To hide the actual story rendering and only show docs:\n controls: { disable: true },\n canvas: { disable: true },\n },\n // Simple render function or leave it empty\n render: () => html`
    `,\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-toolbar.json b/mcp/versions/1.5.0/component-docs/modus-wc-toolbar.json new file mode 100644 index 000000000..e5491dabd --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-toolbar.json @@ -0,0 +1,44 @@ +{ + "description": "A customizable toolbar component used to organize content across the entire page. This component provides 'start', 'center', and 'end' `` elements for inserting custom HTML.", + "properties": [ + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the outer div.", + "default": "''", + "mutable": false, + "end_line": 21 + } + ], + "events": [], + "methods": [], + "slots": [ + { + "name": "start", + "description": "Slot for start content" + }, + { + "name": "center", + "description": "Slot for center content" + }, + { + "name": "end", + "description": "Slot for end content" + } + ], + "examples": { + "basic": "\n
    Start
    \n
    Center
    \n
    End
    \n
    ", + "variations": [], + "args": {}, + "argTypes": {}, + "usage": [] + }, + "tag": "modus-wc-toolbar", + "storyExample": { + "template": "\n
    Start
    \n
    Center
    \n
    End
    \n
    ", + "args": {}, + "argTypes": {}, + "events": [], + "fullContent": "import { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\n\ninterface ToolbarArgs {\n 'custom-class'?: string;\n}\n\nconst meta: Meta = {\n title: 'Components/Toolbar',\n component: 'modus-wc-toolbar',\n parameters: {\n layout: 'padded',\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nconst Template: Story = {\n render: (args) => {\n // prettier-ignore\n return html`\n\n
    Start
    \n
    Center
    \n
    End
    \n
    \n `;\n },\n};\n\nexport const Default: Story = { ...Template };\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n if (!customElements.get('toolbar-shadow-host')) {\n const ToolbarShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-toolbar',\n propsMapper: (v: ToolbarArgs, el: HTMLElement) => {\n const toolbarEl = el as unknown as { customClass: string };\n toolbarEl.customClass = v['custom-class'] || '';\n if (!el.hasChildNodes()) {\n el.innerHTML =\n '
    Start
    Center
    End
    ';\n }\n },\n });\n customElements.define('toolbar-shadow-host', ToolbarShadowHost);\n }\n\n return html``;\n },\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-tooltip.json b/mcp/versions/1.5.0/component-docs/modus-wc-tooltip.json new file mode 100644 index 000000000..db96174d7 --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-tooltip.json @@ -0,0 +1,94 @@ +{ + "description": "A customizable tooltip component used to create tooltips with different content. The tooltip can be dismissed by pressing the Escape key when hovering over it. When forceOpen is enabled, the tooltip will remain open and can only be closed by setting forceOpen to false.", + "properties": [ + { + "name": "content", + "type": "string", + "description": "The text content of the tooltip.", + "default": "''", + "mutable": false, + "end_line": 37 + }, + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the inner div.", + "default": "''", + "mutable": false, + "end_line": 40 + }, + { + "name": "disabled", + "type": "boolean", + "description": "Disables displaying the tooltip on hover", + "default": "false", + "mutable": false, + "end_line": 43 + }, + { + "name": "forceOpen", + "type": "boolean", + "description": "Use this attribute to force the tooltip to remain open.", + "default": null, + "mutable": false, + "end_line": 46 + }, + { + "name": "tooltipId", + "type": "string", + "description": "The ID of the tooltip element, useful for setting the \"aria-describedby\" attribute of related elements.", + "default": null, + "mutable": false, + "end_line": 49 + }, + { + "name": "position", + "type": "'auto' | 'top' | 'right' | 'bottom' | 'left'", + "description": "The position that the tooltip will render in relation to the element.", + "default": "'auto'", + "mutable": false, + "end_line": 52 + } + ], + "events": [ + { + "name": "dismissEscape", + "detail": "void", + "description": "An event that fires when the tooltip is dismissed via Escape key", + "end_line": 206 + } + ], + "methods": [], + "slots": [ + { + "name": "default", + "description": "Slot for default content" + } + ], + "examples": { + "basic": "\n Hover\n ", + "variations": [], + "args": { + "content": "'Tooltip content'", + "position": "'auto'" + }, + "argTypes": {}, + "usage": [], + "events": [ + "dismissEscape" + ] + }, + "tag": "modus-wc-tooltip", + "storyExample": { + "template": "\n Hover\n ", + "args": { + "content": "'Tooltip content'", + "position": "'auto'" + }, + "argTypes": {}, + "events": [ + "dismissEscape" + ], + "fullContent": "import { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\n\ninterface TooltipArgs {\n content?: string;\n 'custom-class'?: string;\n disabled?: boolean;\n 'force-open'?: boolean;\n 'tooltip-id'?: string;\n position: 'auto' | 'top' | 'right' | 'bottom' | 'left';\n}\n\nconst meta: Meta = {\n title: 'Components/Tooltip',\n component: 'modus-wc-tooltip',\n args: {\n content: 'Tooltip content',\n position: 'auto',\n },\n argTypes: {\n position: {\n control: { type: 'select' },\n options: ['auto', 'top', 'right', 'left', 'bottom'],\n },\n },\n parameters: {\n docs: {\n description: {\n component: `\nA customizable tooltip component used to create tooltips with different content.\n\\nThe component supports a \\`\\` for injecting custom tooltip content.\n\n### Features\n- **Escape Key Dismissal**: Tooltips can be dismissed by pressing the Escape key\n- **Auto-positioning**: Automatically positions the tooltip to avoid viewport edges\n- **Customizable**: Supports custom CSS classes and positioning\n\n### Keyboard Interaction\n- Press **Escape** to dismiss the tooltip while it's visible\n- The tooltip will automatically re-enable on mouse enter\n `,\n },\n },\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nconst Template: Story = {\n parameters: {\n actions: {\n handles: ['dismissEscape'],\n },\n },\n render: (args) => {\n // prettier-ignore\n return html`\n \n Hover\n \n `;\n },\n};\n\nexport const Default: Story = { ...Template };\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n if (!customElements.get('tooltip-shadow-host')) {\n class TooltipShadowHost extends HTMLElement {\n private sr: ShadowRoot;\n private _props?: TooltipArgs;\n private tooltipEl?: HTMLElement & {\n content: string;\n customClass: string;\n disabled: boolean;\n forceOpen: boolean | undefined;\n tooltipId: string;\n position: string;\n };\n\n constructor() {\n super();\n this.sr = this.attachShadow({ mode: 'open' });\n }\n\n connectedCallback() {\n if (this.tooltipEl) return;\n this.renderContent();\n }\n\n set props(v: TooltipArgs) {\n this._props = v;\n if (this.tooltipEl) this.applyProps();\n }\n\n private renderContent() {\n this.sr.innerHTML = '';\n\n this.tooltipEl = document.createElement(\n 'modus-wc-tooltip'\n ) as typeof this.tooltipEl;\n\n const badge = document.createElement('modus-wc-badge');\n badge.textContent = 'Hover';\n this.tooltipEl!.appendChild(badge);\n this.sr.appendChild(this.tooltipEl!);\n\n // Apply props after Stencil hydrates the tooltip element\n void Promise.resolve().then(() => this.applyProps());\n }\n\n private applyProps() {\n const v = this._props;\n const tooltip = this.tooltipEl;\n if (!v || !tooltip) return;\n tooltip.content = v.content ?? 'Tooltip content';\n tooltip.customClass = v['custom-class'] ?? '';\n tooltip.disabled = Boolean(v.disabled);\n tooltip.forceOpen = v['force-open'] ?? false;\n tooltip.tooltipId = v['tooltip-id'] ?? '';\n tooltip.position = v.position ?? 'auto';\n }\n }\n customElements.define('tooltip-shadow-host', TooltipShadowHost);\n }\n\n return html``;\n },\n};\nexport const Migration: Story = {\n parameters: {\n docs: {\n description: {\n story: `\n#### Breaking Changes\n\n - In 1.0 tooltip positioning was handled by Popper.js. In 2.0, positioning is handled using CSS.\n - The \\`text\\` prop has been renamed to \\`content\\`.\n\n#### Prop Mapping\n\n| 1.0 Prop | 2.0 Prop | Notes |\n|-------------|-------------|------------------------------------------|\n| aria-label | aria-label | |\n| disabled | disabled | |\n| position | position | Added \\`auto\\` option as default value |\n| text | content | |\n `,\n },\n },\n controls: { disable: true },\n canvas: { disable: true },\n },\n render: () => html`
    `,\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-typography.json b/mcp/versions/1.5.0/component-docs/modus-wc-typography.json new file mode 100644 index 000000000..70fe78e32 --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-typography.json @@ -0,0 +1,78 @@ +{ + "description": "A customizable typography component used to render text with different sizes, hierarchy, and weights. Note: - When using heading elements (h1-h6), the default heading CSS styling can be accessed without modifying the default size (size=\"md\") and weight (weight=\"normal\") properties. Default styling can be overridden by providing your own custom values for the size or weight properties from the available options. - If both slot content and `label` are provided, only the slot content will be rendered - Use the `label` prop when you need to dynamically update the text.", + "properties": [ + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the typography element.", + "default": "''", + "mutable": false, + "end_line": 34 + }, + { + "name": "hierarchy", + "type": "TypographyHierarchy", + "description": "The hierarchy of the typography component.", + "default": "'p'", + "mutable": false, + "end_line": 37 + }, + { + "name": "label", + "type": "string", + "description": "The text label to display.", + "default": null, + "mutable": false, + "end_line": 40 + }, + { + "name": "size", + "type": "TypographySize", + "description": "The size of the font.", + "default": "'md'", + "mutable": false, + "end_line": 43 + }, + { + "name": "weight", + "type": "TypographyWeight", + "description": "The weight of the text.", + "default": "'normal'", + "mutable": false, + "end_line": 46 + } + ], + "events": [], + "methods": [], + "slots": [ + { + "name": "default", + "description": "Slot for default content" + } + ], + "examples": { + "basic": "", + "variations": [], + "args": { + "hierarchy": "'p'", + "label": "content", + "size": "'md'", + "weight": "'normal'" + }, + "argTypes": {}, + "usage": [] + }, + "tag": "modus-wc-typography", + "storyExample": { + "template": "", + "args": { + "hierarchy": "'p'", + "label": "content", + "size": "'md'", + "weight": "'normal'" + }, + "argTypes": {}, + "events": [], + "fullContent": "import { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\nimport {\n TypographyHierarchy,\n TypographySize,\n TypographyWeight,\n} from '../types';\n\nconst content = 'The quick brown fox jumps over the lazy dog';\n\ninterface TypographyArgs {\n 'custom-class'?: string;\n hierarchy: TypographyHierarchy;\n label: string;\n size?: TypographySize;\n weight?: TypographyWeight;\n}\n\nconst meta: Meta = {\n title: 'Components/Typography',\n component: 'modus-wc-typography',\n args: {\n hierarchy: 'p',\n label: content,\n size: 'md',\n weight: 'normal',\n },\n argTypes: {\n hierarchy: {\n control: { type: 'select' },\n options: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p'],\n },\n size: {\n control: { type: 'select' },\n options: [\n 'xs',\n 'sm',\n 'md',\n 'lg',\n 'xl',\n '2xl',\n '3xl',\n '4xl',\n '5xl',\n '6xl',\n '7xl',\n '8xl',\n '9xl',\n ],\n },\n weight: {\n control: { type: 'select' },\n options: ['light', 'normal', 'semibold', 'bold'],\n },\n },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nconst Template: Story = {\n render: (args) => {\n // prettier-ignore\n return html`\n\n `;\n },\n};\n\nexport const Default: Story = { ...Template };\n\nexport const Heading1: Story = {\n ...Template,\n args: { hierarchy: 'h1', label: content },\n};\n\nexport const Heading2: Story = {\n ...Template,\n args: { hierarchy: 'h2', label: content },\n};\n\nexport const Heading3: Story = {\n ...Template,\n args: { hierarchy: 'h3', label: content },\n};\n\nexport const Heading4: Story = {\n ...Template,\n args: { hierarchy: 'h4', label: content },\n};\n\nexport const Heading5: Story = {\n ...Template,\n args: { hierarchy: 'h5', label: content },\n};\n\nexport const Heading6: Story = {\n ...Template,\n args: { hierarchy: 'h6', label: content },\n};\n\nexport const Paragraph: Story = {\n ...Template,\n args: { hierarchy: 'p', label: content },\n};\n\nexport const WithLabel: Story = {\n ...Template,\n args: {\n hierarchy: 'p',\n label: 'This text is set via the label prop',\n },\n};\n\nexport const WithSlot: Story = {\n render: (args) => {\n // prettier-ignore\n return html`\n\n This text is set using slot\n\n `;\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n if (!customElements.get('typography-shadow-host')) {\n const TypographyShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-typography',\n propsMapper: (v: TypographyArgs, el: HTMLElement) => {\n const typographyEl = el as unknown as {\n customClass: string;\n hierarchy: string;\n label: string;\n size: string;\n weight: string;\n };\n typographyEl.customClass = v['custom-class'] || '';\n typographyEl.hierarchy = v.hierarchy;\n typographyEl.label = v.label;\n typographyEl.size = v.size ?? 'md';\n typographyEl.weight = v.weight ?? 'normal';\n },\n });\n customElements.define('typography-shadow-host', TypographyShadowHost);\n }\n\n return html``;\n },\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/component-docs/modus-wc-utility-panel.json b/mcp/versions/1.5.0/component-docs/modus-wc-utility-panel.json new file mode 100644 index 000000000..ca9760e40 --- /dev/null +++ b/mcp/versions/1.5.0/component-docs/modus-wc-utility-panel.json @@ -0,0 +1,94 @@ +{ + "description": "", + "properties": [ + { + "name": "customClass", + "type": "string", + "description": "Custom CSS class to apply to the outer div.", + "default": "''", + "mutable": false, + "end_line": 19 + }, + { + "name": "expanded", + "type": "boolean", + "description": "The panel is expanded or closed", + "default": "false", + "mutable": false, + "end_line": 22 + }, + { + "name": "pushContent", + "type": "boolean", + "description": "Determines if the panel pushes content or displays an overlay.", + "default": "false", + "mutable": false, + "end_line": 25 + }, + { + "name": "targetElement", + "type": "HTMLElement", + "description": "Target element reference to push content when panel opens", + "default": null, + "mutable": false, + "end_line": 28 + } + ], + "events": [ + { + "name": "panelOpened", + "detail": "void", + "description": "An event that fires when the panel is opened.", + "end_line": 31 + }, + { + "name": "panelClosed", + "detail": "void", + "description": "An event that fires when the panel is closed.", + "end_line": 34 + } + ], + "methods": [], + "slots": [ + { + "name": "header", + "description": "Slot for header content" + }, + { + "name": "body", + "description": "Slot for body content" + }, + { + "name": "footer", + "description": "Slot for footer content" + } + ], + "examples": { + "basic": "", + "variations": [], + "args": { + "expanded": "false", + "push-content": "true" + }, + "argTypes": {}, + "usage": [], + "events": [ + "panelOpened", + "panelClosed" + ] + }, + "tag": "modus-wc-utility-panel", + "storyExample": { + "template": "", + "args": { + "expanded": "false", + "push-content": "true" + }, + "argTypes": {}, + "events": [ + "panelOpened", + "panelClosed" + ], + "fullContent": "import { withActions } from '@storybook/addon-actions/decorator';\nimport type { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\n\ninterface UtilityPanelArgs {\n 'custom-class'?: string;\n expanded: boolean;\n 'push-content': boolean;\n}\n\nconst meta: Meta = {\n title: 'Components/Utility Panel',\n component: 'modus-wc-utility-panel',\n args: {\n expanded: false,\n 'push-content': true,\n },\n argTypes: {\n 'custom-class': {\n control: { type: 'text' },\n },\n expanded: {\n control: { type: 'boolean' },\n },\n 'push-content': {\n control: { type: 'boolean' },\n },\n },\n decorators: [\n withActions,\n (story) => {\n // Set up targetElement after story renders\n requestAnimationFrame(() => {\n const panels = document.querySelectorAll('modus-wc-utility-panel');\n panels.forEach((panel) => {\n const container = panel.closest('.demo-container');\n if (container) {\n const contentElement = container.querySelector('.main-content');\n if (contentElement && panel) {\n (\n panel as HTMLElement & { targetElement: HTMLElement }\n ).targetElement = contentElement as HTMLElement;\n }\n }\n });\n });\n return story();\n },\n ],\n parameters: {\n actions: {\n handles: ['panelOpened', 'panelClosed'],\n },\n docs: {\n description: {\n component:\n \"A utility panel component that slides in from the right side of the screen. It can either push content or display as an overlay.\\n\\nThe component supports `` called 'header' for panel title content, 'body' for main content area, and 'footer' for action buttons or additional controls.\",\n },\n },\n layout: 'fullscreen',\n },\n};\n\n// Shared CSS styles as string for documentation\nconst utilityPanelStyles = `\n .demo-container {\n height: 100vh;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n background: white;\n }\n\n modus-wc-navbar {\n flex-shrink: 0;\n }\n\n .main-content-wrapper {\n flex: 1;\n overflow: hidden;\n position: relative;\n background: var(--modus-wc-color-base-page);\n }\n\n .main-content {\n height: 100%;\n padding: 20px;\n background: var(--modus-wc-color-base-page);\n overflow: auto;\n }\n\n .main-content p,\n .modus-wc-utility-panel-body p {\n margin-bottom: var(--modus-wc-spacing-2xl);\n }\n\n .modus-wc-utility-panel-header {\n font-size: 18px;\n font-weight: 600;\n }\n\n .modus-wc-utility-panel-body {\n padding: 20px 0;\n }\n\n .modus-wc-utility-panel-footer {\n display: flex;\n gap: 10px;\n justify-content: flex-end;\n }\n\n`;\n\nconst scriptBlock = `\n`;\n\nexport default meta;\ntype Story = StoryObj;\n\nexport const Default: Story = {\n parameters: {\n docs: {\n source: {\n transform: () => `\n\n\n\n
    \n \n
    \n \n \n \n \n \n
    \n
    \n\n
    \n
    \n

    Main Content Area

    \n

    \n This is the main content area below the navbar. When the utility\n panel opens with pushContent=true, this content will be pushed to\n the left.\n

    \n

    \n This is an example of how the utility panel interacts with the main\n content. When the panel opens with push content enabled, this area\n will shift to the left to make room for the panel.\n

    \n

    \n The content area maintains its full functionality while the panel is\n open. Users can continue to interact with the main content while\n accessing the utility panel features.\n

    \n
    \n\n \n
    \n Utility Panel Header\n
    \n\n
    \n

    This is the utility panel body content.

    \n

    \n You can add any content here including forms, lists, or other\n components.\n

    \n \n \n
    \n\n
    \n Cancel\n Save\n
    \n \n
    \n
    \n${scriptBlock}`,\n },\n },\n },\n render: (args) => html`\n \n
    \n \n
    \n \n \n \n \n \n
    \n
    \n\n
    \n
    \n

    Main Content Area

    \n

    \n This is the main content area below the navbar. When the utility\n panel opens with pushContent=true, this content will be pushed to\n the left.\n

    \n

    \n This is an example of how the utility panel interacts with the main\n content. When the panel opens with push content enabled, this area\n will shift to the left to make room for the panel.\n

    \n

    \n The content area maintains its full functionality while the panel is\n open. Users can continue to interact with the main content while\n accessing the utility panel features.\n

    \n
    \n\n \n
    \n Utility Panel Header\n
    \n\n
    \n

    This is the utility panel body content.

    \n

    \n You can add any content here including forms, lists, or other\n components.\n

    \n \n \n
    \n\n
    \n Cancel\n Save\n
    \n \n
    \n
    \n `,\n};\nexport const Expanded: Story = {\n args: {\n expanded: true,\n 'push-content': true,\n },\n parameters: {\n docs: {\n source: {\n transform: () => `\n\n\n
    \n \n
    \n \n \n \n
    \n
    \n\n
    \n
    \n

    Main Content Area (Expanded Story)

    \n

    \n This story shows the panel already expanded. The content should be\n pushed to the left.\n

    \n

    \n The utility panel provides quick access to additional tools and\n information. It can be used for settings, filters, or any\n supplementary content that enhances the main application.\n

    \n
    \n\n \n
    \n Expanded Panel Header\n
    \n\n
    \n

    This panel starts in the expanded state.

    \n
    \n\n
    \n Cancel\n Save\n
    \n \n
    \n
    \n${scriptBlock}`,\n },\n },\n },\n render: (args) =>\n // prettier-ignore\n html`\n \n\n
    \n \n
    \n \n \n \n
    \n
    \n\n
    \n
    \n

    Main Content Area (Expanded Story)

    \n

    \n This story shows the panel already expanded. The content should be\n pushed to the left.\n

    \n

    \n The utility panel provides quick access to additional tools and\n information. It can be used for settings, filters, or any\n supplementary content that enhances the main application.\n

    \n
    \n\n \n
    \n Expanded Panel Header\n
    \n\n
    \n

    This panel starts in the expanded state.

    \n
    \n\n
    \n Cancel\n Save\n
    \n \n
    \n
    \n `,\n};\nexport const OverlayMode: Story = {\n args: {\n expanded: true,\n 'push-content': false,\n },\n parameters: {\n docs: {\n source: {\n transform: () => `\n\n\n
    \n \n
    \n \n \n \n
    \n
    \n\n
    \n
    \n

    Main Content Area (Overlay Mode)

    \n

    \n In overlay mode, the panel appears over the content without pushing\n it.\n

    \n

    \n This example demonstrates the overlay mode where the panel appears\n on top of the content without pushing it aside. This is useful when\n you want to preserve the layout of the main content area.\n

    \n
    \n\n \n
    \n Overlay Panel Header\n
    \n\n
    \n

    This panel overlays the content without pushing it.

    \n
    \n\n
    \n Cancel\n Save\n
    \n \n
    \n
    \n${scriptBlock}`,\n },\n },\n },\n render: (args) => html`\n \n\n
    \n \n
    \n \n \n \n
    \n
    \n\n
    \n
    \n

    Main Content Area (Overlay Mode)

    \n

    \n In overlay mode, the panel appears over the content without pushing\n it.\n

    \n

    \n This example demonstrates the overlay mode where the panel appears\n on top of the content without pushing it aside. This is useful when\n you want to preserve the layout of the main content area.\n

    \n
    \n\n \n
    \n Overlay Panel Header\n
    \n\n
    \n

    This panel overlays the content without pushing it.

    \n
    \n\n
    \n Cancel\n Save\n
    \n \n
    \n
    \n `,\n};\nexport const WithoutHeaderFooter: Story = {\n args: {\n expanded: false,\n 'push-content': true,\n },\n render: (args: UtilityPanelArgs) => html`\n \n\n
    \n
    \n

    Main Content Area

    \n \n Toggle Panel\n \n
    \n\n \n
    \n

    Simple Body Content

    \n

    This panel only has body content without header or footer.

    \n
    \n \n
    \n `,\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n if (!customElements.get('utility-panel-shadow-host')) {\n const UtilityPanelShadowHost = createShadowHostClass({\n componentTag: 'modus-wc-utility-panel',\n propsMapper: (v: UtilityPanelArgs, el: HTMLElement) => {\n (el as unknown as { expanded: boolean }).expanded = Boolean(\n v.expanded\n );\n (el as unknown as { pushContent: boolean }).pushContent = Boolean(\n v['push-content']\n );\n\n // Build full layout on first render: add slot content to el, then\n // move el into the demo layout inside the helper's wrapper.\n if (!el.hasAttribute('data-layout-built')) {\n el.setAttribute('data-layout-built', '');\n\n const shadowRoot = el.getRootNode() as ShadowRoot;\n const wrapper = el.parentElement!;\n\n // Slot content\n const header = document.createElement('div');\n header.setAttribute('slot', 'header');\n header.className = 'modus-wc-utility-panel-header';\n header.textContent = 'Utility Panel Header';\n\n const body = document.createElement('div');\n body.setAttribute('slot', 'body');\n body.className = 'modus-wc-utility-panel-body';\n const bp1 = document.createElement('p');\n bp1.textContent = 'This is the utility panel body content.';\n const bp2 = document.createElement('p');\n bp2.textContent =\n 'You can add any content here including forms, lists, or other components.';\n const input = document.createElement('modus-wc-text-input');\n input.setAttribute('label', 'Example Input');\n input.setAttribute('placeholder', 'Enter text...');\n body.appendChild(bp1);\n body.appendChild(bp2);\n body.appendChild(input);\n\n const footer = document.createElement('div');\n footer.setAttribute('slot', 'footer');\n footer.className = 'modus-wc-utility-panel-footer';\n const cancelBtn = document.createElement('modus-wc-button');\n cancelBtn.setAttribute('color', 'tertiary');\n cancelBtn.setAttribute('size', 'sm');\n cancelBtn.textContent = 'Cancel';\n const saveBtn = document.createElement('modus-wc-button');\n saveBtn.setAttribute('color', 'primary');\n saveBtn.setAttribute('size', 'sm');\n saveBtn.textContent = 'Save';\n footer.appendChild(cancelBtn);\n footer.appendChild(saveBtn);\n\n el.appendChild(header);\n el.appendChild(body);\n el.appendChild(footer);\n\n // Navbar with toggle button\n const navbar = document.createElement('modus-wc-navbar');\n (navbar as unknown as { visibility: object }).visibility = {\n user: false,\n };\n (navbar as unknown as { userCard: object }).userCard = {\n name: '',\n email: '',\n };\n const endSlot = document.createElement('div');\n endSlot.setAttribute('slot', 'end');\n const tooltip = document.createElement('modus-wc-tooltip');\n tooltip.setAttribute('content', 'Toggle Utility Panel');\n tooltip.setAttribute('position', 'left');\n const toggleBtn = document.createElement('modus-wc-button');\n toggleBtn.setAttribute('color', 'primary');\n toggleBtn.setAttribute('size', 'sm');\n toggleBtn.setAttribute('variant', 'outlined');\n const menuIcon = document.createElement('modus-wc-icon');\n menuIcon.setAttribute('name', 'menu');\n toggleBtn.appendChild(menuIcon);\n tooltip.appendChild(toggleBtn);\n endSlot.appendChild(tooltip);\n navbar.appendChild(endSlot);\n toggleBtn.addEventListener('buttonClick', () => {\n (el as unknown as { expanded: boolean }).expanded = !(\n el as unknown as { expanded: boolean }\n ).expanded;\n });\n\n // Main content\n const mainContent = document.createElement('div');\n mainContent.className = 'main-content';\n const h1 = document.createElement('h1');\n h1.textContent = 'Main Content Area';\n const p1 = document.createElement('p');\n p1.textContent =\n 'This is the main content area below the navbar. When the utility panel opens with pushContent=true, this content will be pushed to the left.';\n const p2 = document.createElement('p');\n p2.textContent =\n 'This is an example of how the utility panel interacts with the main content. When the panel opens with push content enabled, this area will shift to the left to make room for the panel.';\n const p3 = document.createElement('p');\n p3.textContent =\n 'The content area maintains its full functionality while the panel is open. Users can continue to interact with the main content while accessing the utility panel features.';\n mainContent.appendChild(h1);\n mainContent.appendChild(p1);\n mainContent.appendChild(p2);\n mainContent.appendChild(p3);\n\n // Move el into the content wrapper\n const contentWrapper = document.createElement('div');\n contentWrapper.className = 'main-content-wrapper';\n contentWrapper.appendChild(mainContent);\n contentWrapper.appendChild(el);\n\n const container = document.createElement('div');\n container.className = 'demo-container';\n container.appendChild(navbar);\n container.appendChild(contentWrapper);\n\n wrapper.appendChild(container);\n\n const styleEl = document.createElement('style');\n styleEl.textContent = utilityPanelStyles;\n shadowRoot.appendChild(styleEl);\n\n requestAnimationFrame(() => {\n (el as unknown as { targetElement: HTMLElement }).targetElement =\n mainContent;\n });\n }\n },\n });\n customElements.define(\n 'utility-panel-shadow-host',\n UtilityPanelShadowHost\n );\n }\n\n return html``;\n },\n};\n" + } +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/docs/_all_docs.json b/mcp/versions/1.5.0/docs/_all_docs.json new file mode 100644 index 000000000..05039a663 --- /dev/null +++ b/mcp/versions/1.5.0/docs/_all_docs.json @@ -0,0 +1,15 @@ +{ + "total": 10, + "docs": [ + "accessibility", + "angular", + "form-inputs", + "getting-started", + "modus-figma-mcp-integration-guide", + "modus-icon-usage", + "react", + "styling", + "testing", + "vue" + ] +} \ No newline at end of file diff --git a/mcp/versions/1.5.0/docs/angular.mdx b/mcp/versions/1.5.0/docs/angular.mdx new file mode 100644 index 000000000..7f65fade5 --- /dev/null +++ b/mcp/versions/1.5.0/docs/angular.mdx @@ -0,0 +1,374 @@ +import { Meta } from '@storybook/blocks'; + + + +# Angular Framework Integration + +This guide will help you get started with consuming the Modus Angular Web Component library in your Angular project. + +We highly recommend using the Modus Angular Components library for Angular based projects. +These components are automatically generated using the Stencil Angular Framework Integration. + +Follow the steps outlined below to integrate and use Modus Angular Web Components effectively. + +Please refer to the [official stencil documentation](https://stenciljs.com/docs/angular#consumer-usage) for more information on how to integrate with your Angular project. + +## Angular with modules + +Modus Angular Components have a peer dependency with Modus Web Components and require the +installation of both packages. + +### 1. Install both `modus-wc` and `modus-wc-angular` dependencies: + +Ensure that you specify the target version of Angular for the `modus-wc-angular` package (e.g., `ng18` for Angular 18). + + + Lock the installed package versions to avoid unintended breakages on future + npm installs. + + +```bash +npm install @trimble-oss/moduswebcomponents @trimble-oss/moduswebcomponents-angular@-ng +``` + +### 2. Set up the styling: + +You will need to import our styling in your main JavaScript or CSS file: + +```js +import '@trimble-oss/moduswebcomponents/modus-wc-styles.css'; +``` + +### 3. Configure asset copying in `angular.json`: + +For components that use static assets (like `modus-wc-logo`), you need to configure Angular to copy the assets to your build output. + +Add this to your `angular.json` under `projects > your-app > architect > build > options`: + +```json +{ + "projects": { + "your-app": { + "architect": { + "build": { + "options": { + "assets": [ + { + "glob": "**/*", + "input": "node_modules/@trimble-oss/moduswebcomponents/assets", + "output": "/assets" + } + ] + } + } + } + } + } +} +``` + +### 4. Import Modus Angular Web Components library into your Angular app's module: + +```ts +// app.module.ts +import { ModusAngularComponentsModule } from '@trimble-oss/moduswebcomponents-angular'; + +@NgModule({ + ... + imports: [ComponentLibraryModule], + ... +}) +export class AppModule {} +``` + +### 4. Use Modus Angular Web Components while leveraging Angular template binding syntax: + +```ts +// app.component.html + +``` + +## Angular with standalone components + +Modus Angular Components have a peer dependency with Modus Web Components and require the +installation of both packages. + +### 1. Install both `modus-wc` and `modus-wc-angular` dependencies: + +Ensure that you specify the target version of Angular for the `modus-wc-angular` package (e.g., `ng18` for Angular 18). + + + Lock the installed package versions to avoid unintended breakages on future + npm installs. + + +```bash +npm install @trimble-oss/moduswebcomponents @trimble-oss/moduswebcomponents-angular@-ng +``` + +### 2. Set up the styling: + +You will need to import our styling in your main JavaScript or CSS file: + +```js +import '@trimble-oss/moduswebcomponents/modus-wc-styles.css'; +``` + +### 3. Import your component library into your component. + +You must distribute your components through a primary `NgModule` to use your components in a standalone component. + +```ts +// app.component.ts +import { Component } from '@angular/core'; +import { ModusAngularComponentsModule } from '@trimble-oss/moduswebcomponents-angular'; + +@Component({ + selector: 'app-root', + standalone: true, + imports: [ModusAngularComponentsModule], + templateUrl: './app.component.html', +}) +export class AppComponent {} +``` + +### 4. Use Modus Angular Web Components while leveraging Angular template binding syntax: + +```ts +// app.component.html + +``` + +### Custom Elements Schema + +In the `app.module.ts` file, you need to tell angular that you are using custom element schemas +so that it does not throw errors when unknown element names are used in the markup. + +Import `CUSTOM_ELEMENTS_SCHEMA` and add it to your `@NgModule`'s schemas: + +```ts +import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; + +@NgModule({ + ... + schemas: [CUSTOM_ELEMENTS_SCHEMA] + ... +}) +``` + +### Wrapping Components + +When using Modus Web Components directly, it is recommended to wrap it in corresponding Angular components within your application. This will abstract away from the library dependency, allowing more flexibility for you and your application in the future. Each part of the component is able to be abstracted, leaving you with an Angular-native component. + +Notice Angular allows `[]` and `()` markup syntax for the web component's inputs and outputs, respectively. + +Wrapped Modus Button Example: + +```ts +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +@Component({ + selector: 'button-component', + template: ` + + + + `, +}) +export class ButtonComponent { + @Input() buttonStyle: 'borderless' | 'fill' | 'outline' = 'fill'; + @Input() color: 'danger' | 'default' | 'primary' | 'secondary' | 'warning' = + 'default'; + @Input() disabled: boolean; + @Input() size: 'small' | 'medium' | 'large' = 'medium'; + + @Output() onButtonClick = new EventEmitter(); +} +``` + +### Reactive Forms + +Working with a web component's inputs/outputs works great but these components do not integrate with Angular's reactive forms quite as easily. Since web components do not know about Angular's form APIs, we must extend form-compatible components' behavior with simple directives. These directives are applied to the web component selectors, giving the components Angular form functionality. + +Let's take a look at a directive implementation for a Modus Select's form functionality. + +#### Wrapper + +You'll notice the `modus-select` in the markup is taking extra inputs, such as `formControl` and `optionsDisplayProp`, these inputs are provided by the directive below. Here is what our wrapper looks like: + +```ts +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { FormControl } from '@angular/forms'; + +@Component({ + selector: 'select-component', + template: ` + + + `, +}) +export class SelectComponent { + @Input() disabled: boolean; + @Input() errorText: string; + @Input() formControl: FormControl; + @Input() helperText: string; + @Input() label: string; + @Input() options: unknown[] = []; + @Input() optionsDisplayProp: string; + @Input() required: boolean; + @Input() size: 'medium' | 'large' = 'medium'; + @Input() validText: string; + @Input() value: unknown; + + @Output() onSelectValueChange = new EventEmitter(); +} +``` + +#### Directive + +Moving onto the directive, there are a few things to keep in mind. + +- The directive's selector is set to the web component's tag, not the wrapper's. +- Implementing the `ControlValueAccessor` interface helps Angular understand when the form control has been updated or changed. + - When the value is set, `onChange()` notifies that the control has been updated. + - Calling `onTouched()` lets Angular know the component has been touched, which is needed for form validation. +- The `get value()`, and `set value()` are used by Angular's form control. +- Using the `@HostListener` decorator lets you listen to events from the web component, and execute appropriate logic. + +Here is what our directive looks like: + +```ts +import { + Directive, + forwardRef, + ElementRef, + HostListener, + Input, + OnInit, + Output, + EventEmitter, +} from '@angular/core'; +import { + ControlValueAccessor, + FormControl, + NG_VALUE_ACCESSOR, +} from '@angular/forms'; + +@Directive({ + selector: 'modus-wc-select', + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ModusSelectDirective), + multi: true, + }, + ], +}) +export class ModusSelectDirective implements ControlValueAccessor, OnInit { + @Input() disabled: boolean; + @Input() errorText: string; + @Input() formControl: FormControl; + @Input() helperText: string; + @Input() label: string; + @Input() options: unknown[]; + @Input() optionsDisplayProp: string; + @Input() required: boolean; + @Input() selectValue: unknown; + @Input() size: 'medium' | 'large'; + @Input() validText: string; + + @Output() valueChange = new EventEmitter(); + + onChange: any = () => {}; + onTouched: any = () => {}; + + private _value: string; + + get value() { + return this._value; + } + + set value(value) { + if (value !== this._value) { + this._value = value; + this.onChange(this._value); + this.onTouched(); + this.elementRef.nativeElement.value = value; + } + } + + constructor(private elementRef: ElementRef) {} + + ngOnInit(): void { + const modusSelect = this.elementRef.nativeElement as HTMLModusSelectElement; + modusSelect.disabled = this.disabled; + modusSelect.errorText = this.errorText; + modusSelect.helperText = this.helperText; + modusSelect.label = this.label; + modusSelect.options = this.options; + modusSelect.optionsDisplayProp = this.optionsDisplayProp; + modusSelect.required = this.required; + modusSelect.size = this.size; + modusSelect.validText = this.validText; + modusSelect.value = this.selectValue; + + if (!this.formControl) { + this.formControl = new FormControl(null); + } + } + + @HostListener('valueChange', ['$event.detail']) + listenForValueChange(value: string): void { + this.value = value; + } + + registerOnChange(fn: Function): void { + this.onChange = fn; + } + + registerOnTouched(fn: Function): void { + this.onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + writeValue(value: string): void { + if (value) { + this.value = value; + } + } +} +``` + +Now adding the Modus Select as a form control is as easy as: + +```ts + + +``` diff --git a/mcp/versions/1.5.0/docs/getting-started.mdx b/mcp/versions/1.5.0/docs/getting-started.mdx new file mode 100644 index 000000000..8c28629b6 --- /dev/null +++ b/mcp/versions/1.5.0/docs/getting-started.mdx @@ -0,0 +1,315 @@ +import { Meta } from '@storybook/blocks'; + + + +# Getting Started + +## Installation + + + Lock the installed package version to avoid unintended breakages on future npm + installs. + + +```bash +npm install @trimble-oss/moduswebcomponents +``` + +## Usage + +### 1. Set up the styling: + +You will need to import our styling in your main JavaScript or CSS file: + +```js +import '@trimble-oss/moduswebcomponents/modus-wc-styles.css'; +``` + +#### Variables-Only CSS + +If you want Modus styling to apply only to your components and **not affect the rest of your application**, you can import the lightweight variables-only file instead: + +```js +import '@trimble-oss/moduswebcomponents/modus-wc-variables.css'; +``` + +Modus components inside Shadow DOM will automatically inject their own class rules via `handleShadowDOMStyles()`, so they render correctly without the full stylesheet being loaded globally. + +> **Note:** Both imports work with Shadow DOM. We recommend `modus-wc-styles.css` for new applications. Consider `modus-wc-variables.css` for existing applications where you want to avoid global style changes. + +### 2. Set the theme: + +The theme can be set manually or by using the `ThemeSwitcher` component. See the "Use a Theme" section of [Styling](/docs/documentation-styling--docs) for guidance. + +Available themes are: + +- `modus-modern-light` (default) +- `modus-modern-dark` +- `modus-classic-light` +- `modus-classic-dark` +- `connect-light` +- `connect-dark` + +### 3. Register custom elements: + +```javascript +import { defineCustomElements } from '@trimble-oss/moduswebcomponents/loader'; + +// Call during the initial loading of your application +const Root = () => { + defineCustomElements(); + + return ; +}; +``` + +### 4. Use the components: + +```javascript +// Use in HTML +Click me +``` + +## Types + +Types are a crucial part of our component library, providing robust type safety and enhanced developer experience through comprehensive TypeScript definitions for all components. + +```javascript +import { ISelectOption, ModusWcSelectCustomEvent } from '@trimble-oss/moduswebcomponents'; + +const options: ISelectOption[] = [ + { + label: 'Option 1', + value: '1', + }, + { + label: 'Option 2', + value: '2', + }, +]; + +const handleEvent = (e: ModusWcSelectCustomEvent) => {} +``` + +## Testing with Jest + +If you are using the React integration package (`@trimble-oss/moduswebcomponents-react`), this package is published as ES modules. To use it in a Jest environment you need to configure Babel to transpile it. + +Add `transformIgnorePatterns` to your Jest config (`package.json` or `jest.config.js`): + +```json +{ + "jest": { + "transformIgnorePatterns": ["/node_modules/(?!(@trimble-oss|@stencil))"] + } +} +``` + +Ensure your `babel.config.js` includes these presets: + +```js +module.exports = { + presets: [ + ['@babel/preset-env', { targets: { node: 'current' } }], + ['@babel/preset-react', { runtime: 'automatic' }], + '@babel/preset-typescript', + ], +}; +``` + +## Framework Integrations + +- ### [Angular](?path=/docs/documentation-frameworks-angular--docs) + +- ### [React](?path=/docs/documentation-frameworks-react--docs) + +- ### [Vue](?path=/docs/documentation-frameworks-vue--docs) + +## Server-Side Rendering (SSR) + +Modus Web Components supports server-side rendering for React 19 + Next.js (App Router) via [`@stencil/ssr`](https://www.npmjs.com/package/@stencil/ssr). + +This is a one-time setup — once configured, all Modus React components are automatically server-rendered on every page with no per-page wrappers needed. + +### Requirements + +- React 19 +- Next.js (App Router) +- `@trimble-oss/moduswebcomponents-react` (React 19 integration package) + +### Installation + +```bash +npm install @trimble-oss/moduswebcomponents @trimble-oss/moduswebcomponents-react +npm install --save-dev @stencil/ssr +``` + +### Setup + +**1. Configure `next.config.ts` (one-time)** + +```ts +// next.config.ts +import type { NextConfig } from 'next'; +import stencilSSR from '@stencil/ssr/next'; + +const nextConfig: NextConfig = {}; + +export default stencilSSR({ + module: import('@trimble-oss/moduswebcomponents-react'), + from: '@trimble-oss/moduswebcomponents-react', + hydrateModule: import('@trimble-oss/moduswebcomponents/hydrate'), + serializeShadowRoot: 'declarative-shadow-dom', +})(nextConfig); +``` + +> **Note:** `@stencil/ssr` currently requires the Webpack bundler. Start your dev server with `next dev --webpack`. + +**2. Register custom elements for interactivity (one-time)** + +SSR provides the initial HTML render. `defineCustomElements()` must still be called client-side to enable events and component interactivity. + +```tsx +// app/modus-init.tsx +'use client'; +import { useEffect } from 'react'; +import { defineCustomElements } from '@trimble-oss/moduswebcomponents/loader'; + +export function ModusInit() { + useEffect(() => { + defineCustomElements(); + }, []); + return null; +} +``` + +Add it once to your root layout: + +```tsx +// app/layout.tsx +import { ModusInit } from './modus-init'; + +export default function RootLayout({ children }) { + return ( + + + + + + + + {children} + + + ); +} +``` + +**3. Use components — no wrappers needed** + +Server Components render Modus components directly. Client Components handle interactivity. + +```tsx +// app/page.tsx — server-rendered display components +import { + ModusWcBadge, + ModusWcAlert, +} from '@trimble-oss/moduswebcomponents-react'; +import { InteractiveSection } from './interactive-section'; + +export default function Page() { + return ( + <> + New + + + + ); +} +``` + +```tsx +// app/interactive-section.tsx — client-rendered interactive components +'use client'; +import { ModusWcButton } from '@trimble-oss/moduswebcomponents-react'; + +export function InteractiveSection() { + return ( + console.log('clicked')} + > + Click me + + ); +} +``` + +**4. Copy CSS and assets to `public/`** + +Modus CSS references font files via relative paths and must be served statically. Add a `postinstall` script to automate this: + +```json +{ + "scripts": { + "postinstall": "cp node_modules/@trimble-oss/moduswebcomponents/modus-wc-styles.css public/ && cp node_modules/@trimble-oss/moduswebcomponents/modus-icons.css public/ && cp -r node_modules/@trimble-oss/moduswebcomponents/assets public/" + } +} +``` + +### How it works + +- **Server Components** — Modus components are serialized to static HTML on the server, with their styles included inline. The browser displays styled content immediately, before JavaScript loads. +- **Client Components** — Components with event handlers must be in a `'use client'` file. These hydrate after JS loads. +- **`defineCustomElements()`** — Required once in the client to register the Stencil runtime for interactivity. SSR alone does not wire up events. + +### Notes + +- Components that accept named slot content as React children (e.g. a custom dropdown trigger) may not render that slot content during SSR. Move such components into a `'use client'` boundary to ensure correct rendering. +- This setup is specific to Next.js App Router with React 19. For other frameworks or React 18, see the manual SSR approach below. + +### Manual SSR (React 18 / Remix / other Node.js servers) + +For frameworks other than Next.js App Router, or when using React 18, you can call `renderToString` directly from the `hydrate` subpath. + +```tsx +// lib/modus-ssr.tsx +import { renderToString } from '@trimble-oss/moduswebcomponents/hydrate'; + +export async function ModusSSR({ html: inputHtml }: { html: string }) { + const { html } = await renderToString(inputHtml, { + fullDocument: false, + serializeShadowRoot: 'declarative-shadow-dom', + }); + return
    ; +} +``` + +Use it in any server-rendered page or route: + +```tsx +import { ModusSSR } from '@/lib/modus-ssr'; + +export default async function Page() { + return ( + Click me + New + `} + /> + ); +} +``` + +> **Note:** `inputHtml` must be trusted server-side markup — do not pass user-supplied content. `renderToString` is a Node.js API and cannot run in browser or Edge Runtime environments. + +--- + +Need help? Check out our [GitHub repository](https://github.com/trimble-oss/modus-wc-2.0) +or [raise an issue](https://github.com/trimble-oss/modus-wc-2.0/issues). diff --git a/mcp/versions/1.5.0/docs/react.mdx b/mcp/versions/1.5.0/docs/react.mdx new file mode 100644 index 000000000..5acc9afd4 --- /dev/null +++ b/mcp/versions/1.5.0/docs/react.mdx @@ -0,0 +1,131 @@ +import { Meta } from '@storybook/blocks'; + + + +# React Framework Integration + +This guide will help you get started with consuming the Modus React Web Component library in your React project. + +We highly recommend using the Modus React Components library for React based projects. +These components are automatically generated using the Stencil React Framework Integration. + +Follow the steps outlined below to integrate and use Modus React Web Components effectively. + +Please refer to the [official stencil documentation](https://stenciljs.com/docs/react#consumer-usage) for more information on how to integrate with your React project. + +## Usage + +Modus React Components have a peer dependency with Modus Web Components and require the +installation of both packages. + +### 1. Install `modus-wc-react`: + +Ensure that you specify the target version of React for the `modus-wc-react` package (e.g., `react18` for React 18). + + + Lock the installed package versions to avoid unintended breakages on future + npm installs. + + +```bash +npm install @trimble-oss/moduswebcomponents-react@-react +# e.g., +npm install @trimble-oss/moduswebcomponents-react@1.0.0-react18 +``` + +### 2. Set up the styling: + +You will need to import our styling in your main JavaScript or CSS file: + +```js +import '@trimble-oss/moduswebcomponents/modus-wc-styles.css'; +``` + +### 3. Use the component library as normal. + +```tsx +import { ModusWcBadge } from '@trimble-oss/moduswebcomponents-react'; + +; +``` + +### Using the Controlled Input Pattern + +The controlled input pattern involves maintaining the state of the input's value within the React application or +component. The [React Docs](https://react.dev/reference/react-dom/components/input#controlling-an-input-with-a-state-variable) +describe this in more detail. + +```tsx +import React, { useState } from 'react'; +import { ModusWcTextInput } from '@trimble-oss/moduswebcomponents-react'; + +interface Props extends React.ComponentProps {} + +const MyComponent: React.FC = (props) => { + const [value, setValue] = useState(''); + + const handleInputChange = ( + e: CustomEvent + ) => { + const value = e.detail.target.value; + setValue(value); + }; + + return ( + + ); +}; + +export default MyComponent; +``` + +### Wrapping Components + +When using Modus React Components directly, it is recommended to wrap it in corresponding React components within your application. +This will abstract away from the library dependency, allowing more flexibility for you and your application in the future. + +Wrapped Modus component example: + +```tsx +import React from 'react'; +import { ModusWcAvatar } from '@trimble-oss/moduswebcomponents-react'; + +interface Props extends React.ComponentProps {} + +const Avatar: React.FC = (props) => { + return ; +}; + +export default Avatar; +``` + +or, a more complex wrapper: + +```tsx +import React from 'react'; +import { ModusWcTextInput } from '@trimble-oss/moduswebcomponents-react'; + +interface Props extends Omit< + React.ComponentProps, + 'inputChange' +> { + onValueChange: (value: string) => void; +} + +const TextInput: React.FC = (props) => { + const handleInputChange = ( + e: CustomEvent + ) => { + const value = e.detail.target.value; + props.onValueChange(value); + }; + + return ; +}; + +export default TextInput; +``` diff --git a/mcp/versions/1.5.0/docs/vue.mdx b/mcp/versions/1.5.0/docs/vue.mdx new file mode 100644 index 000000000..a6f3929e4 --- /dev/null +++ b/mcp/versions/1.5.0/docs/vue.mdx @@ -0,0 +1,161 @@ +import { Meta } from '@storybook/blocks'; + + + +# Vue Framework Integration + +This guide will help you get started with consuming the Modus Vue Web Component library in your Vue project. + +We highly recommend using the Modus Vue Components library for Vue based projects. +These components are automatically generated using the Stencil Vue Framework Integration. + +Follow the steps outlined below to integrate and use Modus Vue Web Components effectively. + +Please refer to the [official stencil documentation](https://stenciljs.com/docs/vue) for more information on how to integrate with your Vue project. + +## Usage + +Modus Vue Components have a peer dependency with Modus Web Components and require the +installation of both packages. + +### 1. Install `@trimble-oss/moduswebcomponents-vue`: + + + Lock the installed package versions to avoid unintended breakages on future + npm installs. + + +```bash +npm install @trimble-oss/moduswebcomponents @trimble-oss/moduswebcomponents-vue@ +# e.g., +npm install @trimble-oss/moduswebcomponents@1.0.0 @trimble-oss/moduswebcomponents-vue@1.0.0 +``` + +### 2. Set up the styling: + +You will need to import our styling in your main JavaScript or CSS file: + +```js +// main.ts +import '@trimble-oss/moduswebcomponents/modus-wc-styles.css'; +``` + +### 3. Use the component library as normal: + +**Note:** Use camelCase for multi-word properties (e.g., `alertTitle` instead of `alert-title`) + +```vue + + + +``` + +## Custom Elements Configuration + +When using web components directly in Vue, you need to configure Vue to recognize custom elements: + +### 1. Install `@trimble-oss/moduswebcomponents`: + +```bash +npm install @trimble-oss/moduswebcomponents@latest +``` + +### 2. Set up the styling: + +```js +// main.ts +import '@trimble-oss/moduswebcomponents/modus-wc-styles.css'; +``` + +### 3. Configure Vue to work with custom elements: + +In your `vite.config.ts`, you need to tell Vue to ignore custom elements: + +```ts +// vite.config.ts +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; + +export default defineConfig({ + plugins: [ + vue({ + template: { + compilerOptions: { + // Tell Vue to ignore all components starting with "modus-wc" + isCustomElement: (tag) => tag.startsWith('modus-wc'), + }, + }, + }), + ], +}); +``` + +### 4. Use the web components in your Vue templates: + +```vue + +``` + +## Wrapping Components + +When using Modus Vue Components directly, it is recommended to wrap them in corresponding Vue components within your application. This will abstract away from the library dependency, allowing more flexibility for you and your application in the future. + +Wrapped Modus Avatar Example: + +```vue + + + +``` + +or, a more complex wrapper: + +```vue + + + +```