@morya-ui/mcp 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/data/catalog.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
- "generatedAt": "2026-09-14T01:38:21.808Z",
2
+ "generatedAt": "2026-09-14T07:45:14.694Z",
3
3
  "library": {
4
4
  "name": "morya-ui",
5
- "version": "0.2.2"
5
+ "version": "0.2.4"
6
6
  },
7
7
  "mcp": {
8
8
  "name": "@morya-ui/mcp",
9
- "version": "0.2.2"
9
+ "version": "0.2.4"
10
10
  },
11
11
  "components": [
12
12
  {
@@ -4400,6 +4400,11 @@
4400
4400
  "title": "Nested + useContextMenu",
4401
4401
  "body": "```vue preview\n<script setup lang=\"ts\">\nimport { useContextMenu, MContextMenu } from 'morya-ui'\n\nconst menu = useContextMenu()\nconst model = [\n { label: '复制', command: () => undefined },\n { label: '更多', items: [{ label: '深层' }] },\n]\n</script>\n\n<template>\n <div\n style=\"border: 1px dashed var(--m-color-border); padding: 2rem; border-radius: var(--m-radius-md)\"\n @contextmenu=\"menu.show\"\n >\n 右键此处(composable)\n </div>\n <MContextMenu v-model=\"menu.visible\" v-model:position=\"menu.position\" :model=\"model\" />\n</template>\n```"
4402
4402
  },
4403
+ {
4404
+ "id": "滚动与嵌套子菜单",
4405
+ "title": "滚动与嵌套子菜单",
4406
+ "body": "菜单列表与嵌套 `items` 飞出层使用内置 `MScrollbar`(`max-height: min(18rem, 45vh)`)。嵌套层 Teleport 到 `body` 相对父项定位,避免长列表滚动时侧向菜单被裁剪。"
4407
+ },
4403
4408
  {
4404
4409
  "id": "props",
4405
4410
  "title": "Props",
@@ -4426,7 +4431,7 @@
4426
4431
  "body": "<h4 id=\"ContextMenuItem\">ContextMenuItem</h4>\n\n完整定义见源码 `types.ts`。\n\n```ts\ninterface ContextMenuItem extends MenuNodeBase {\n items?: ContextMenuItem[]\n}\n```"
4427
4432
  }
4428
4433
  ],
4429
- "markdown": "---\ntitle: ContextMenu\ncategory: 04 / NAVIGATION\ndescription: 右键上下文菜单,支持 show(event) / hide()。\n---\n\n# ContextMenu\n\n在指针位置弹出的上下文菜单。支持嵌套 `items`。也可用 `useContextMenu()` 绑定 `v-model` / `v-model:position`。\n\n## 引入\n\n```ts\nimport { useContextMenu, MContextMenu } from 'morya-ui'\n```\n\n## 基础用法\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MContextMenu } from 'morya-ui'\n\nconst model = [\n { label: '复制' },\n { label: '粘贴' },\n { separator: true },\n { label: '删除', disabled: true },\n]\n</script>\n\n<template>\n <MContextMenu :model=\"model\">\n <div\n style=\"border: 1px dashed var(--m-color-border); padding: 2rem; border-radius: var(--m-radius-md)\"\n >\n 右键此处打开菜单\n </div>\n </MContextMenu>\n</template>\n```\n\n## Nested + useContextMenu\n\n```vue preview\n<script setup lang=\"ts\">\nimport { useContextMenu, MContextMenu } from 'morya-ui'\n\nconst menu = useContextMenu()\nconst model = [\n { label: '复制', command: () => undefined },\n { label: '更多', items: [{ label: '深层' }] },\n]\n</script>\n\n<template>\n <div\n style=\"border: 1px dashed var(--m-color-border); padding: 2rem; border-radius: var(--m-radius-md)\"\n @contextmenu=\"menu.show\"\n >\n 右键此处(composable)\n </div>\n <MContextMenu v-model=\"menu.visible\" v-model:position=\"menu.position\" :model=\"model\" />\n</template>\n```\n\n## Props\n\n| 参数 | 类型 | 默认值 | 说明 |\n| --- | --- | --- | --- |\n| `model` | `ContextMenuItem[]` | — | 菜单项,可嵌套 `items`;项可含 `key` / `icon`。 |\n| `modelValue` | `boolean` | `false` | 是否可见。 |\n| `position` | `{ x: number; y: number }` | — | 菜单坐标。 |\n| `teleport` | `boolean` | `true` | 浮层 Teleport;默认挂到 `body`。 |\n| `appendTo` | `string \\| HTMLElement \\| 'self' \\| false` | `'body'` | 挂载目标;`'self'` / `false` 就地渲染。 |\n| `pt` | [RootPassThrough](/docs/types#RootPassThrough) `{ root? }` | — | DOM 透传,见 [样式与 attrs](/docs/attrs). |\n\n\n## Events\n\n| 事件名 | 参数 | 说明 |\n| --- | --- | --- |\n| `update:modelValue` | `boolean` | 可见性变化。 |\n| `update:position` | `{ x; y }` | 位置变化。 |\n\n## Methods\n\n| 方法 | 说明 |\n| --- | --- |\n| `show(event)` | 根据鼠标事件或坐标显示。 |\n| `hide()` | 隐藏菜单。 |\n\n`useContextMenu()` 返回 `{ visible, position, show, hide }`,便于命令式打开。\n\n## Slots\n\n| 插槽名 | 说明 |\n| --- | --- |\n| `default` | 触发元素(非 popup 模式)。 |\n\n## 类型\n\n<h4 id=\"ContextMenuItem\">ContextMenuItem</h4>\n\n完整定义见源码 `types.ts`。\n\n```ts\ninterface ContextMenuItem extends MenuNodeBase {\n items?: ContextMenuItem[]\n}\n```\n"
4434
+ "markdown": "---\ntitle: ContextMenu\ncategory: 04 / NAVIGATION\ndescription: 右键上下文菜单,支持 show(event) / hide()。\n---\n\n# ContextMenu\n\n在指针位置弹出的上下文菜单。支持嵌套 `items`。也可用 `useContextMenu()` 绑定 `v-model` / `v-model:position`。\n\n## 引入\n\n```ts\nimport { useContextMenu, MContextMenu } from 'morya-ui'\n```\n\n## 基础用法\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MContextMenu } from 'morya-ui'\n\nconst model = [\n { label: '复制' },\n { label: '粘贴' },\n { separator: true },\n { label: '删除', disabled: true },\n]\n</script>\n\n<template>\n <MContextMenu :model=\"model\">\n <div\n style=\"border: 1px dashed var(--m-color-border); padding: 2rem; border-radius: var(--m-radius-md)\"\n >\n 右键此处打开菜单\n </div>\n </MContextMenu>\n</template>\n```\n\n## Nested + useContextMenu\n\n```vue preview\n<script setup lang=\"ts\">\nimport { useContextMenu, MContextMenu } from 'morya-ui'\n\nconst menu = useContextMenu()\nconst model = [\n { label: '复制', command: () => undefined },\n { label: '更多', items: [{ label: '深层' }] },\n]\n</script>\n\n<template>\n <div\n style=\"border: 1px dashed var(--m-color-border); padding: 2rem; border-radius: var(--m-radius-md)\"\n @contextmenu=\"menu.show\"\n >\n 右键此处(composable)\n </div>\n <MContextMenu v-model=\"menu.visible\" v-model:position=\"menu.position\" :model=\"model\" />\n</template>\n```\n\n## 滚动与嵌套子菜单\n\n菜单列表与嵌套 `items` 飞出层使用内置 `MScrollbar`(`max-height: min(18rem, 45vh)`)。嵌套层 Teleport 到 `body` 相对父项定位,避免长列表滚动时侧向菜单被裁剪。\n\n## Props\n\n| 参数 | 类型 | 默认值 | 说明 |\n| --- | --- | --- | --- |\n| `model` | `ContextMenuItem[]` | — | 菜单项,可嵌套 `items`;项可含 `key` / `icon`。 |\n| `modelValue` | `boolean` | `false` | 是否可见。 |\n| `position` | `{ x: number; y: number }` | — | 菜单坐标。 |\n| `teleport` | `boolean` | `true` | 浮层 Teleport;默认挂到 `body`。 |\n| `appendTo` | `string \\| HTMLElement \\| 'self' \\| false` | `'body'` | 挂载目标;`'self'` / `false` 就地渲染。 |\n| `pt` | [RootPassThrough](/docs/types#RootPassThrough) `{ root? }` | — | DOM 透传,见 [样式与 attrs](/docs/attrs). |\n\n\n## Events\n\n| 事件名 | 参数 | 说明 |\n| --- | --- | --- |\n| `update:modelValue` | `boolean` | 可见性变化。 |\n| `update:position` | `{ x; y }` | 位置变化。 |\n\n## Methods\n\n| 方法 | 说明 |\n| --- | --- |\n| `show(event)` | 根据鼠标事件或坐标显示。 |\n| `hide()` | 隐藏菜单。 |\n\n`useContextMenu()` 返回 `{ visible, position, show, hide }`,便于命令式打开。\n\n## Slots\n\n| 插槽名 | 说明 |\n| --- | --- |\n| `default` | 触发元素(非 popup 模式)。 |\n\n## 类型\n\n<h4 id=\"ContextMenuItem\">ContextMenuItem</h4>\n\n完整定义见源码 `types.ts`。\n\n```ts\ninterface ContextMenuItem extends MenuNodeBase {\n items?: ContextMenuItem[]\n}\n```\n"
4430
4435
  },
4431
4436
  "en-US": {
4432
4437
  "title": "ContextMenu",
@@ -4452,6 +4457,11 @@
4452
4457
  "title": "Nested + useContextMenu",
4453
4458
  "body": "```vue preview\n<script setup lang=\"ts\">\nimport { useContextMenu, MContextMenu } from 'morya-ui'\n\nconst menu = useContextMenu()\nconst model = [\n { label: 'Copy', command: () => undefined },\n { label: 'More', items: [{ label: 'Nested' }] },\n]\n</script>\n\n<template>\n <div\n style=\"border: 1px dashed var(--m-color-border); padding: 2rem; border-radius: var(--m-radius-md)\"\n @contextmenu=\"menu.show\"\n >\n Right-click here (composable)\n </div>\n <MContextMenu v-model=\"menu.visible\" v-model:position=\"menu.position\" :model=\"model\" />\n</template>\n```"
4454
4459
  },
4460
+ {
4461
+ "id": "scrolling-nested-flyouts",
4462
+ "title": "Scrolling & nested flyouts",
4463
+ "body": "The menu list and nested `items` flyouts use built-in `MScrollbar` (`max-height: min(18rem, 45vh)`). Nested panels teleport to `body` and anchor to the parent item so side flyouts are not clipped when the root menu scrolls."
4464
+ },
4455
4465
  {
4456
4466
  "id": "props",
4457
4467
  "title": "Props",
@@ -4478,7 +4488,7 @@
4478
4488
  "body": "<h4 id=\"ContextMenuItem\">ContextMenuItem</h4>\n\nSee source `types.ts` for the full definition.\n\n```ts\ninterface ContextMenuItem extends MenuNodeBase {\n items?: ContextMenuItem[]\n}\n```"
4479
4489
  }
4480
4490
  ],
4481
- "markdown": "---\ntitle: ContextMenu\ncategory: 04 / NAVIGATION\ndescription: Right-click context menu with show(event) / hide().\n---\n\n# ContextMenu\n\nContext menu that opens at the pointer position. Nested `items` are supported. `useContextMenu()` can bind `v-model` / `v-model:position`.\n\n## Import\n\n```ts\nimport { useContextMenu, MContextMenu } from 'morya-ui'\n```\n\n## Basic\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MContextMenu } from 'morya-ui'\n\nconst model = [\n { label: 'Copy' },\n { label: 'Paste' },\n { separator: true },\n { label: 'Delete', disabled: true },\n]\n</script>\n\n<template>\n <MContextMenu :model=\"model\">\n <div\n style=\"border: 1px dashed var(--m-color-border); padding: 2rem; border-radius: var(--m-radius-md)\"\n >\n Right-click here to open the menu\n </div>\n </MContextMenu>\n</template>\n```\n\n## Nested + useContextMenu\n\n```vue preview\n<script setup lang=\"ts\">\nimport { useContextMenu, MContextMenu } from 'morya-ui'\n\nconst menu = useContextMenu()\nconst model = [\n { label: 'Copy', command: () => undefined },\n { label: 'More', items: [{ label: 'Nested' }] },\n]\n</script>\n\n<template>\n <div\n style=\"border: 1px dashed var(--m-color-border); padding: 2rem; border-radius: var(--m-radius-md)\"\n @contextmenu=\"menu.show\"\n >\n Right-click here (composable)\n </div>\n <MContextMenu v-model=\"menu.visible\" v-model:position=\"menu.position\" :model=\"model\" />\n</template>\n```\n\n## Props\n\n| Prop | Type | Default | Description |\n| --- | --- | --- | --- |\n| `model` | `ContextMenuItem[]` | — | Menu items; may nest `items`. Items may include `key` / `icon`. |\n| `modelValue` | `boolean` | `false` | Whether the menu is visible. |\n| `position` | `{ x: number; y: number }` | — | Menu coordinates. |\n| `teleport` | `boolean` | `true` | Overlay Teleport; mounts to `body` by default. |\n| `appendTo` | `string \\| HTMLElement \\| 'self' \\| false` | `'body'` | Mount target; `'self'` / `false` renders in place. |\n| `pt` | [RootPassThrough](/docs/types#RootPassThrough) `{ root? }` | — | Pass-through; see [Styling & attrs](/docs/attrs). |\n\n\n## Events\n\n| Event | Prop | Description |\n| --- | --- | --- |\n| `update:modelValue` | `boolean` | Visibility change. |\n| `update:position` | `{ x; y }` | Position change. |\n\n## Methods\n\n| Method / Property | Description |\n| --- | --- |\n| `show(event)` | Show from a mouse event or coordinates. |\n| `hide()` | Hide the menu. |\n\n`useContextMenu()` returns `{ visible, position, show, hide }` for imperative open.\n\n## Slots\n\n| Slot | Description |\n| --- | --- |\n| `default` | Trigger element (inline mode). |\n\n## Types\n\n<h4 id=\"ContextMenuItem\">ContextMenuItem</h4>\n\nSee source `types.ts` for the full definition.\n\n```ts\ninterface ContextMenuItem extends MenuNodeBase {\n items?: ContextMenuItem[]\n}\n```\n"
4491
+ "markdown": "---\ntitle: ContextMenu\ncategory: 04 / NAVIGATION\ndescription: Right-click context menu with show(event) / hide().\n---\n\n# ContextMenu\n\nContext menu that opens at the pointer position. Nested `items` are supported. `useContextMenu()` can bind `v-model` / `v-model:position`.\n\n## Import\n\n```ts\nimport { useContextMenu, MContextMenu } from 'morya-ui'\n```\n\n## Basic\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MContextMenu } from 'morya-ui'\n\nconst model = [\n { label: 'Copy' },\n { label: 'Paste' },\n { separator: true },\n { label: 'Delete', disabled: true },\n]\n</script>\n\n<template>\n <MContextMenu :model=\"model\">\n <div\n style=\"border: 1px dashed var(--m-color-border); padding: 2rem; border-radius: var(--m-radius-md)\"\n >\n Right-click here to open the menu\n </div>\n </MContextMenu>\n</template>\n```\n\n## Nested + useContextMenu\n\n```vue preview\n<script setup lang=\"ts\">\nimport { useContextMenu, MContextMenu } from 'morya-ui'\n\nconst menu = useContextMenu()\nconst model = [\n { label: 'Copy', command: () => undefined },\n { label: 'More', items: [{ label: 'Nested' }] },\n]\n</script>\n\n<template>\n <div\n style=\"border: 1px dashed var(--m-color-border); padding: 2rem; border-radius: var(--m-radius-md)\"\n @contextmenu=\"menu.show\"\n >\n Right-click here (composable)\n </div>\n <MContextMenu v-model=\"menu.visible\" v-model:position=\"menu.position\" :model=\"model\" />\n</template>\n```\n\n## Scrolling & nested flyouts\n\nThe menu list and nested `items` flyouts use built-in `MScrollbar` (`max-height: min(18rem, 45vh)`). Nested panels teleport to `body` and anchor to the parent item so side flyouts are not clipped when the root menu scrolls.\n\n## Props\n\n| Prop | Type | Default | Description |\n| --- | --- | --- | --- |\n| `model` | `ContextMenuItem[]` | — | Menu items; may nest `items`. Items may include `key` / `icon`. |\n| `modelValue` | `boolean` | `false` | Whether the menu is visible. |\n| `position` | `{ x: number; y: number }` | — | Menu coordinates. |\n| `teleport` | `boolean` | `true` | Overlay Teleport; mounts to `body` by default. |\n| `appendTo` | `string \\| HTMLElement \\| 'self' \\| false` | `'body'` | Mount target; `'self'` / `false` renders in place. |\n| `pt` | [RootPassThrough](/docs/types#RootPassThrough) `{ root? }` | — | Pass-through; see [Styling & attrs](/docs/attrs). |\n\n\n## Events\n\n| Event | Prop | Description |\n| --- | --- | --- |\n| `update:modelValue` | `boolean` | Visibility change. |\n| `update:position` | `{ x; y }` | Position change. |\n\n## Methods\n\n| Method / Property | Description |\n| --- | --- |\n| `show(event)` | Show from a mouse event or coordinates. |\n| `hide()` | Hide the menu. |\n\n`useContextMenu()` returns `{ visible, position, show, hide }` for imperative open.\n\n## Slots\n\n| Slot | Description |\n| --- | --- |\n| `default` | Trigger element (inline mode). |\n\n## Types\n\n<h4 id=\"ContextMenuItem\">ContextMenuItem</h4>\n\nSee source `types.ts` for the full definition.\n\n```ts\ninterface ContextMenuItem extends MenuNodeBase {\n items?: ContextMenuItem[]\n}\n```\n"
4482
4492
  }
4483
4493
  }
4484
4494
  },
@@ -6593,6 +6603,11 @@
6593
6603
  "title": "Nested / group / hover",
6594
6604
  "body": "```vue preview\n<script setup lang=\"ts\">\nimport { MButton, MDropdown } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst open = ref(false)\nconst items = [\n { type: 'group' as const, label: '编辑', items: [{ value: 'cut', label: '剪切' }, { value: 'copy', label: '复制' }] },\n { separator: true },\n { value: 'more', label: '更多', items: [{ value: 'deep', label: '深层操作' }] },\n]\n</script>\n\n<template>\n <MDropdown v-model=\"open\" :items=\"items\" trigger=\"hover\" :show-delay=\"0\" :hide-delay=\"200\">\n <template #trigger>\n <MButton label=\"悬停打开\" severity=\"secondary\" />\n </template>\n </MDropdown>\n</template>\n```"
6595
6605
  },
6606
+ {
6607
+ "id": "滚动与嵌套子菜单",
6608
+ "title": "滚动与嵌套子菜单",
6609
+ "body": "主菜单与嵌套 `items` 的侧向飞出层均内置 `MScrollbar`,高度上限为 `min(18rem, 45vh)`。嵌套子菜单会 **额外 Teleport 到 `body`** 并相对父项定位,避免主菜单滚动时裁剪飞出层;hover 移入飞出层时有短暂延迟关闭。"
6610
+ },
6596
6611
  {
6597
6612
  "id": "props",
6598
6613
  "title": "Props",
@@ -6614,7 +6629,7 @@
6614
6629
  "body": "<h4 id=\"DropdownItem\">DropdownItem</h4>\n\n完整定义见源码 `types.ts`。\n\n```ts\ninterface DropdownItem extends MenuNodeBase {\n type?: DropdownItemType\n items?: DropdownItem[]\n}\n```"
6615
6630
  }
6616
6631
  ],
6617
- "markdown": "---\ntitle: Dropdown\ncategory: 04 / NAVIGATION\ndescription: 动作菜单覆盖层(非表单选择器)。与 Select 不同:用于触发编辑、删除等操作项。\n---\n\n# Dropdown\n\n动作菜单(action menu overlay)。用于从触发器打开一组操作项。\n\n**与 Select 的区别:** `MDropdown` 是菜单覆盖层;表单选项选择请使用 `MSelect`。\n\n支持分组(`type: 'group'`)、分割线(`separator` / `type: 'divider'`)、嵌套 `items`,以及 `trigger: 'hover'` + `showDelay` / `hideDelay`。键盘高亮仍只覆盖顶层叶子项。\n\n## 引入\n\n```ts\nimport { MButton, MDropdown } from 'morya-ui'\n```\n\n## 基础用法\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MButton, MDropdown } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst open = ref(false)\nconst items = [\n { value: 'edit', label: 'Edit' },\n { value: 'duplicate', label: 'Duplicate' },\n { value: 'delete', label: 'Delete', disabled: true },\n]\n\nfunction onSelect(item: { value: string; label: string }) {\n open.value = false\n console.log(item.value)\n}\n</script>\n\n<template>\n <MDropdown v-model=\"open\" :items=\"items\" @select=\"onSelect\">\n <template #trigger>\n <MButton label=\"Actions\" icon=\"chevron-down\" icon-pos=\"right\" severity=\"secondary\" />\n </template>\n </MDropdown>\n</template>\n```\n\n## Nested / group / hover\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MButton, MDropdown } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst open = ref(false)\nconst items = [\n { type: 'group' as const, label: '编辑', items: [{ value: 'cut', label: '剪切' }, { value: 'copy', label: '复制' }] },\n { separator: true },\n { value: 'more', label: '更多', items: [{ value: 'deep', label: '深层操作' }] },\n]\n</script>\n\n<template>\n <MDropdown v-model=\"open\" :items=\"items\" trigger=\"hover\" :show-delay=\"0\" :hide-delay=\"200\">\n <template #trigger>\n <MButton label=\"悬停打开\" severity=\"secondary\" />\n </template>\n </MDropdown>\n</template>\n```\n\n## Props\n\n| 参数 | 类型 | 默认值 | 说明 |\n| --- | --- | --- | --- |\n| `modelValue` | `boolean` | `false` | 菜单是否打开。 |\n| `items` | `DropdownItem[]` | — | 菜单项;可含 `type` / `separator` / `items` / `icon` / `command`。 |\n| `placement` | `'bottom-start' \\| 'bottom-end'` | `'bottom-start'` | 对齐方式。 |\n| `closeOnSelect` | `boolean` | `true` | 选择后关闭。 |\n| `trigger` | `'click' \\| 'hover'` | `'click'` | 打开方式。 |\n| `showDelay` / `hideDelay` | `number` | `0` / `200` | hover 延迟(ms)。 |\n| `teleport` | `boolean` | `true` | 将菜单 Teleport 出去;默认挂到 `body`。 |\n| `appendTo` | `string \\| HTMLElement \\| 'self' \\| false` | `'body'` | 挂载目标;`'self'` / `false` 表示就地渲染。 |\n| `pt` | [RootPassThrough](/docs/types#RootPassThrough) `{ root? }` | — | DOM 透传,见 [样式与 attrs](/docs/attrs). |\n\n\n## Events\n\n| 事件名 | 参数 | 说明 |\n| --- | --- | --- |\n| `update:modelValue` | `boolean` | 打开状态变化。 |\n| `select` | `DropdownItem` | 选中启用项时触发。 |\n\n## Slots\n\n| 插槽名 | 说明 |\n| --- | --- |\n| `trigger` | 触发器。 |\n| `item` | 自定义菜单项,作用域 `{ item }`。 |\n\n## 类型\n\n<h4 id=\"DropdownItem\">DropdownItem</h4>\n\n完整定义见源码 `types.ts`。\n\n```ts\ninterface DropdownItem extends MenuNodeBase {\r\n type?: DropdownItemType\r\n items?: DropdownItem[]\r\n}\n```\n"
6632
+ "markdown": "---\ntitle: Dropdown\ncategory: 04 / NAVIGATION\ndescription: 动作菜单覆盖层(非表单选择器)。与 Select 不同:用于触发编辑、删除等操作项。\n---\n\n# Dropdown\n\n动作菜单(action menu overlay)。用于从触发器打开一组操作项。\n\n**与 Select 的区别:** `MDropdown` 是菜单覆盖层;表单选项选择请使用 `MSelect`。\n\n支持分组(`type: 'group'`)、分割线(`separator` / `type: 'divider'`)、嵌套 `items`,以及 `trigger: 'hover'` + `showDelay` / `hideDelay`。键盘高亮仍只覆盖顶层叶子项。\n\n## 引入\n\n```ts\nimport { MButton, MDropdown } from 'morya-ui'\n```\n\n## 基础用法\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MButton, MDropdown } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst open = ref(false)\nconst items = [\n { value: 'edit', label: 'Edit' },\n { value: 'duplicate', label: 'Duplicate' },\n { value: 'delete', label: 'Delete', disabled: true },\n]\n\nfunction onSelect(item: { value: string; label: string }) {\n open.value = false\n console.log(item.value)\n}\n</script>\n\n<template>\n <MDropdown v-model=\"open\" :items=\"items\" @select=\"onSelect\">\n <template #trigger>\n <MButton label=\"Actions\" icon=\"chevron-down\" icon-pos=\"right\" severity=\"secondary\" />\n </template>\n </MDropdown>\n</template>\n```\n\n## Nested / group / hover\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MButton, MDropdown } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst open = ref(false)\nconst items = [\n { type: 'group' as const, label: '编辑', items: [{ value: 'cut', label: '剪切' }, { value: 'copy', label: '复制' }] },\n { separator: true },\n { value: 'more', label: '更多', items: [{ value: 'deep', label: '深层操作' }] },\n]\n</script>\n\n<template>\n <MDropdown v-model=\"open\" :items=\"items\" trigger=\"hover\" :show-delay=\"0\" :hide-delay=\"200\">\n <template #trigger>\n <MButton label=\"悬停打开\" severity=\"secondary\" />\n </template>\n </MDropdown>\n</template>\n```\n\n## 滚动与嵌套子菜单\n\n主菜单与嵌套 `items` 的侧向飞出层均内置 `MScrollbar`,高度上限为 `min(18rem, 45vh)`。嵌套子菜单会 **额外 Teleport 到 `body`** 并相对父项定位,避免主菜单滚动时裁剪飞出层;hover 移入飞出层时有短暂延迟关闭。\n\n## Props\n\n| 参数 | 类型 | 默认值 | 说明 |\n| --- | --- | --- | --- |\n| `modelValue` | `boolean` | `false` | 菜单是否打开。 |\n| `items` | `DropdownItem[]` | — | 菜单项;可含 `type` / `separator` / `items` / `icon` / `command`。 |\n| `placement` | `'bottom-start' \\| 'bottom-end'` | `'bottom-start'` | 对齐方式。 |\n| `closeOnSelect` | `boolean` | `true` | 选择后关闭。 |\n| `trigger` | `'click' \\| 'hover'` | `'click'` | 打开方式。 |\n| `showDelay` / `hideDelay` | `number` | `0` / `200` | hover 延迟(ms)。 |\n| `teleport` | `boolean` | `true` | 将菜单 Teleport 出去;默认挂到 `body`。 |\n| `appendTo` | `string \\| HTMLElement \\| 'self' \\| false` | `'body'` | 挂载目标;`'self'` / `false` 表示就地渲染。 |\n| `pt` | [RootPassThrough](/docs/types#RootPassThrough) `{ root? }` | — | DOM 透传,见 [样式与 attrs](/docs/attrs). |\n\n\n## Events\n\n| 事件名 | 参数 | 说明 |\n| --- | --- | --- |\n| `update:modelValue` | `boolean` | 打开状态变化。 |\n| `select` | `DropdownItem` | 选中启用项时触发。 |\n\n## Slots\n\n| 插槽名 | 说明 |\n| --- | --- |\n| `trigger` | 触发器。 |\n| `item` | 自定义菜单项,作用域 `{ item }`。 |\n\n## 类型\n\n<h4 id=\"DropdownItem\">DropdownItem</h4>\n\n完整定义见源码 `types.ts`。\n\n```ts\ninterface DropdownItem extends MenuNodeBase {\n type?: DropdownItemType\n items?: DropdownItem[]\n}\n```\n"
6618
6633
  },
6619
6634
  "en-US": {
6620
6635
  "title": "Dropdown",
@@ -6640,6 +6655,11 @@
6640
6655
  "title": "Nested / group / hover",
6641
6656
  "body": "```vue preview\n<script setup lang=\"ts\">\nimport { MButton, MDropdown } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst open = ref(false)\nconst items = [\n { type: 'group' as const, label: 'Edit', items: [{ value: 'cut', label: 'Cut' }, { value: 'copy', label: 'Copy' }] },\n { separator: true },\n { value: 'more', label: 'More', items: [{ value: 'deep', label: 'Deep action' }] },\n]\n</script>\n\n<template>\n <MDropdown v-model=\"open\" :items=\"items\" trigger=\"hover\" :show-delay=\"0\" :hide-delay=\"200\">\n <template #trigger>\n <MButton label=\"Hover to open\" severity=\"secondary\" />\n </template>\n </MDropdown>\n</template>\n```"
6642
6657
  },
6658
+ {
6659
+ "id": "scrolling-nested-flyouts",
6660
+ "title": "Scrolling & nested flyouts",
6661
+ "body": "Both the root menu and nested `items` flyouts use built-in `MScrollbar` with `max-height: min(18rem, 45vh)`. Nested submenus are **also teleported to `body`** and positioned against the parent item so they are not clipped when the root menu scrolls; a short hover delay keeps the flyout open while moving the pointer into it."
6662
+ },
6643
6663
  {
6644
6664
  "id": "props",
6645
6665
  "title": "Props",
@@ -6661,7 +6681,7 @@
6661
6681
  "body": "<h4 id=\"DropdownItem\">DropdownItem</h4>\n\nSee source `types.ts` for the full definition.\n\n```ts\ninterface DropdownItem extends MenuNodeBase {\n type?: DropdownItemType\n items?: DropdownItem[]\n}\n```"
6662
6682
  }
6663
6683
  ],
6664
- "markdown": "---\ntitle: Dropdown\ncategory: 04 / NAVIGATION\ndescription: Action menu overlay (not a form select). Unlike Select, it is used to trigger actions such as edit and delete.\n---\n\n# Dropdown\n\nAction menu overlay. Opens a set of actions from a trigger.\n\n**Difference from Select:** `MDropdown` is a menu overlay; for form option selection, use `MSelect`.\n\nSupports groups (`type: 'group'`), dividers (`separator` / `type: 'divider'`), nested `items`, and `trigger: 'hover'` with `showDelay` / `hideDelay`. Keyboard highlight still covers top-level leaves only.\n\n## Import\n\n```ts\nimport { MButton, MDropdown } from 'morya-ui'\n```\n\n## Basic\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MButton, MDropdown } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst open = ref(false)\nconst items = [\n { value: 'edit', label: 'Edit' },\n { value: 'duplicate', label: 'Duplicate' },\n { value: 'delete', label: 'Delete', disabled: true },\n]\n\nfunction onSelect(item: { value: string; label: string }) {\n open.value = false\n console.log(item.value)\n}\n</script>\n\n<template>\n <MDropdown v-model=\"open\" :items=\"items\" @select=\"onSelect\">\n <template #trigger>\n <MButton label=\"Actions\" icon=\"chevron-down\" icon-pos=\"right\" severity=\"secondary\" />\n </template>\n </MDropdown>\n</template>\n```\n\n## Nested / group / hover\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MButton, MDropdown } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst open = ref(false)\nconst items = [\n { type: 'group' as const, label: 'Edit', items: [{ value: 'cut', label: 'Cut' }, { value: 'copy', label: 'Copy' }] },\n { separator: true },\n { value: 'more', label: 'More', items: [{ value: 'deep', label: 'Deep action' }] },\n]\n</script>\n\n<template>\n <MDropdown v-model=\"open\" :items=\"items\" trigger=\"hover\" :show-delay=\"0\" :hide-delay=\"200\">\n <template #trigger>\n <MButton label=\"Hover to open\" severity=\"secondary\" />\n </template>\n </MDropdown>\n</template>\n```\n\n## Props\n\n| Prop | Type | Default | Description |\n| --- | --- | --- | --- |\n| `modelValue` | `boolean` | `false` | Whether the menu is open. |\n| `items` | `DropdownItem[]` | — | Menu items; may include `type` / `separator` / `items` / `icon` / `command`. |\n| `placement` | `'bottom-start' \\| 'bottom-end'` | `'bottom-start'` | Alignment. |\n| `closeOnSelect` | `boolean` | `true` | Close after selection. |\n| `trigger` | `'click' \\| 'hover'` | `'click'` | How the menu opens. |\n| `showDelay` / `hideDelay` | `number` | `0` / `200` | Hover delays in ms. |\n| `teleport` | `boolean` | `true` | Teleport the menu; mounts to `body` by default. |\n| `appendTo` | `string \\| HTMLElement \\| 'self' \\| false` | `'body'` | Mount target; `'self'` / `false` renders in place. |\n| `pt` | [RootPassThrough](/docs/types#RootPassThrough) `{ root? }` | — | Pass-through; see [Styling & attrs](/docs/attrs). |\n\n\n## Events\n\n| Event | Prop | Description |\n| --- | --- | --- |\n| `update:modelValue` | `boolean` | Open state change. |\n| `select` | `DropdownItem` | Emitted when an enabled item is selected. |\n\n## Slots\n\n| Slot | Description |\n| --- | --- |\n| `trigger` | Trigger. |\n| `item` | Custom menu item, scope `{ item }`. |\n\n## Types\n\n<h4 id=\"DropdownItem\">DropdownItem</h4>\n\nSee source `types.ts` for the full definition.\n\n```ts\ninterface DropdownItem extends MenuNodeBase {\r\n type?: DropdownItemType\r\n items?: DropdownItem[]\r\n}\n```\n"
6684
+ "markdown": "---\ntitle: Dropdown\ncategory: 04 / NAVIGATION\ndescription: Action menu overlay (not a form select). Unlike Select, it is used to trigger actions such as edit and delete.\n---\n\n# Dropdown\n\nAction menu overlay. Opens a set of actions from a trigger.\n\n**Difference from Select:** `MDropdown` is a menu overlay; for form option selection, use `MSelect`.\n\nSupports groups (`type: 'group'`), dividers (`separator` / `type: 'divider'`), nested `items`, and `trigger: 'hover'` with `showDelay` / `hideDelay`. Keyboard highlight still covers top-level leaves only.\n\n## Import\n\n```ts\nimport { MButton, MDropdown } from 'morya-ui'\n```\n\n## Basic\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MButton, MDropdown } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst open = ref(false)\nconst items = [\n { value: 'edit', label: 'Edit' },\n { value: 'duplicate', label: 'Duplicate' },\n { value: 'delete', label: 'Delete', disabled: true },\n]\n\nfunction onSelect(item: { value: string; label: string }) {\n open.value = false\n console.log(item.value)\n}\n</script>\n\n<template>\n <MDropdown v-model=\"open\" :items=\"items\" @select=\"onSelect\">\n <template #trigger>\n <MButton label=\"Actions\" icon=\"chevron-down\" icon-pos=\"right\" severity=\"secondary\" />\n </template>\n </MDropdown>\n</template>\n```\n\n## Nested / group / hover\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MButton, MDropdown } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst open = ref(false)\nconst items = [\n { type: 'group' as const, label: 'Edit', items: [{ value: 'cut', label: 'Cut' }, { value: 'copy', label: 'Copy' }] },\n { separator: true },\n { value: 'more', label: 'More', items: [{ value: 'deep', label: 'Deep action' }] },\n]\n</script>\n\n<template>\n <MDropdown v-model=\"open\" :items=\"items\" trigger=\"hover\" :show-delay=\"0\" :hide-delay=\"200\">\n <template #trigger>\n <MButton label=\"Hover to open\" severity=\"secondary\" />\n </template>\n </MDropdown>\n</template>\n```\n\n## Scrolling & nested flyouts\n\nBoth the root menu and nested `items` flyouts use built-in `MScrollbar` with `max-height: min(18rem, 45vh)`. Nested submenus are **also teleported to `body`** and positioned against the parent item so they are not clipped when the root menu scrolls; a short hover delay keeps the flyout open while moving the pointer into it.\n\n## Props\n\n| Prop | Type | Default | Description |\n| --- | --- | --- | --- |\n| `modelValue` | `boolean` | `false` | Whether the menu is open. |\n| `items` | `DropdownItem[]` | — | Menu items; may include `type` / `separator` / `items` / `icon` / `command`. |\n| `placement` | `'bottom-start' \\| 'bottom-end'` | `'bottom-start'` | Alignment. |\n| `closeOnSelect` | `boolean` | `true` | Close after selection. |\n| `trigger` | `'click' \\| 'hover'` | `'click'` | How the menu opens. |\n| `showDelay` / `hideDelay` | `number` | `0` / `200` | Hover delays in ms. |\n| `teleport` | `boolean` | `true` | Teleport the menu; mounts to `body` by default. |\n| `appendTo` | `string \\| HTMLElement \\| 'self' \\| false` | `'body'` | Mount target; `'self'` / `false` renders in place. |\n| `pt` | [RootPassThrough](/docs/types#RootPassThrough) `{ root? }` | — | Pass-through; see [Styling & attrs](/docs/attrs). |\n\n\n## Events\n\n| Event | Prop | Description |\n| --- | --- | --- |\n| `update:modelValue` | `boolean` | Open state change. |\n| `select` | `DropdownItem` | Emitted when an enabled item is selected. |\n\n## Slots\n\n| Slot | Description |\n| --- | --- |\n| `trigger` | Trigger. |\n| `item` | Custom menu item, scope `{ item }`. |\n\n## Types\n\n<h4 id=\"DropdownItem\">DropdownItem</h4>\n\nSee source `types.ts` for the full definition.\n\n```ts\ninterface DropdownItem extends MenuNodeBase {\n type?: DropdownItemType\n items?: DropdownItem[]\n}\n```\n"
6665
6685
  }
6666
6686
  }
6667
6687
  },
@@ -11728,7 +11748,7 @@
11728
11748
  {
11729
11749
  "id": "scrollable-content",
11730
11750
  "title": "Scrollable Content",
11731
- "body": "内容超出时仅 Content 区域滚动,Header / Sider 保持固定。\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MLayout,\n MLayoutContent,\n MLayoutHeader,\n MLayoutSider,\n} from 'morya-ui'\n</script>\n\n<template>\n <MLayout style=\"height:14rem;border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden\">\n <MLayoutHeader bordered style=\"padding:0.75rem 1rem\">\n Scroll demo\n </MLayoutHeader>\n <MLayout has-sider>\n <MLayoutSider bordered :width=\"120\" content-style=\"padding:0.75rem\">\n 固定侧栏\n </MLayoutSider>\n <MLayoutContent embedded content-style=\"padding:1rem\">\n <div style=\"display:grid;gap:0.5rem\">\n <div v-for=\"n in 20\" :key=\"n\">\n 行 {{ n }} — 向下滚动\n </div>\n </div>\n </MLayoutContent>\n </MLayout>\n </MLayout>\n</template>\n```"
11751
+ "body": "内容超出时仅 Content 区域滚动,Header / Sider 保持固定。`MLayout` / `MLayoutContent` / `MLayoutSider` 通过内置 `MScrollbar` 提供统一滚动条。\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MLayout,\n MLayoutContent,\n MLayoutHeader,\n MLayoutSider,\n} from 'morya-ui'\n</script>\n\n<template>\n <MLayout style=\"height:14rem;border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden\">\n <MLayoutHeader bordered style=\"padding:0.75rem 1rem\">\n Scroll demo\n </MLayoutHeader>\n <MLayout has-sider>\n <MLayoutSider bordered :width=\"120\" content-style=\"padding:0.75rem\">\n 固定侧栏\n </MLayoutSider>\n <MLayoutContent embedded content-style=\"padding:1rem\">\n <div style=\"display:grid;gap:0.5rem\">\n <div v-for=\"n in 20\" :key=\"n\">\n 行 {{ n }} — 向下滚动\n </div>\n </div>\n </MLayoutContent>\n </MLayout>\n </MLayout>\n</template>\n```"
11732
11752
  },
11733
11753
  {
11734
11754
  "id": "absolute-shell",
@@ -11743,7 +11763,7 @@
11743
11763
  {
11744
11764
  "id": "layoutsider-props",
11745
11765
  "title": "LayoutSider Props",
11746
- "body": "| 参数 | 类型 | 默认值 | 说明 |\n| --- | --- | --- | --- |\n| `width` | `number \\| string` | `272` | 展开宽度(始终写在 `width` 上)。 |\n| `collapsedWidth` | `number` | `48` | 折叠时的 `max-width`。 |\n| `collapsed` | `boolean` | — | 折叠状态,支持 `v-model:collapsed`。 |\n| `defaultCollapsed` | `boolean` | `false` | 非受控初始折叠。 |\n| `showTrigger` | `boolean \\| 'bar' \\| 'arrow-circle' \\| 'arrow'` | `false` | 折叠触发器;`arrow` 等同 `arrow-circle`。 |\n| `collapseMode` | `'width' \\| 'transform'` | `'transform'` | `transform` 裁切内容;`width` 随侧栏收缩。 |\n| `showCollapsedContent` | `boolean` | `true` | 折叠后是否仍显示侧栏内容。 |\n| `bordered` / `inverted` | `boolean` | `false` | 边框 / 反色。 |\n| `triggerClass` / `triggerStyle` | — | — | 展开态触发器样式。 |\n| `collapsedTriggerClass` / `collapsedTriggerStyle` | — | — | 折叠态触发器样式。 |"
11766
+ "body": "| 参数 | 类型 | 默认值 | 说明 |\n| --- | --- | --- | --- |\n| `width` | `number \\| string` | `272` | 展开宽度(始终写在 `width` 上)。 |\n| `collapsedWidth` | `number` | `48` | 折叠时的 `max-width`。 |\n| `collapsed` | `boolean` | — | 折叠状态,支持 `v-model:collapsed`。 |\n| `defaultCollapsed` | `boolean` | `false` | 非受控初始折叠。 |\n| `showTrigger` | `boolean \\| 'bar' \\| 'arrow-circle' \\| 'arrow'` | `false` | 折叠触发器;`arrow` 等同 `arrow-circle`。 |\n| `collapseMode` | `'width' \\| 'transform'` | `'transform'` | `transform` 裁切内容;`width` 随侧栏收缩。 |\n| `showCollapsedContent` | `boolean` | `true` | 折叠后是否仍显示侧栏内容。 |\n| `bordered` / `inverted` | `boolean` | `false` | 边框 / 反色。 |\n| `triggerClass` / `triggerStyle` | — | — | 展开态触发器样式。 |\n| `collapsedTriggerClass` / `collapsedTriggerStyle` | — | — | 折叠态触发器样式。 |\n| `contentClass` / `contentStyle` | — | — | 滚动容器 class / style。 |"
11747
11767
  },
11748
11768
  {
11749
11769
  "id": "events",
@@ -11766,7 +11786,7 @@
11766
11786
  "body": "| 插槽名 | 说明 |\n| --- | --- |\n| `default` | 布局区域。 |"
11767
11787
  }
11768
11788
  ],
11769
- "markdown": "---\ntitle: Layout\ncategory: 06 / LAYOUT\ndescription: 页面级布局骨架,含 Header / Sider / Content / Footer。\n---\n\n# Layout\n\n页面级布局容器。侧栏场景需在对应 `MLayout` 上设置 `has-sider`。给根布局固定高度(或 `min-height`)后,`MLayoutContent` 会自动撑满剩余空间。\n\n## 引入\n\n```ts\nimport {\n MLayout,\n MLayoutContent,\n MLayoutFooter,\n MLayoutHeader,\n MLayoutSider,\n} from 'morya-ui'\n```\n\n## 基础用法\n\nHeader / Content / Footer。Content 会占满中间剩余高度。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MLayout, MLayoutContent, MLayoutFooter, MLayoutHeader } from 'morya-ui'\n</script>\n\n<template>\n <MLayout style=\"height:16rem;border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden\">\n <MLayoutHeader bordered style=\"padding:0.75rem 1rem\">\n Header\n </MLayoutHeader>\n <MLayoutContent embedded content-style=\"padding:1rem;display:flex;align-items:center;justify-content:center\">\n Content(自动撑开)\n </MLayoutContent>\n <MLayoutFooter bordered style=\"padding:0.75rem 1rem\">\n Footer\n </MLayoutFooter>\n </MLayout>\n</template>\n```\n\n## With Sider\n\n顶栏 + 左侧栏 + 主内容。内层 `has-sider` 的 Layout 会吃掉 Header 以下的全部高度。\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MLayout,\n MLayoutContent,\n MLayoutHeader,\n MLayoutSider,\n} from 'morya-ui'\nimport { ref } from 'vue'\n\nconst collapsed = ref(false)\n</script>\n\n<template>\n <MLayout style=\"height:16rem;border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden\">\n <MLayoutHeader bordered style=\"padding:0.75rem 1rem;display:flex;align-items:center;justify-content:space-between\">\n <strong>App</strong>\n <span style=\"color:var(--m-color-text-muted);font-size:0.75rem\">{{ collapsed ? '已折叠' : '已展开' }}</span>\n </MLayoutHeader>\n <MLayout has-sider>\n <MLayoutSider\n v-model:collapsed=\"collapsed\"\n bordered\n show-trigger=\"arrow-circle\"\n :width=\"160\"\n content-style=\"padding:0.75rem\"\n >\n <div style=\"display:grid;gap:0.5rem\">\n <div>概览</div>\n <div>项目</div>\n <div>设置</div>\n </div>\n </MLayoutSider>\n <MLayoutContent embedded content-style=\"padding:1rem\">\n 主内容区会横向、纵向同时撑满。\n </MLayoutContent>\n </MLayout>\n </MLayout>\n</template>\n```\n\n## Right Sider\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MLayout,\n MLayoutContent,\n MLayoutHeader,\n MLayoutSider,\n} from 'morya-ui'\n</script>\n\n<template>\n <MLayout style=\"height:14rem;border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden\">\n <MLayoutHeader bordered style=\"padding:0.75rem 1rem\">\n Inspector\n </MLayoutHeader>\n <MLayout has-sider sider-placement=\"right\">\n <MLayoutSider bordered :width=\"140\" content-style=\"padding:0.75rem\">\n 属性面板\n </MLayoutSider>\n <MLayoutContent embedded content-style=\"padding:1rem\">\n 画布 / 主区域\n </MLayoutContent>\n </MLayout>\n </MLayout>\n</template>\n```\n\n## Full Shell\n\n完整后台骨架:顶栏 + 侧栏 + 内容 + 底栏。\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MButton,\n MLayout,\n MLayoutContent,\n MLayoutFooter,\n MLayoutHeader,\n MLayoutSider,\n MTag,\n} from 'morya-ui'\nimport { ref } from 'vue'\n\nconst collapsed = ref(false)\n</script>\n\n<template>\n <MLayout style=\"height:18rem;border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden\">\n <MLayoutHeader\n bordered\n inverted\n style=\"padding:0.65rem 1rem;display:flex;align-items:center;gap:0.75rem\"\n >\n <strong>Morya UI</strong>\n <MTag value=\"Studio\" />\n <span style=\"flex:1\" />\n <MButton size=\"small\" label=\"发布\" />\n </MLayoutHeader>\n\n <MLayout has-sider>\n <MLayoutSider\n v-model:collapsed=\"collapsed\"\n bordered\n inverted\n show-trigger=\"bar\"\n :width=\"168\"\n :collapsed-width=\"56\"\n content-style=\"padding:0.75rem\"\n >\n <div style=\"display:grid;gap:0.65rem;font-size:0.875rem\">\n <div>仪表盘</div>\n <div>数据源</div>\n <div>组件</div>\n <div>主题</div>\n </div>\n </MLayoutSider>\n\n <MLayout>\n <MLayoutContent embedded content-style=\"padding:1rem;display:grid;gap:0.75rem;align-content:start\">\n <strong>工作区</strong>\n <p style=\"margin:0;color:var(--m-color-text-muted);font-size:0.875rem\">\n Content 已撑满 Header 与 Footer 之间的空间;侧栏折叠不影响主区高度。\n </p>\n </MLayoutContent>\n <MLayoutFooter bordered style=\"padding:0.5rem 1rem;color:var(--m-color-text-muted);font-size:0.75rem\">\n Ready · local\n </MLayoutFooter>\n </MLayout>\n </MLayout>\n </MLayout>\n</template>\n```\n\n## Embedded Content\n\n`embedded` 给内容区柔和背景,便于和顶栏/侧栏区分。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MLayout, MLayoutContent, MLayoutHeader } from 'morya-ui'\n</script>\n\n<template>\n <MLayout style=\"height:12rem;border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden\">\n <MLayoutHeader bordered style=\"padding:0.75rem 1rem\">\n Settings\n </MLayoutHeader>\n <MLayoutContent embedded content-style=\"padding:1rem\">\n 嵌套表单 / 列表放在这里。\n </MLayoutContent>\n </MLayout>\n</template>\n```\n\n## Scrollable Content\n\n内容超出时仅 Content 区域滚动,Header / Sider 保持固定。\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MLayout,\n MLayoutContent,\n MLayoutHeader,\n MLayoutSider,\n} from 'morya-ui'\n</script>\n\n<template>\n <MLayout style=\"height:14rem;border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden\">\n <MLayoutHeader bordered style=\"padding:0.75rem 1rem\">\n Scroll demo\n </MLayoutHeader>\n <MLayout has-sider>\n <MLayoutSider bordered :width=\"120\" content-style=\"padding:0.75rem\">\n 固定侧栏\n </MLayoutSider>\n <MLayoutContent embedded content-style=\"padding:1rem\">\n <div style=\"display:grid;gap:0.5rem\">\n <div v-for=\"n in 20\" :key=\"n\">\n 行 {{ n }} — 向下滚动\n </div>\n </div>\n </MLayoutContent>\n </MLayout>\n </MLayout>\n</template>\n```\n\n## Absolute Shell\n\n根布局 `position=\"absolute\"` 铺满父级(父级需 `position: relative` + 明确高度)。\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MLayout,\n MLayoutContent,\n MLayoutHeader,\n MLayoutSider,\n} from 'morya-ui'\n</script>\n\n<template>\n <div style=\"position:relative;height:14rem;border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden\">\n <MLayout position=\"absolute\" has-sider>\n <MLayoutSider bordered :width=\"120\" content-style=\"padding:0.75rem\">\n Nav\n </MLayoutSider>\n <MLayout>\n <MLayoutHeader bordered style=\"padding:0.75rem 1rem\">\n Absolute layout\n </MLayoutHeader>\n <MLayoutContent embedded content-style=\"padding:1rem\">\n 填满相对定位容器\n </MLayoutContent>\n </MLayout>\n </MLayout>\n </div>\n</template>\n```\n\n## Layout Props\n\n| 参数 | 类型 | 默认值 | 说明 |\n| --- | --- | --- | --- |\n| `hasSider` | `boolean` | `false` | 横向容纳 `MLayoutSider`。 |\n| `siderPlacement` | `'left' \\| 'right'` | `'left'` | 侧栏位置。 |\n| `embedded` | `boolean` | `false` | 柔和背景(嵌套内容区)。 |\n| `position` | `'static' \\| 'absolute'` | `'static'` | 定位模式。 |\n| `contentClass` / `contentStyle` | — | — | 滚动容器 class / style。 |\n| `height` | `number \\| string` | — | — |\n| `padding` | `number \\| string` | — | — |\n| `radius` | `number \\| string` | — | — |\n\n## LayoutSider Props\n\n| 参数 | 类型 | 默认值 | 说明 |\n| --- | --- | --- | --- |\n| `width` | `number \\| string` | `272` | 展开宽度(始终写在 `width` 上)。 |\n| `collapsedWidth` | `number` | `48` | 折叠时的 `max-width`。 |\n| `collapsed` | `boolean` | — | 折叠状态,支持 `v-model:collapsed`。 |\n| `defaultCollapsed` | `boolean` | `false` | 非受控初始折叠。 |\n| `showTrigger` | `boolean \\| 'bar' \\| 'arrow-circle' \\| 'arrow'` | `false` | 折叠触发器;`arrow` 等同 `arrow-circle`。 |\n| `collapseMode` | `'width' \\| 'transform'` | `'transform'` | `transform` 裁切内容;`width` 随侧栏收缩。 |\n| `showCollapsedContent` | `boolean` | `true` | 折叠后是否仍显示侧栏内容。 |\n| `bordered` / `inverted` | `boolean` | `false` | 边框 / 反色。 |\n| `triggerClass` / `triggerStyle` | — | — | 展开态触发器样式。 |\n| `collapsedTriggerClass` / `collapsedTriggerStyle` | — | — | 折叠态触发器样式。 |\n\n## Events\n\n| 事件 | 说明 |\n| --- | --- |\n| `scroll` | 滚动容器滚动时触发。 |\n| `after-enter` | 侧栏展开动画结束。 |\n| `after-leave` | 侧栏收起动画结束。 |\n| `collapse` | 侧栏开始收起。 |\n| `expand` | 侧栏开始展开。 |\n| `update:collapsed` | 折叠状态 v-model。 |\n\n## Expose\n\n`MLayout` / `MLayoutContent` / `MLayoutSider` 均暴露 `scrollTo(...)`。\n\n## Components\n\n| 组件 | 说明 |\n| --- | --- |\n| `MLayout` | 根布局。 |\n| `MLayoutHeader` | 顶栏。 |\n| `MLayoutContent` | 主内容区(默认撑满剩余空间)。 |\n| `MLayoutFooter` | 底栏。 |\n| `MLayoutSider` | 侧栏。 |\n\n## Slots\n\n| 插槽名 | 说明 |\n| --- | --- |\n| `default` | 布局区域。 |\n"
11789
+ "markdown": "---\ntitle: Layout\ncategory: 06 / LAYOUT\ndescription: 页面级布局骨架,含 Header / Sider / Content / Footer。\n---\n\n# Layout\n\n页面级布局容器。侧栏场景需在对应 `MLayout` 上设置 `has-sider`。给根布局固定高度(或 `min-height`)后,`MLayoutContent` 会自动撑满剩余空间。\n\n## 引入\n\n```ts\nimport {\n MLayout,\n MLayoutContent,\n MLayoutFooter,\n MLayoutHeader,\n MLayoutSider,\n} from 'morya-ui'\n```\n\n## 基础用法\n\nHeader / Content / Footer。Content 会占满中间剩余高度。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MLayout, MLayoutContent, MLayoutFooter, MLayoutHeader } from 'morya-ui'\n</script>\n\n<template>\n <MLayout style=\"height:16rem;border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden\">\n <MLayoutHeader bordered style=\"padding:0.75rem 1rem\">\n Header\n </MLayoutHeader>\n <MLayoutContent embedded content-style=\"padding:1rem;display:flex;align-items:center;justify-content:center\">\n Content(自动撑开)\n </MLayoutContent>\n <MLayoutFooter bordered style=\"padding:0.75rem 1rem\">\n Footer\n </MLayoutFooter>\n </MLayout>\n</template>\n```\n\n## With Sider\n\n顶栏 + 左侧栏 + 主内容。内层 `has-sider` 的 Layout 会吃掉 Header 以下的全部高度。\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MLayout,\n MLayoutContent,\n MLayoutHeader,\n MLayoutSider,\n} from 'morya-ui'\nimport { ref } from 'vue'\n\nconst collapsed = ref(false)\n</script>\n\n<template>\n <MLayout style=\"height:16rem;border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden\">\n <MLayoutHeader bordered style=\"padding:0.75rem 1rem;display:flex;align-items:center;justify-content:space-between\">\n <strong>App</strong>\n <span style=\"color:var(--m-color-text-muted);font-size:0.75rem\">{{ collapsed ? '已折叠' : '已展开' }}</span>\n </MLayoutHeader>\n <MLayout has-sider>\n <MLayoutSider\n v-model:collapsed=\"collapsed\"\n bordered\n show-trigger=\"arrow-circle\"\n :width=\"160\"\n content-style=\"padding:0.75rem\"\n >\n <div style=\"display:grid;gap:0.5rem\">\n <div>概览</div>\n <div>项目</div>\n <div>设置</div>\n </div>\n </MLayoutSider>\n <MLayoutContent embedded content-style=\"padding:1rem\">\n 主内容区会横向、纵向同时撑满。\n </MLayoutContent>\n </MLayout>\n </MLayout>\n</template>\n```\n\n## Right Sider\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MLayout,\n MLayoutContent,\n MLayoutHeader,\n MLayoutSider,\n} from 'morya-ui'\n</script>\n\n<template>\n <MLayout style=\"height:14rem;border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden\">\n <MLayoutHeader bordered style=\"padding:0.75rem 1rem\">\n Inspector\n </MLayoutHeader>\n <MLayout has-sider sider-placement=\"right\">\n <MLayoutSider bordered :width=\"140\" content-style=\"padding:0.75rem\">\n 属性面板\n </MLayoutSider>\n <MLayoutContent embedded content-style=\"padding:1rem\">\n 画布 / 主区域\n </MLayoutContent>\n </MLayout>\n </MLayout>\n</template>\n```\n\n## Full Shell\n\n完整后台骨架:顶栏 + 侧栏 + 内容 + 底栏。\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MButton,\n MLayout,\n MLayoutContent,\n MLayoutFooter,\n MLayoutHeader,\n MLayoutSider,\n MTag,\n} from 'morya-ui'\nimport { ref } from 'vue'\n\nconst collapsed = ref(false)\n</script>\n\n<template>\n <MLayout style=\"height:18rem;border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden\">\n <MLayoutHeader\n bordered\n inverted\n style=\"padding:0.65rem 1rem;display:flex;align-items:center;gap:0.75rem\"\n >\n <strong>Morya UI</strong>\n <MTag value=\"Studio\" />\n <span style=\"flex:1\" />\n <MButton size=\"small\" label=\"发布\" />\n </MLayoutHeader>\n\n <MLayout has-sider>\n <MLayoutSider\n v-model:collapsed=\"collapsed\"\n bordered\n inverted\n show-trigger=\"bar\"\n :width=\"168\"\n :collapsed-width=\"56\"\n content-style=\"padding:0.75rem\"\n >\n <div style=\"display:grid;gap:0.65rem;font-size:0.875rem\">\n <div>仪表盘</div>\n <div>数据源</div>\n <div>组件</div>\n <div>主题</div>\n </div>\n </MLayoutSider>\n\n <MLayout>\n <MLayoutContent embedded content-style=\"padding:1rem;display:grid;gap:0.75rem;align-content:start\">\n <strong>工作区</strong>\n <p style=\"margin:0;color:var(--m-color-text-muted);font-size:0.875rem\">\n Content 已撑满 Header 与 Footer 之间的空间;侧栏折叠不影响主区高度。\n </p>\n </MLayoutContent>\n <MLayoutFooter bordered style=\"padding:0.5rem 1rem;color:var(--m-color-text-muted);font-size:0.75rem\">\n Ready · local\n </MLayoutFooter>\n </MLayout>\n </MLayout>\n </MLayout>\n</template>\n```\n\n## Embedded Content\n\n`embedded` 给内容区柔和背景,便于和顶栏/侧栏区分。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MLayout, MLayoutContent, MLayoutHeader } from 'morya-ui'\n</script>\n\n<template>\n <MLayout style=\"height:12rem;border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden\">\n <MLayoutHeader bordered style=\"padding:0.75rem 1rem\">\n Settings\n </MLayoutHeader>\n <MLayoutContent embedded content-style=\"padding:1rem\">\n 嵌套表单 / 列表放在这里。\n </MLayoutContent>\n </MLayout>\n</template>\n```\n\n## Scrollable Content\n\n内容超出时仅 Content 区域滚动,Header / Sider 保持固定。`MLayout` / `MLayoutContent` / `MLayoutSider` 通过内置 `MScrollbar` 提供统一滚动条。\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MLayout,\n MLayoutContent,\n MLayoutHeader,\n MLayoutSider,\n} from 'morya-ui'\n</script>\n\n<template>\n <MLayout style=\"height:14rem;border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden\">\n <MLayoutHeader bordered style=\"padding:0.75rem 1rem\">\n Scroll demo\n </MLayoutHeader>\n <MLayout has-sider>\n <MLayoutSider bordered :width=\"120\" content-style=\"padding:0.75rem\">\n 固定侧栏\n </MLayoutSider>\n <MLayoutContent embedded content-style=\"padding:1rem\">\n <div style=\"display:grid;gap:0.5rem\">\n <div v-for=\"n in 20\" :key=\"n\">\n 行 {{ n }} — 向下滚动\n </div>\n </div>\n </MLayoutContent>\n </MLayout>\n </MLayout>\n</template>\n```\n\n## Absolute Shell\n\n根布局 `position=\"absolute\"` 铺满父级(父级需 `position: relative` + 明确高度)。\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MLayout,\n MLayoutContent,\n MLayoutHeader,\n MLayoutSider,\n} from 'morya-ui'\n</script>\n\n<template>\n <div style=\"position:relative;height:14rem;border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden\">\n <MLayout position=\"absolute\" has-sider>\n <MLayoutSider bordered :width=\"120\" content-style=\"padding:0.75rem\">\n Nav\n </MLayoutSider>\n <MLayout>\n <MLayoutHeader bordered style=\"padding:0.75rem 1rem\">\n Absolute layout\n </MLayoutHeader>\n <MLayoutContent embedded content-style=\"padding:1rem\">\n 填满相对定位容器\n </MLayoutContent>\n </MLayout>\n </MLayout>\n </div>\n</template>\n```\n\n## Layout Props\n\n| 参数 | 类型 | 默认值 | 说明 |\n| --- | --- | --- | --- |\n| `hasSider` | `boolean` | `false` | 横向容纳 `MLayoutSider`。 |\n| `siderPlacement` | `'left' \\| 'right'` | `'left'` | 侧栏位置。 |\n| `embedded` | `boolean` | `false` | 柔和背景(嵌套内容区)。 |\n| `position` | `'static' \\| 'absolute'` | `'static'` | 定位模式。 |\n| `contentClass` / `contentStyle` | — | — | 滚动容器 class / style。 |\n| `height` | `number \\| string` | — | — |\n| `padding` | `number \\| string` | — | — |\n| `radius` | `number \\| string` | — | — |\n\n## LayoutSider Props\n\n| 参数 | 类型 | 默认值 | 说明 |\n| --- | --- | --- | --- |\n| `width` | `number \\| string` | `272` | 展开宽度(始终写在 `width` 上)。 |\n| `collapsedWidth` | `number` | `48` | 折叠时的 `max-width`。 |\n| `collapsed` | `boolean` | — | 折叠状态,支持 `v-model:collapsed`。 |\n| `defaultCollapsed` | `boolean` | `false` | 非受控初始折叠。 |\n| `showTrigger` | `boolean \\| 'bar' \\| 'arrow-circle' \\| 'arrow'` | `false` | 折叠触发器;`arrow` 等同 `arrow-circle`。 |\n| `collapseMode` | `'width' \\| 'transform'` | `'transform'` | `transform` 裁切内容;`width` 随侧栏收缩。 |\n| `showCollapsedContent` | `boolean` | `true` | 折叠后是否仍显示侧栏内容。 |\n| `bordered` / `inverted` | `boolean` | `false` | 边框 / 反色。 |\n| `triggerClass` / `triggerStyle` | — | — | 展开态触发器样式。 |\n| `collapsedTriggerClass` / `collapsedTriggerStyle` | — | — | 折叠态触发器样式。 |\n| `contentClass` / `contentStyle` | — | — | 滚动容器 class / style。 |\n\n## Events\n\n| 事件 | 说明 |\n| --- | --- |\n| `scroll` | 滚动容器滚动时触发。 |\n| `after-enter` | 侧栏展开动画结束。 |\n| `after-leave` | 侧栏收起动画结束。 |\n| `collapse` | 侧栏开始收起。 |\n| `expand` | 侧栏开始展开。 |\n| `update:collapsed` | 折叠状态 v-model。 |\n\n## Expose\n\n`MLayout` / `MLayoutContent` / `MLayoutSider` 均暴露 `scrollTo(...)`。\n\n## Components\n\n| 组件 | 说明 |\n| --- | --- |\n| `MLayout` | 根布局。 |\n| `MLayoutHeader` | 顶栏。 |\n| `MLayoutContent` | 主内容区(默认撑满剩余空间)。 |\n| `MLayoutFooter` | 底栏。 |\n| `MLayoutSider` | 侧栏。 |\n\n## Slots\n\n| 插槽名 | 说明 |\n| --- | --- |\n| `default` | 布局区域。 |\n"
11770
11790
  },
11771
11791
  "en-US": {
11772
11792
  "title": "Layout",
@@ -11810,7 +11830,7 @@
11810
11830
  {
11811
11831
  "id": "scrollable-content",
11812
11832
  "title": "Scrollable Content",
11813
- "body": "Only the content pane scrolls; header and sider stay fixed.\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MLayout,\n MLayoutContent,\n MLayoutHeader,\n MLayoutSider,\n} from 'morya-ui'\n</script>\n\n<template>\n <MLayout style=\"height:14rem;border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden\">\n <MLayoutHeader bordered style=\"padding:0.75rem 1rem\">\n Scroll demo\n </MLayoutHeader>\n <MLayout has-sider>\n <MLayoutSider bordered :width=\"120\" content-style=\"padding:0.75rem\">\n Fixed sider\n </MLayoutSider>\n <MLayoutContent embedded content-style=\"padding:1rem\">\n <div style=\"display:grid;gap:0.5rem\">\n <div v-for=\"n in 20\" :key=\"n\">\n Row {{ n }} — scroll down\n </div>\n </div>\n </MLayoutContent>\n </MLayout>\n </MLayout>\n</template>\n```"
11833
+ "body": "Only the content pane scrolls; header and sider stay fixed. `MLayout`, `MLayoutContent`, and `MLayoutSider` use built-in `MScrollbar`.\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MLayout,\n MLayoutContent,\n MLayoutHeader,\n MLayoutSider,\n} from 'morya-ui'\n</script>\n\n<template>\n <MLayout style=\"height:14rem;border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden\">\n <MLayoutHeader bordered style=\"padding:0.75rem 1rem\">\n Scroll demo\n </MLayoutHeader>\n <MLayout has-sider>\n <MLayoutSider bordered :width=\"120\" content-style=\"padding:0.75rem\">\n Fixed sider\n </MLayoutSider>\n <MLayoutContent embedded content-style=\"padding:1rem\">\n <div style=\"display:grid;gap:0.5rem\">\n <div v-for=\"n in 20\" :key=\"n\">\n Row {{ n }} — scroll down\n </div>\n </div>\n </MLayoutContent>\n </MLayout>\n </MLayout>\n</template>\n```"
11814
11834
  },
11815
11835
  {
11816
11836
  "id": "absolute-shell",
@@ -11825,7 +11845,7 @@
11825
11845
  {
11826
11846
  "id": "layoutsider-props",
11827
11847
  "title": "LayoutSider Props",
11828
- "body": "| Prop | Type | Default | Description |\n| --- | --- | --- | --- |\n| `width` | `number \\| string` | `272` | Expanded width (always set as `width`). |\n| `collapsedWidth` | `number` | `48` | Collapsed `max-width`. |\n| `collapsed` | `boolean` | — | Collapsed state (`v-model:collapsed`). |\n| `defaultCollapsed` | `boolean` | `false` | Uncontrolled initial collapsed state. |\n| `showTrigger` | `boolean \\| 'bar' \\| 'arrow-circle' \\| 'arrow'` | `false` | Collapse trigger; `arrow` aliases `arrow-circle`. |\n| `collapseMode` | `'width' \\| 'transform'` | `'transform'` | `transform` clips content; `width` shrinks with sider. |\n| `showCollapsedContent` | `boolean` | `true` | Keep sider content visible while collapsed. |\n| `bordered` / `inverted` | `boolean` | `false` | Border / inverted colors. |\n| `triggerClass` / `triggerStyle` | — | — | Expanded trigger styles. |\n| `collapsedTriggerClass` / `collapsedTriggerStyle` | — | — | Collapsed trigger styles. |"
11848
+ "body": "| Prop | Type | Default | Description |\n| --- | --- | --- | --- |\n| `width` | `number \\| string` | `272` | Expanded width (always set as `width`). |\n| `collapsedWidth` | `number` | `48` | Collapsed `max-width`. |\n| `collapsed` | `boolean` | — | Collapsed state (`v-model:collapsed`). |\n| `defaultCollapsed` | `boolean` | `false` | Uncontrolled initial collapsed state. |\n| `showTrigger` | `boolean \\| 'bar' \\| 'arrow-circle' \\| 'arrow'` | `false` | Collapse trigger; `arrow` aliases `arrow-circle`. |\n| `collapseMode` | `'width' \\| 'transform'` | `'transform'` | `transform` clips content; `width` shrinks with sider. |\n| `showCollapsedContent` | `boolean` | `true` | Keep sider content visible while collapsed. |\n| `bordered` / `inverted` | `boolean` | `false` | Border / inverted colors. |\n| `triggerClass` / `triggerStyle` | — | — | Expanded trigger styles. |\n| `collapsedTriggerClass` / `collapsedTriggerStyle` | — | — | Collapsed trigger styles. |\n| `contentClass` / `contentStyle` | — | — | Scroll container class / style. |"
11829
11849
  },
11830
11850
  {
11831
11851
  "id": "events",
@@ -11848,7 +11868,7 @@
11848
11868
  "body": "| Slot | Description |\n| --- | --- |\n| `default` | Layout regions. |"
11849
11869
  }
11850
11870
  ],
11851
- "markdown": "---\ntitle: Layout\ncategory: 06 / LAYOUT\ndescription: Page layout shell with Header / Sider / Content / Footer.\n---\n\n# Layout\n\nPage-level layout shell. Set `has-sider` on the `MLayout` that hosts a sider. Give the root layout a fixed `height` (or `min-height`) so `MLayoutContent` can fill the remaining space.\n\n## Import\n\n```ts\nimport {\n MLayout,\n MLayoutContent,\n MLayoutFooter,\n MLayoutHeader,\n MLayoutSider,\n} from 'morya-ui'\n```\n\n## Basic\n\nHeader / Content / Footer. Content fills the leftover height.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MLayout, MLayoutContent, MLayoutFooter, MLayoutHeader } from 'morya-ui'\n</script>\n\n<template>\n <MLayout style=\"height:16rem;border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden\">\n <MLayoutHeader bordered style=\"padding:0.75rem 1rem\">\n Header\n </MLayoutHeader>\n <MLayoutContent embedded content-style=\"padding:1rem;display:flex;align-items:center;justify-content:center\">\n Content (fills remaining space)\n </MLayoutContent>\n <MLayoutFooter bordered style=\"padding:0.75rem 1rem\">\n Footer\n </MLayoutFooter>\n </MLayout>\n</template>\n```\n\n## With Sider\n\nHeader + left sider + main. The inner `has-sider` layout consumes all height below the header.\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MLayout,\n MLayoutContent,\n MLayoutHeader,\n MLayoutSider,\n} from 'morya-ui'\nimport { ref } from 'vue'\n\nconst collapsed = ref(false)\n</script>\n\n<template>\n <MLayout style=\"height:16rem;border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden\">\n <MLayoutHeader bordered style=\"padding:0.75rem 1rem;display:flex;align-items:center;justify-content:space-between\">\n <strong>App</strong>\n <span style=\"color:var(--m-color-text-muted);font-size:0.75rem\">{{ collapsed ? 'Collapsed' : 'Expanded' }}</span>\n </MLayoutHeader>\n <MLayout has-sider>\n <MLayoutSider\n v-model:collapsed=\"collapsed\"\n bordered\n show-trigger=\"arrow-circle\"\n :width=\"160\"\n content-style=\"padding:0.75rem\"\n >\n <div style=\"display:grid;gap:0.5rem\">\n <div>Overview</div>\n <div>Projects</div>\n <div>Settings</div>\n </div>\n </MLayoutSider>\n <MLayoutContent embedded content-style=\"padding:1rem\">\n Main area stretches both horizontally and vertically.\n </MLayoutContent>\n </MLayout>\n </MLayout>\n</template>\n```\n\n## Right Sider\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MLayout,\n MLayoutContent,\n MLayoutHeader,\n MLayoutSider,\n} from 'morya-ui'\n</script>\n\n<template>\n <MLayout style=\"height:14rem;border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden\">\n <MLayoutHeader bordered style=\"padding:0.75rem 1rem\">\n Inspector\n </MLayoutHeader>\n <MLayout has-sider sider-placement=\"right\">\n <MLayoutSider bordered :width=\"140\" content-style=\"padding:0.75rem\">\n Props panel\n </MLayoutSider>\n <MLayoutContent embedded content-style=\"padding:1rem\">\n Canvas / main\n </MLayoutContent>\n </MLayout>\n </MLayout>\n</template>\n```\n\n## Full Shell\n\nAdmin-style shell: header + sider + content + footer.\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MButton,\n MLayout,\n MLayoutContent,\n MLayoutFooter,\n MLayoutHeader,\n MLayoutSider,\n MTag,\n} from 'morya-ui'\nimport { ref } from 'vue'\n\nconst collapsed = ref(false)\n</script>\n\n<template>\n <MLayout style=\"height:18rem;border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden\">\n <MLayoutHeader\n bordered\n inverted\n style=\"padding:0.65rem 1rem;display:flex;align-items:center;gap:0.75rem\"\n >\n <strong>Morya UI</strong>\n <MTag value=\"Studio\" />\n <span style=\"flex:1\" />\n <MButton size=\"small\" label=\"Publish\" />\n </MLayoutHeader>\n\n <MLayout has-sider>\n <MLayoutSider\n v-model:collapsed=\"collapsed\"\n bordered\n inverted\n show-trigger=\"bar\"\n :width=\"168\"\n :collapsed-width=\"56\"\n content-style=\"padding:0.75rem\"\n >\n <div style=\"display:grid;gap:0.65rem;font-size:0.875rem\">\n <div>Dashboard</div>\n <div>Datasources</div>\n <div>Widgets</div>\n <div>Theme</div>\n </div>\n </MLayoutSider>\n\n <MLayout>\n <MLayoutContent embedded content-style=\"padding:1rem;display:grid;gap:0.75rem;align-content:start\">\n <strong>Workspace</strong>\n <p style=\"margin:0;color:var(--m-color-text-muted);font-size:0.875rem\">\n Content fills the space between Header and Footer; collapsing the sider keeps the height.\n </p>\n </MLayoutContent>\n <MLayoutFooter bordered style=\"padding:0.5rem 1rem;color:var(--m-color-text-muted);font-size:0.75rem\">\n Ready · local\n </MLayoutFooter>\n </MLayout>\n </MLayout>\n </MLayout>\n</template>\n```\n\n## Embedded Content\n\n`embedded` softens the content background so it separates from header / sider.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MLayout, MLayoutContent, MLayoutHeader } from 'morya-ui'\n</script>\n\n<template>\n <MLayout style=\"height:12rem;border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden\">\n <MLayoutHeader bordered style=\"padding:0.75rem 1rem\">\n Settings\n </MLayoutHeader>\n <MLayoutContent embedded content-style=\"padding:1rem\">\n Nested forms / lists go here.\n </MLayoutContent>\n </MLayout>\n</template>\n```\n\n## Scrollable Content\n\nOnly the content pane scrolls; header and sider stay fixed.\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MLayout,\n MLayoutContent,\n MLayoutHeader,\n MLayoutSider,\n} from 'morya-ui'\n</script>\n\n<template>\n <MLayout style=\"height:14rem;border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden\">\n <MLayoutHeader bordered style=\"padding:0.75rem 1rem\">\n Scroll demo\n </MLayoutHeader>\n <MLayout has-sider>\n <MLayoutSider bordered :width=\"120\" content-style=\"padding:0.75rem\">\n Fixed sider\n </MLayoutSider>\n <MLayoutContent embedded content-style=\"padding:1rem\">\n <div style=\"display:grid;gap:0.5rem\">\n <div v-for=\"n in 20\" :key=\"n\">\n Row {{ n }} — scroll down\n </div>\n </div>\n </MLayoutContent>\n </MLayout>\n </MLayout>\n</template>\n```\n\n## Absolute Shell\n\nRoot `position=\"absolute\"` fills a relatively positioned parent with an explicit height.\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MLayout,\n MLayoutContent,\n MLayoutHeader,\n MLayoutSider,\n} from 'morya-ui'\n</script>\n\n<template>\n <div style=\"position:relative;height:14rem;border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden\">\n <MLayout position=\"absolute\" has-sider>\n <MLayoutSider bordered :width=\"120\" content-style=\"padding:0.75rem\">\n Nav\n </MLayoutSider>\n <MLayout>\n <MLayoutHeader bordered style=\"padding:0.75rem 1rem\">\n Absolute layout\n </MLayoutHeader>\n <MLayoutContent embedded content-style=\"padding:1rem\">\n Fills the relative container\n </MLayoutContent>\n </MLayout>\n </MLayout>\n </div>\n</template>\n```\n\n## Layout Props\n\n| Prop | Type | Default | Description |\n| --- | --- | --- | --- |\n| `hasSider` | `boolean` | `false` | Horizontal layout for `MLayoutSider`. |\n| `siderPlacement` | `'left' \\| 'right'` | `'left'` | Sider side. |\n| `embedded` | `boolean` | `false` | Soft background for nested content. |\n| `position` | `'static' \\| 'absolute'` | `'static'` | Positioning mode. |\n| `contentClass` / `contentStyle` | — | — | Scroll container class / style. |\n\n## LayoutSider Props\n\n| Prop | Type | Default | Description |\n| --- | --- | --- | --- |\n| `width` | `number \\| string` | `272` | Expanded width (always set as `width`). |\n| `collapsedWidth` | `number` | `48` | Collapsed `max-width`. |\n| `collapsed` | `boolean` | — | Collapsed state (`v-model:collapsed`). |\n| `defaultCollapsed` | `boolean` | `false` | Uncontrolled initial collapsed state. |\n| `showTrigger` | `boolean \\| 'bar' \\| 'arrow-circle' \\| 'arrow'` | `false` | Collapse trigger; `arrow` aliases `arrow-circle`. |\n| `collapseMode` | `'width' \\| 'transform'` | `'transform'` | `transform` clips content; `width` shrinks with sider. |\n| `showCollapsedContent` | `boolean` | `true` | Keep sider content visible while collapsed. |\n| `bordered` / `inverted` | `boolean` | `false` | Border / inverted colors. |\n| `triggerClass` / `triggerStyle` | — | — | Expanded trigger styles. |\n| `collapsedTriggerClass` / `collapsedTriggerStyle` | — | — | Collapsed trigger styles. |\n\n## Events\n\n| Event | Description |\n| --- | --- |\n| `scroll` | Fired when the scroll container scrolls. |\n\n## Expose\n\n`MLayout` / `MLayoutContent` / `MLayoutSider` expose `scrollTo(...)`.\n\n## Components\n\n| Component | Description |\n| --- | --- |\n| `MLayout` | Root layout. |\n| `MLayoutHeader` | Header bar. |\n| `MLayoutContent` | Main content (fills leftover space by default). |\n| `MLayoutFooter` | Footer bar. |\n| `MLayoutSider` | Side panel. |\n\n## Slots\n\n| Slot | Description |\n| --- | --- |\n| `default` | Layout regions. |\n"
11871
+ "markdown": "---\ntitle: Layout\ncategory: 06 / LAYOUT\ndescription: Page layout shell with Header / Sider / Content / Footer.\n---\n\n# Layout\n\nPage-level layout shell. Set `has-sider` on the `MLayout` that hosts a sider. Give the root layout a fixed `height` (or `min-height`) so `MLayoutContent` can fill the remaining space.\n\n## Import\n\n```ts\nimport {\n MLayout,\n MLayoutContent,\n MLayoutFooter,\n MLayoutHeader,\n MLayoutSider,\n} from 'morya-ui'\n```\n\n## Basic\n\nHeader / Content / Footer. Content fills the leftover height.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MLayout, MLayoutContent, MLayoutFooter, MLayoutHeader } from 'morya-ui'\n</script>\n\n<template>\n <MLayout style=\"height:16rem;border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden\">\n <MLayoutHeader bordered style=\"padding:0.75rem 1rem\">\n Header\n </MLayoutHeader>\n <MLayoutContent embedded content-style=\"padding:1rem;display:flex;align-items:center;justify-content:center\">\n Content (fills remaining space)\n </MLayoutContent>\n <MLayoutFooter bordered style=\"padding:0.75rem 1rem\">\n Footer\n </MLayoutFooter>\n </MLayout>\n</template>\n```\n\n## With Sider\n\nHeader + left sider + main. The inner `has-sider` layout consumes all height below the header.\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MLayout,\n MLayoutContent,\n MLayoutHeader,\n MLayoutSider,\n} from 'morya-ui'\nimport { ref } from 'vue'\n\nconst collapsed = ref(false)\n</script>\n\n<template>\n <MLayout style=\"height:16rem;border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden\">\n <MLayoutHeader bordered style=\"padding:0.75rem 1rem;display:flex;align-items:center;justify-content:space-between\">\n <strong>App</strong>\n <span style=\"color:var(--m-color-text-muted);font-size:0.75rem\">{{ collapsed ? 'Collapsed' : 'Expanded' }}</span>\n </MLayoutHeader>\n <MLayout has-sider>\n <MLayoutSider\n v-model:collapsed=\"collapsed\"\n bordered\n show-trigger=\"arrow-circle\"\n :width=\"160\"\n content-style=\"padding:0.75rem\"\n >\n <div style=\"display:grid;gap:0.5rem\">\n <div>Overview</div>\n <div>Projects</div>\n <div>Settings</div>\n </div>\n </MLayoutSider>\n <MLayoutContent embedded content-style=\"padding:1rem\">\n Main area stretches both horizontally and vertically.\n </MLayoutContent>\n </MLayout>\n </MLayout>\n</template>\n```\n\n## Right Sider\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MLayout,\n MLayoutContent,\n MLayoutHeader,\n MLayoutSider,\n} from 'morya-ui'\n</script>\n\n<template>\n <MLayout style=\"height:14rem;border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden\">\n <MLayoutHeader bordered style=\"padding:0.75rem 1rem\">\n Inspector\n </MLayoutHeader>\n <MLayout has-sider sider-placement=\"right\">\n <MLayoutSider bordered :width=\"140\" content-style=\"padding:0.75rem\">\n Props panel\n </MLayoutSider>\n <MLayoutContent embedded content-style=\"padding:1rem\">\n Canvas / main\n </MLayoutContent>\n </MLayout>\n </MLayout>\n</template>\n```\n\n## Full Shell\n\nAdmin-style shell: header + sider + content + footer.\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MButton,\n MLayout,\n MLayoutContent,\n MLayoutFooter,\n MLayoutHeader,\n MLayoutSider,\n MTag,\n} from 'morya-ui'\nimport { ref } from 'vue'\n\nconst collapsed = ref(false)\n</script>\n\n<template>\n <MLayout style=\"height:18rem;border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden\">\n <MLayoutHeader\n bordered\n inverted\n style=\"padding:0.65rem 1rem;display:flex;align-items:center;gap:0.75rem\"\n >\n <strong>Morya UI</strong>\n <MTag value=\"Studio\" />\n <span style=\"flex:1\" />\n <MButton size=\"small\" label=\"Publish\" />\n </MLayoutHeader>\n\n <MLayout has-sider>\n <MLayoutSider\n v-model:collapsed=\"collapsed\"\n bordered\n inverted\n show-trigger=\"bar\"\n :width=\"168\"\n :collapsed-width=\"56\"\n content-style=\"padding:0.75rem\"\n >\n <div style=\"display:grid;gap:0.65rem;font-size:0.875rem\">\n <div>Dashboard</div>\n <div>Datasources</div>\n <div>Widgets</div>\n <div>Theme</div>\n </div>\n </MLayoutSider>\n\n <MLayout>\n <MLayoutContent embedded content-style=\"padding:1rem;display:grid;gap:0.75rem;align-content:start\">\n <strong>Workspace</strong>\n <p style=\"margin:0;color:var(--m-color-text-muted);font-size:0.875rem\">\n Content fills the space between Header and Footer; collapsing the sider keeps the height.\n </p>\n </MLayoutContent>\n <MLayoutFooter bordered style=\"padding:0.5rem 1rem;color:var(--m-color-text-muted);font-size:0.75rem\">\n Ready · local\n </MLayoutFooter>\n </MLayout>\n </MLayout>\n </MLayout>\n</template>\n```\n\n## Embedded Content\n\n`embedded` softens the content background so it separates from header / sider.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MLayout, MLayoutContent, MLayoutHeader } from 'morya-ui'\n</script>\n\n<template>\n <MLayout style=\"height:12rem;border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden\">\n <MLayoutHeader bordered style=\"padding:0.75rem 1rem\">\n Settings\n </MLayoutHeader>\n <MLayoutContent embedded content-style=\"padding:1rem\">\n Nested forms / lists go here.\n </MLayoutContent>\n </MLayout>\n</template>\n```\n\n## Scrollable Content\n\nOnly the content pane scrolls; header and sider stay fixed. `MLayout`, `MLayoutContent`, and `MLayoutSider` use built-in `MScrollbar`.\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MLayout,\n MLayoutContent,\n MLayoutHeader,\n MLayoutSider,\n} from 'morya-ui'\n</script>\n\n<template>\n <MLayout style=\"height:14rem;border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden\">\n <MLayoutHeader bordered style=\"padding:0.75rem 1rem\">\n Scroll demo\n </MLayoutHeader>\n <MLayout has-sider>\n <MLayoutSider bordered :width=\"120\" content-style=\"padding:0.75rem\">\n Fixed sider\n </MLayoutSider>\n <MLayoutContent embedded content-style=\"padding:1rem\">\n <div style=\"display:grid;gap:0.5rem\">\n <div v-for=\"n in 20\" :key=\"n\">\n Row {{ n }} — scroll down\n </div>\n </div>\n </MLayoutContent>\n </MLayout>\n </MLayout>\n</template>\n```\n\n## Absolute Shell\n\nRoot `position=\"absolute\"` fills a relatively positioned parent with an explicit height.\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MLayout,\n MLayoutContent,\n MLayoutHeader,\n MLayoutSider,\n} from 'morya-ui'\n</script>\n\n<template>\n <div style=\"position:relative;height:14rem;border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden\">\n <MLayout position=\"absolute\" has-sider>\n <MLayoutSider bordered :width=\"120\" content-style=\"padding:0.75rem\">\n Nav\n </MLayoutSider>\n <MLayout>\n <MLayoutHeader bordered style=\"padding:0.75rem 1rem\">\n Absolute layout\n </MLayoutHeader>\n <MLayoutContent embedded content-style=\"padding:1rem\">\n Fills the relative container\n </MLayoutContent>\n </MLayout>\n </MLayout>\n </div>\n</template>\n```\n\n## Layout Props\n\n| Prop | Type | Default | Description |\n| --- | --- | --- | --- |\n| `hasSider` | `boolean` | `false` | Horizontal layout for `MLayoutSider`. |\n| `siderPlacement` | `'left' \\| 'right'` | `'left'` | Sider side. |\n| `embedded` | `boolean` | `false` | Soft background for nested content. |\n| `position` | `'static' \\| 'absolute'` | `'static'` | Positioning mode. |\n| `contentClass` / `contentStyle` | — | — | Scroll container class / style. |\n\n## LayoutSider Props\n\n| Prop | Type | Default | Description |\n| --- | --- | --- | --- |\n| `width` | `number \\| string` | `272` | Expanded width (always set as `width`). |\n| `collapsedWidth` | `number` | `48` | Collapsed `max-width`. |\n| `collapsed` | `boolean` | — | Collapsed state (`v-model:collapsed`). |\n| `defaultCollapsed` | `boolean` | `false` | Uncontrolled initial collapsed state. |\n| `showTrigger` | `boolean \\| 'bar' \\| 'arrow-circle' \\| 'arrow'` | `false` | Collapse trigger; `arrow` aliases `arrow-circle`. |\n| `collapseMode` | `'width' \\| 'transform'` | `'transform'` | `transform` clips content; `width` shrinks with sider. |\n| `showCollapsedContent` | `boolean` | `true` | Keep sider content visible while collapsed. |\n| `bordered` / `inverted` | `boolean` | `false` | Border / inverted colors. |\n| `triggerClass` / `triggerStyle` | — | — | Expanded trigger styles. |\n| `collapsedTriggerClass` / `collapsedTriggerStyle` | — | — | Collapsed trigger styles. |\n| `contentClass` / `contentStyle` | — | — | Scroll container class / style. |\n\n## Events\n\n| Event | Description |\n| --- | --- |\n| `scroll` | Fired when the scroll container scrolls. |\n\n## Expose\n\n`MLayout` / `MLayoutContent` / `MLayoutSider` expose `scrollTo(...)`.\n\n## Components\n\n| Component | Description |\n| --- | --- |\n| `MLayout` | Root layout. |\n| `MLayoutHeader` | Header bar. |\n| `MLayoutContent` | Main content (fills leftover space by default). |\n| `MLayoutFooter` | Footer bar. |\n| `MLayoutSider` | Side panel. |\n\n## Slots\n\n| Slot | Description |\n| --- | --- |\n| `default` | Layout regions. |\n"
11852
11872
  }
11853
11873
  }
11854
11874
  },
@@ -12656,7 +12676,7 @@
12656
12676
  {
12657
12677
  "id": "折叠与飞出层",
12658
12678
  "title": "折叠与飞出层",
12659
- "body": "`collapsed` 隐藏文案,仅保留图标;悬停时在右侧显示 `MTooltip` 标签,带子项的节点还会弹出飞出层(`.m-menu--flyout`)。`collapsed-width` 应与侧栏折叠宽度一致,用于居中图标。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst selectedKey = ref('home')\nconst model = [\n {\n key: 'workspace',\n label: '工作区',\n icon: 'folder',\n items: [\n { key: 'home', label: '首页', icon: 'home' },\n { key: 'docs', label: '文档', icon: 'file-text' },\n ],\n },\n { key: 'settings', label: '设置', icon: 'settings' },\n]\n</script>\n\n<template>\n <div style=\"display: flex; gap: var(--m-space-4); align-items: stretch\">\n <div\n style=\"\n flex: 1;\n min-width: 0;\n padding: var(--m-space-3);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <p\n style=\"\n margin: 0 0 var(--m-space-3);\n font-size: var(--m-font-size-xs);\n color: var(--m-color-text-muted);\n \"\n >\n 展开\n </p>\n <MMenu v-model:selected-key=\"selectedKey\" :model=\"model\" embedded />\n </div>\n <div\n style=\"\n width: 4.5rem;\n padding: var(--m-space-3) var(--m-space-2);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <p\n style=\"\n margin: 0 0 var(--m-space-3);\n font-size: var(--m-font-size-xs);\n color: var(--m-color-text-muted);\n text-align: center;\n \"\n >\n 折叠\n </p>\n <MMenu\n v-model:selected-key=\"selectedKey\"\n :model=\"model\"\n collapsed\n embedded\n :collapsed-width=\"64\"\n />\n </div>\n </div>\n</template>\n```"
12679
+ "body": "`collapsed` 隐藏文案,仅保留图标;悬停时在右侧显示 `MTooltip` 标签,带子项的节点还会弹出飞出层(`.m-menu--flyout`)。飞出层经 `MPopover` Teleport 到 `body`,不会被侧栏或 `MLayoutSider` 滚动区域裁剪。`collapsed-width` 应与侧栏折叠宽度一致,用于居中图标。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst selectedKey = ref('home')\nconst model = [\n {\n key: 'workspace',\n label: '工作区',\n icon: 'folder',\n items: [\n { key: 'home', label: '首页', icon: 'home' },\n { key: 'docs', label: '文档', icon: 'file-text' },\n ],\n },\n { key: 'settings', label: '设置', icon: 'settings' },\n]\n</script>\n\n<template>\n <div style=\"display: flex; gap: var(--m-space-4); align-items: stretch\">\n <div\n style=\"\n flex: 1;\n min-width: 0;\n padding: var(--m-space-3);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <p\n style=\"\n margin: 0 0 var(--m-space-3);\n font-size: var(--m-font-size-xs);\n color: var(--m-color-text-muted);\n \"\n >\n 展开\n </p>\n <MMenu v-model:selected-key=\"selectedKey\" :model=\"model\" embedded />\n </div>\n <div\n style=\"\n width: 4.5rem;\n padding: var(--m-space-3) var(--m-space-2);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <p\n style=\"\n margin: 0 0 var(--m-space-3);\n font-size: var(--m-font-size-xs);\n color: var(--m-color-text-muted);\n text-align: center;\n \"\n >\n 折叠\n </p>\n <MMenu\n v-model:selected-key=\"selectedKey\"\n :model=\"model\"\n collapsed\n embedded\n :collapsed-width=\"64\"\n />\n </div>\n </div>\n</template>\n```"
12660
12680
  },
12661
12681
  {
12662
12682
  "id": "嵌入-layout-侧栏",
@@ -12666,7 +12686,7 @@
12666
12686
  {
12667
12687
  "id": "水平菜单",
12668
12688
  "title": "水平菜单",
12669
- "body": "`mode=\"horizontal\"` 用于顶栏一级导航;子菜单以下拉飞出层展示,选中后自动关闭。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst selectedKey = ref('home')\nconst model = [\n { key: 'home', label: '首页', icon: 'home' },\n {\n key: 'products',\n label: '产品',\n icon: 'box',\n items: [\n { key: 'cloud', label: '云服务', icon: 'cloud' },\n { key: 'edge', label: '边缘计算', icon: 'server' },\n ],\n },\n { key: 'about', label: '关于', icon: 'info-circle' },\n]\n</script>\n\n<template>\n <div\n style=\"\n padding: 0 var(--m-space-2);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <MMenu v-model:selected-key=\"selectedKey\" :model=\"model\" mode=\"horizontal\" embedded />\n </div>\n</template>\n```"
12689
+ "body": "`mode=\"horizontal\"` 用于顶栏一级导航;子菜单经 `MPopover` 以下拉飞出层展示(Teleport + 主题滚动条),选中后自动关闭。`popup` 模式的主菜单列表同样内置 `MScrollbar`。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst selectedKey = ref('home')\nconst model = [\n { key: 'home', label: '首页', icon: 'home' },\n {\n key: 'products',\n label: '产品',\n icon: 'box',\n items: [\n { key: 'cloud', label: '云服务', icon: 'cloud' },\n { key: 'edge', label: '边缘计算', icon: 'server' },\n ],\n },\n { key: 'about', label: '关于', icon: 'info-circle' },\n]\n</script>\n\n<template>\n <div\n style=\"\n padding: 0 var(--m-space-2);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <MMenu v-model:selected-key=\"selectedKey\" :model=\"model\" mode=\"horizontal\" embedded />\n </div>\n</template>\n```"
12670
12690
  },
12671
12691
  {
12672
12692
  "id": "反色-深色侧栏",
@@ -12704,7 +12724,7 @@
12704
12724
  "body": "<h4 id=\"MenuItem\">MenuItem</h4>\n\n`model` 数组项,支持嵌套:\n\n```ts\ninterface MenuItem {\n key?: string\n label?: string\n icon?: string\n to?: string | RouteLocationRaw // 有 vue-router 时用 RouterLink\n command?: () => void\n disabled?: boolean\n separator?: boolean\n items?: MenuItem[]\n}\n```\n\n`key` 未传时用 `label` 作为标识。`separator: true` 时渲染分隔线。更多见 [API 类型](/docs/types)。"
12705
12725
  }
12706
12726
  ],
12707
- "markdown": "---\ntitle: Menu\ncategory: 04 / NAVIGATION\ndescription: 垂直/水平导航菜单,支持多级嵌套、受控选中、手风琴展开与折叠侧栏飞出层。\n---\n\n# Menu\n\n基于 `model` 渲染的**导航菜单**,适合后台侧栏、顶栏导航等场景。支持:\n\n- 多级嵌套 `items` 与受控 `selectedKey`\n- 展开路径自动跟随选中项;`accordion` 手风琴\n- `collapsed` 图标模式 + 右侧飞出子菜单(Popover)\n- 非 popup 时默认 `embedded`,无边框铺满 `MLayoutSider`\n\n> 单层悬停子菜单见 [TieredMenu](/components/TieredMenu);顶栏菜单见 [Menubar](/components/Menubar);操作列表见 [Dropdown](/components/Dropdown)。\n\n## 引入\n\n```ts\nimport type {MenuItem} from 'morya-ui';\nimport { MMenu } from 'morya-ui'\n```\n\n## 导航选中\n\n为叶子项设置稳定的 `key`,用 `v-model:selected-key` 与路由同步;点击时触发 `select`。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst selectedKey = ref('dashboard')\nconst model = [\n { key: 'dashboard', label: '仪表盘', icon: 'layout-dashboard' },\n { key: 'users', label: '用户', icon: 'users' },\n { key: 'settings', label: '设置', icon: 'settings', disabled: true },\n]\n</script>\n\n<template>\n <div>\n <MMenu\n v-model:selected-key=\"selectedKey\"\n :model=\"model\"\n embedded\n @select=\"(item) => console.log('select', item.key)\"\n />\n </div>\n</template>\n```\n\n未提供 `key` 时会回退到 `label`;生产环境建议始终显式设置 `key`。\n\n## 嵌套子菜单\n\n点击带子项的节点可展开/收起;选中子项时父级会显示 `child-active` 高亮。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst selectedKey = ref('reports')\nconst model = [\n {\n key: 'analytics',\n label: '数据分析',\n icon: 'chart-bar',\n items: [\n { key: 'reports', label: '报表', icon: 'file-text' },\n { key: 'monitor', label: '监控', icon: 'activity' },\n ],\n },\n { key: 'settings', label: '系统设置', icon: 'settings' },\n]\n</script>\n\n<template>\n <div\n style=\"\n width: 16rem;\n padding: var(--m-space-3);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <MMenu v-model:selected-key=\"selectedKey\" :model=\"model\" embedded />\n </div>\n</template>\n```\n\n## 手风琴与展开控制\n\n`accordion` 同时只保留一个一级子菜单展开。`defaultExpandedKeys` / `v-model:expanded-keys` 可受控展开项;变更 `selectedKey` 时会自动展开其祖先路径。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst selectedKey = ref('a1')\nconst model = [\n { key: 'a', label: '模块 A', items: [{ key: 'a1', label: 'A-1' }] },\n { key: 'b', label: '模块 B', items: [{ key: 'b1', label: 'B-1' }] },\n]\n</script>\n\n<template>\n <div\n style=\"\n width: 14rem;\n padding: var(--m-space-3);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <MMenu v-model:selected-key=\"selectedKey\" :model=\"model\" accordion embedded />\n </div>\n</template>\n```\n\n## 折叠与飞出层\n\n`collapsed` 隐藏文案,仅保留图标;悬停时在右侧显示 `MTooltip` 标签,带子项的节点还会弹出飞出层(`.m-menu--flyout`)。`collapsed-width` 应与侧栏折叠宽度一致,用于居中图标。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst selectedKey = ref('home')\nconst model = [\n {\n key: 'workspace',\n label: '工作区',\n icon: 'folder',\n items: [\n { key: 'home', label: '首页', icon: 'home' },\n { key: 'docs', label: '文档', icon: 'file-text' },\n ],\n },\n { key: 'settings', label: '设置', icon: 'settings' },\n]\n</script>\n\n<template>\n <div style=\"display: flex; gap: var(--m-space-4); align-items: stretch\">\n <div\n style=\"\n flex: 1;\n min-width: 0;\n padding: var(--m-space-3);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <p\n style=\"\n margin: 0 0 var(--m-space-3);\n font-size: var(--m-font-size-xs);\n color: var(--m-color-text-muted);\n \"\n >\n 展开\n </p>\n <MMenu v-model:selected-key=\"selectedKey\" :model=\"model\" embedded />\n </div>\n <div\n style=\"\n width: 4.5rem;\n padding: var(--m-space-3) var(--m-space-2);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <p\n style=\"\n margin: 0 0 var(--m-space-3);\n font-size: var(--m-font-size-xs);\n color: var(--m-color-text-muted);\n text-align: center;\n \"\n >\n 折叠\n </p>\n <MMenu\n v-model:selected-key=\"selectedKey\"\n :model=\"model\"\n collapsed\n embedded\n :collapsed-width=\"64\"\n />\n </div>\n </div>\n</template>\n```\n\n## 嵌入 Layout 侧栏\n\n推荐结构:**全局 Header + 下方 `has-sider` Layout**。菜单放在 `MLayoutSider` 内,与 `v-model:collapsed` 联动。\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MLayout,\n MLayoutContent,\n MLayoutHeader,\n MLayoutSider,\n MMenu,\n} from 'morya-ui'\nimport { ref } from 'vue'\n\nconst collapsed = ref(false)\nconst selectedKey = ref('dashboard')\nconst model = [\n { key: 'dashboard', label: '仪表盘', icon: 'layout-dashboard' },\n {\n key: 'system',\n label: '系统',\n icon: 'settings',\n items: [\n { key: 'users', label: '用户', icon: 'users' },\n { key: 'roles', label: '角色', icon: 'shield' },\n ],\n },\n]\n</script>\n\n<template>\n <MLayout\n style=\"\n height: 14rem;\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n overflow: hidden;\n \"\n >\n <MLayoutHeader\n bordered\n style=\"\n padding: 0 var(--m-space-4);\n display: flex;\n align-items: center;\n min-height: var(--m-layout-header-height);\n \"\n >\n <strong style=\"color: var(--m-color-primary); font-size: var(--m-font-size-md)\">头部菜单</strong>\n </MLayoutHeader>\n <MLayout has-sider>\n <MLayoutSider\n v-model:collapsed=\"collapsed\"\n bordered\n show-trigger=\"arrow-circle\"\n collapse-mode=\"width\"\n :collapsed-width=\"120\"\n >\n <MMenu\n v-model:selected-key=\"selectedKey\"\n :model=\"model\"\n :collapsed=\"collapsed\"\n :collapsed-width=\"64\"\n />\n </MLayoutSider>\n <MLayoutContent embedded content-style=\"padding: var(--m-space-4)\">\n <p style=\"margin: 0; color: var(--m-color-text-muted); font-size: var(--m-font-size-sm)\">\n 当前选中:<strong style=\"color: var(--m-color-text)\">{{ selectedKey }}</strong>\n </p>\n </MLayoutContent>\n </MLayout>\n </MLayout>\n</template>\n```\n\n## 水平菜单\n\n`mode=\"horizontal\"` 用于顶栏一级导航;子菜单以下拉飞出层展示,选中后自动关闭。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst selectedKey = ref('home')\nconst model = [\n { key: 'home', label: '首页', icon: 'home' },\n {\n key: 'products',\n label: '产品',\n icon: 'box',\n items: [\n { key: 'cloud', label: '云服务', icon: 'cloud' },\n { key: 'edge', label: '边缘计算', icon: 'server' },\n ],\n },\n { key: 'about', label: '关于', icon: 'info-circle' },\n]\n</script>\n\n<template>\n <div\n style=\"\n padding: 0 var(--m-space-2);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <MMenu v-model:selected-key=\"selectedKey\" :model=\"model\" mode=\"horizontal\" embedded />\n </div>\n</template>\n```\n\n## 反色(深色侧栏)\n\n`inverted` 配合 `MLayoutSider` 的 `inverted`,用于深色背景侧栏。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MLayout, MLayoutSider, MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst selectedKey = ref('dashboard')\nconst model = [\n { key: 'dashboard', label: '仪表盘', icon: 'layout-dashboard' },\n { key: 'users', label: '用户', icon: 'users' },\n]\n</script>\n\n<template>\n <MLayout\n has-sider\n style=\"\n height: 10rem;\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n overflow: hidden;\n \"\n >\n <MLayoutSider inverted bordered style=\"width: 12rem\">\n <MMenu v-model:selected-key=\"selectedKey\" :model=\"model\" inverted />\n </MLayoutSider>\n </MLayout>\n</template>\n```\n\n## 弹出模式\n\n`popup` + `v-model` 将菜单作为浮层,默认 Teleport 到 `body` 并相对**默认插槽触发器**定位(无插槽时回退到最后一次指针位置)。点击外部或选中叶子项后关闭。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MButton, MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst open = ref(false)\nconst model = [\n { label: '复制', command: () => undefined },\n { separator: true },\n { label: '删除', disabled: true },\n]\n</script>\n\n<template>\n <MMenu v-model=\"open\" popup :model=\"model\">\n <MButton label=\"更多操作\" @click=\"open = !open\" />\n </MMenu>\n</template>\n```\n\n## Props\n\n| 参数 | 类型 | 默认值 | 说明 |\n| --- | --- | --- | --- |\n| `model` | `MenuItem[]` | — | 菜单项,可嵌套 `items`。 |\n| `popup` | `boolean` | `false` | 浮层模式;配合 `v-model` 控制显隐。 |\n| `modelValue` | `boolean` | `false` | popup 可见性(`v-model`)。 |\n| `placement` | `'bottom-start' \\| 'bottom-end' \\| 'top-start' \\| 'top-end'` | `'bottom-start'` | popup 相对触发器的位置。 |\n| `selectedKey` | `string \\| null` | `null` | 当前选中项 key(`v-model:selected-key`)。 |\n| `collapsed` | `boolean` | `false` | 图标模式;子菜单以右侧飞出层展示。 |\n| `collapsedWidth` | `number` | `80` | 折叠宽度(px),用于居中图标。 |\n| `indent` | `number` | `12` | 每层额外左内边距(px)。 |\n| `rootIndent` | `number` | `16` | 根级左内边距(px)。 |\n| `accordion` | `boolean` | `false` | 手风琴:同时只展开一个一级子菜单。 |\n| `defaultExpandedKeys` | `string[]` | `[]` | 默认展开的 submenu keys。 |\n| `expandedKeys` | `string[]` | — | 受控展开 keys(`v-model:expanded-keys`)。 |\n| `defaultExpandAll` | `boolean` | `false` | 初始展开全部子菜单。 |\n| `mode` | `'vertical' \\| 'horizontal'` | `'vertical'` | 布局方向。 |\n| `inverted` | `boolean` | `false` | 反色样式,适合深色侧栏。 |\n| `embedded` | `boolean` | `!popup` | 嵌入布局:去边框与最小宽度。 |\n| `teleport` | `boolean` | `true` | popup 时 Teleport 到 `appendTo`。 |\n| `appendTo` | `string \\| HTMLElement \\| 'self' \\| false` | `'body'` | Teleport 目标;未传时使用 ConfigProvider。 |\n| `pt` | [RootPassThrough](/docs/types#RootPassThrough) `{ root? }` | — | DOM 透传,见 [样式与 attrs](/docs/attrs). |\n\n\n## Events\n\n| 事件名 | 参数 | 说明 |\n| --- | --- | --- |\n| `update:modelValue` | `boolean` | popup 可见性变化。 |\n| `update:selectedKey` | `string \\| null` | 选中项变化。 |\n| `update:expandedKeys` | `string[]` | 展开项变化。 |\n| `select` | `MenuItem` | 点击叶子项(非 disabled / separator)。 |\n\n## Slots\n\n| 插槽 | 说明 |\n| --- | --- |\n| `default` | popup 模式的触发器锚点(如按钮);菜单相对其定位。 |\n\n## MenuItem\n\n| 字段 | 类型 | 说明 |\n| --- | --- | --- |\n| `key` | `string` | 唯一标识;未传时使用 `label`。 |\n| `label` | `string` | 展示文本。 |\n| `icon` | `string` | [Tabler 图标名](/components/Icon) 或字符。 |\n| `command` | `() => void` | 点击回调(与 `select` 事件同时触发)。 |\n| `disabled` | `boolean` | 禁用。 |\n| `separator` | `boolean` | 分隔线(忽略其他字段)。 |\n| `items` | `MenuItem[]` | 子菜单。 |\n\n## 类型\n\n<h4 id=\"MenuItem\">MenuItem</h4>\n\n`model` 数组项,支持嵌套:\n\n```ts\ninterface MenuItem {\n key?: string\n label?: string\n icon?: string\n to?: string | RouteLocationRaw // 有 vue-router 时用 RouterLink\n command?: () => void\n disabled?: boolean\n separator?: boolean\n items?: MenuItem[]\n}\n```\n\n`key` 未传时用 `label` 作为标识。`separator: true` 时渲染分隔线。更多见 [API 类型](/docs/types)。\n"
12727
+ "markdown": "---\ntitle: Menu\ncategory: 04 / NAVIGATION\ndescription: 垂直/水平导航菜单,支持多级嵌套、受控选中、手风琴展开与折叠侧栏飞出层。\n---\n\n# Menu\n\n基于 `model` 渲染的**导航菜单**,适合后台侧栏、顶栏导航等场景。支持:\n\n- 多级嵌套 `items` 与受控 `selectedKey`\n- 展开路径自动跟随选中项;`accordion` 手风琴\n- `collapsed` 图标模式 + 右侧飞出子菜单(Popover)\n- 非 popup 时默认 `embedded`,无边框铺满 `MLayoutSider`\n\n> 单层悬停子菜单见 [TieredMenu](/components/TieredMenu);顶栏菜单见 [Menubar](/components/Menubar);操作列表见 [Dropdown](/components/Dropdown)。\n\n## 引入\n\n```ts\nimport type {MenuItem} from 'morya-ui';\nimport { MMenu } from 'morya-ui'\n```\n\n## 导航选中\n\n为叶子项设置稳定的 `key`,用 `v-model:selected-key` 与路由同步;点击时触发 `select`。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst selectedKey = ref('dashboard')\nconst model = [\n { key: 'dashboard', label: '仪表盘', icon: 'layout-dashboard' },\n { key: 'users', label: '用户', icon: 'users' },\n { key: 'settings', label: '设置', icon: 'settings', disabled: true },\n]\n</script>\n\n<template>\n <div>\n <MMenu\n v-model:selected-key=\"selectedKey\"\n :model=\"model\"\n embedded\n @select=\"(item) => console.log('select', item.key)\"\n />\n </div>\n</template>\n```\n\n未提供 `key` 时会回退到 `label`;生产环境建议始终显式设置 `key`。\n\n## 嵌套子菜单\n\n点击带子项的节点可展开/收起;选中子项时父级会显示 `child-active` 高亮。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst selectedKey = ref('reports')\nconst model = [\n {\n key: 'analytics',\n label: '数据分析',\n icon: 'chart-bar',\n items: [\n { key: 'reports', label: '报表', icon: 'file-text' },\n { key: 'monitor', label: '监控', icon: 'activity' },\n ],\n },\n { key: 'settings', label: '系统设置', icon: 'settings' },\n]\n</script>\n\n<template>\n <div\n style=\"\n width: 16rem;\n padding: var(--m-space-3);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <MMenu v-model:selected-key=\"selectedKey\" :model=\"model\" embedded />\n </div>\n</template>\n```\n\n## 手风琴与展开控制\n\n`accordion` 同时只保留一个一级子菜单展开。`defaultExpandedKeys` / `v-model:expanded-keys` 可受控展开项;变更 `selectedKey` 时会自动展开其祖先路径。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst selectedKey = ref('a1')\nconst model = [\n { key: 'a', label: '模块 A', items: [{ key: 'a1', label: 'A-1' }] },\n { key: 'b', label: '模块 B', items: [{ key: 'b1', label: 'B-1' }] },\n]\n</script>\n\n<template>\n <div\n style=\"\n width: 14rem;\n padding: var(--m-space-3);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <MMenu v-model:selected-key=\"selectedKey\" :model=\"model\" accordion embedded />\n </div>\n</template>\n```\n\n## 折叠与飞出层\n\n`collapsed` 隐藏文案,仅保留图标;悬停时在右侧显示 `MTooltip` 标签,带子项的节点还会弹出飞出层(`.m-menu--flyout`)。飞出层经 `MPopover` Teleport 到 `body`,不会被侧栏或 `MLayoutSider` 滚动区域裁剪。`collapsed-width` 应与侧栏折叠宽度一致,用于居中图标。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst selectedKey = ref('home')\nconst model = [\n {\n key: 'workspace',\n label: '工作区',\n icon: 'folder',\n items: [\n { key: 'home', label: '首页', icon: 'home' },\n { key: 'docs', label: '文档', icon: 'file-text' },\n ],\n },\n { key: 'settings', label: '设置', icon: 'settings' },\n]\n</script>\n\n<template>\n <div style=\"display: flex; gap: var(--m-space-4); align-items: stretch\">\n <div\n style=\"\n flex: 1;\n min-width: 0;\n padding: var(--m-space-3);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <p\n style=\"\n margin: 0 0 var(--m-space-3);\n font-size: var(--m-font-size-xs);\n color: var(--m-color-text-muted);\n \"\n >\n 展开\n </p>\n <MMenu v-model:selected-key=\"selectedKey\" :model=\"model\" embedded />\n </div>\n <div\n style=\"\n width: 4.5rem;\n padding: var(--m-space-3) var(--m-space-2);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <p\n style=\"\n margin: 0 0 var(--m-space-3);\n font-size: var(--m-font-size-xs);\n color: var(--m-color-text-muted);\n text-align: center;\n \"\n >\n 折叠\n </p>\n <MMenu\n v-model:selected-key=\"selectedKey\"\n :model=\"model\"\n collapsed\n embedded\n :collapsed-width=\"64\"\n />\n </div>\n </div>\n</template>\n```\n\n## 嵌入 Layout 侧栏\n\n推荐结构:**全局 Header + 下方 `has-sider` Layout**。菜单放在 `MLayoutSider` 内,与 `v-model:collapsed` 联动。\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MLayout,\n MLayoutContent,\n MLayoutHeader,\n MLayoutSider,\n MMenu,\n} from 'morya-ui'\nimport { ref } from 'vue'\n\nconst collapsed = ref(false)\nconst selectedKey = ref('dashboard')\nconst model = [\n { key: 'dashboard', label: '仪表盘', icon: 'layout-dashboard' },\n {\n key: 'system',\n label: '系统',\n icon: 'settings',\n items: [\n { key: 'users', label: '用户', icon: 'users' },\n { key: 'roles', label: '角色', icon: 'shield' },\n ],\n },\n]\n</script>\n\n<template>\n <MLayout\n style=\"\n height: 14rem;\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n overflow: hidden;\n \"\n >\n <MLayoutHeader\n bordered\n style=\"\n padding: 0 var(--m-space-4);\n display: flex;\n align-items: center;\n min-height: var(--m-layout-header-height);\n \"\n >\n <strong style=\"color: var(--m-color-primary); font-size: var(--m-font-size-md)\">头部菜单</strong>\n </MLayoutHeader>\n <MLayout has-sider>\n <MLayoutSider\n v-model:collapsed=\"collapsed\"\n bordered\n show-trigger=\"arrow-circle\"\n collapse-mode=\"width\"\n :collapsed-width=\"120\"\n >\n <MMenu\n v-model:selected-key=\"selectedKey\"\n :model=\"model\"\n :collapsed=\"collapsed\"\n :collapsed-width=\"64\"\n />\n </MLayoutSider>\n <MLayoutContent embedded content-style=\"padding: var(--m-space-4)\">\n <p style=\"margin: 0; color: var(--m-color-text-muted); font-size: var(--m-font-size-sm)\">\n 当前选中:<strong style=\"color: var(--m-color-text)\">{{ selectedKey }}</strong>\n </p>\n </MLayoutContent>\n </MLayout>\n </MLayout>\n</template>\n```\n\n## 水平菜单\n\n`mode=\"horizontal\"` 用于顶栏一级导航;子菜单经 `MPopover` 以下拉飞出层展示(Teleport + 主题滚动条),选中后自动关闭。`popup` 模式的主菜单列表同样内置 `MScrollbar`。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst selectedKey = ref('home')\nconst model = [\n { key: 'home', label: '首页', icon: 'home' },\n {\n key: 'products',\n label: '产品',\n icon: 'box',\n items: [\n { key: 'cloud', label: '云服务', icon: 'cloud' },\n { key: 'edge', label: '边缘计算', icon: 'server' },\n ],\n },\n { key: 'about', label: '关于', icon: 'info-circle' },\n]\n</script>\n\n<template>\n <div\n style=\"\n padding: 0 var(--m-space-2);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <MMenu v-model:selected-key=\"selectedKey\" :model=\"model\" mode=\"horizontal\" embedded />\n </div>\n</template>\n```\n\n## 反色(深色侧栏)\n\n`inverted` 配合 `MLayoutSider` 的 `inverted`,用于深色背景侧栏。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MLayout, MLayoutSider, MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst selectedKey = ref('dashboard')\nconst model = [\n { key: 'dashboard', label: '仪表盘', icon: 'layout-dashboard' },\n { key: 'users', label: '用户', icon: 'users' },\n]\n</script>\n\n<template>\n <MLayout\n has-sider\n style=\"\n height: 10rem;\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n overflow: hidden;\n \"\n >\n <MLayoutSider inverted bordered style=\"width: 12rem\">\n <MMenu v-model:selected-key=\"selectedKey\" :model=\"model\" inverted />\n </MLayoutSider>\n </MLayout>\n</template>\n```\n\n## 弹出模式\n\n`popup` + `v-model` 将菜单作为浮层,默认 Teleport 到 `body` 并相对**默认插槽触发器**定位(无插槽时回退到最后一次指针位置)。点击外部或选中叶子项后关闭。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MButton, MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst open = ref(false)\nconst model = [\n { label: '复制', command: () => undefined },\n { separator: true },\n { label: '删除', disabled: true },\n]\n</script>\n\n<template>\n <MMenu v-model=\"open\" popup :model=\"model\">\n <MButton label=\"更多操作\" @click=\"open = !open\" />\n </MMenu>\n</template>\n```\n\n## Props\n\n| 参数 | 类型 | 默认值 | 说明 |\n| --- | --- | --- | --- |\n| `model` | `MenuItem[]` | — | 菜单项,可嵌套 `items`。 |\n| `popup` | `boolean` | `false` | 浮层模式;配合 `v-model` 控制显隐。 |\n| `modelValue` | `boolean` | `false` | popup 可见性(`v-model`)。 |\n| `placement` | `'bottom-start' \\| 'bottom-end' \\| 'top-start' \\| 'top-end'` | `'bottom-start'` | popup 相对触发器的位置。 |\n| `selectedKey` | `string \\| null` | `null` | 当前选中项 key(`v-model:selected-key`)。 |\n| `collapsed` | `boolean` | `false` | 图标模式;子菜单以右侧飞出层展示。 |\n| `collapsedWidth` | `number` | `80` | 折叠宽度(px),用于居中图标。 |\n| `indent` | `number` | `12` | 每层额外左内边距(px)。 |\n| `rootIndent` | `number` | `16` | 根级左内边距(px)。 |\n| `accordion` | `boolean` | `false` | 手风琴:同时只展开一个一级子菜单。 |\n| `defaultExpandedKeys` | `string[]` | `[]` | 默认展开的 submenu keys。 |\n| `expandedKeys` | `string[]` | — | 受控展开 keys(`v-model:expanded-keys`)。 |\n| `defaultExpandAll` | `boolean` | `false` | 初始展开全部子菜单。 |\n| `mode` | `'vertical' \\| 'horizontal'` | `'vertical'` | 布局方向。 |\n| `inverted` | `boolean` | `false` | 反色样式,适合深色侧栏。 |\n| `embedded` | `boolean` | `!popup` | 嵌入布局:去边框与最小宽度。 |\n| `teleport` | `boolean` | `true` | popup 时 Teleport 到 `appendTo`。 |\n| `appendTo` | `string \\| HTMLElement \\| 'self' \\| false` | `'body'` | Teleport 目标;未传时使用 ConfigProvider。 |\n| `pt` | [RootPassThrough](/docs/types#RootPassThrough) `{ root? }` | — | DOM 透传,见 [样式与 attrs](/docs/attrs). |\n\n\n## Events\n\n| 事件名 | 参数 | 说明 |\n| --- | --- | --- |\n| `update:modelValue` | `boolean` | popup 可见性变化。 |\n| `update:selectedKey` | `string \\| null` | 选中项变化。 |\n| `update:expandedKeys` | `string[]` | 展开项变化。 |\n| `select` | `MenuItem` | 点击叶子项(非 disabled / separator)。 |\n\n## Slots\n\n| 插槽 | 说明 |\n| --- | --- |\n| `default` | popup 模式的触发器锚点(如按钮);菜单相对其定位。 |\n\n## MenuItem\n\n| 字段 | 类型 | 说明 |\n| --- | --- | --- |\n| `key` | `string` | 唯一标识;未传时使用 `label`。 |\n| `label` | `string` | 展示文本。 |\n| `icon` | `string` | [Tabler 图标名](/components/Icon) 或字符。 |\n| `command` | `() => void` | 点击回调(与 `select` 事件同时触发)。 |\n| `disabled` | `boolean` | 禁用。 |\n| `separator` | `boolean` | 分隔线(忽略其他字段)。 |\n| `items` | `MenuItem[]` | 子菜单。 |\n\n## 类型\n\n<h4 id=\"MenuItem\">MenuItem</h4>\n\n`model` 数组项,支持嵌套:\n\n```ts\ninterface MenuItem {\n key?: string\n label?: string\n icon?: string\n to?: string | RouteLocationRaw // 有 vue-router 时用 RouterLink\n command?: () => void\n disabled?: boolean\n separator?: boolean\n items?: MenuItem[]\n}\n```\n\n`key` 未传时用 `label` 作为标识。`separator: true` 时渲染分隔线。更多见 [API 类型](/docs/types)。\n"
12708
12728
  },
12709
12729
  "en-US": {
12710
12730
  "title": "Menu",
@@ -12738,7 +12758,7 @@
12738
12758
  {
12739
12759
  "id": "collapsed-flyout",
12740
12760
  "title": "Collapsed & flyout",
12741
- "body": "`collapsed` hides labels and keeps icons. Hover shows an `MTooltip` with the label; groups also open a right flyout (`.m-menu--flyout`). Set `collapsed-width` to match the sider width for centered icons.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst selectedKey = ref('home')\nconst model = [\n {\n key: 'workspace',\n label: 'Workspace',\n icon: 'folder',\n items: [\n { key: 'home', label: 'Home', icon: 'home' },\n { key: 'docs', label: 'Docs', icon: 'file-text' },\n ],\n },\n { key: 'settings', label: 'Settings', icon: 'settings' },\n]\n</script>\n\n<template>\n <div style=\"display: flex; gap: var(--m-space-4); align-items: stretch\">\n <div\n style=\"\n flex: 1;\n min-width: 0;\n padding: var(--m-space-3);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <p\n style=\"\n margin: 0 0 var(--m-space-3);\n font-size: var(--m-font-size-xs);\n color: var(--m-color-text-muted);\n \"\n >\n Expanded\n </p>\n <MMenu v-model:selected-key=\"selectedKey\" :model=\"model\" embedded />\n </div>\n <div\n style=\"\n width: 4.5rem;\n padding: var(--m-space-3) var(--m-space-2);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <p\n style=\"\n margin: 0 0 var(--m-space-3);\n font-size: var(--m-font-size-xs);\n color: var(--m-color-text-muted);\n text-align: center;\n \"\n >\n Collapsed\n </p>\n <MMenu\n v-model:selected-key=\"selectedKey\"\n :model=\"model\"\n collapsed\n embedded\n :collapsed-width=\"64\"\n />\n </div>\n </div>\n</template>\n```"
12761
+ "body": "`collapsed` hides labels and keeps icons. Hover shows an `MTooltip` with the label; groups also open a right flyout (`.m-menu--flyout`) via `MPopover` teleported to `body`, so it is not clipped by sider scroll regions. Set `collapsed-width` to match the sider width for centered icons.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst selectedKey = ref('home')\nconst model = [\n {\n key: 'workspace',\n label: 'Workspace',\n icon: 'folder',\n items: [\n { key: 'home', label: 'Home', icon: 'home' },\n { key: 'docs', label: 'Docs', icon: 'file-text' },\n ],\n },\n { key: 'settings', label: 'Settings', icon: 'settings' },\n]\n</script>\n\n<template>\n <div style=\"display: flex; gap: var(--m-space-4); align-items: stretch\">\n <div\n style=\"\n flex: 1;\n min-width: 0;\n padding: var(--m-space-3);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <p\n style=\"\n margin: 0 0 var(--m-space-3);\n font-size: var(--m-font-size-xs);\n color: var(--m-color-text-muted);\n \"\n >\n Expanded\n </p>\n <MMenu v-model:selected-key=\"selectedKey\" :model=\"model\" embedded />\n </div>\n <div\n style=\"\n width: 4.5rem;\n padding: var(--m-space-3) var(--m-space-2);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <p\n style=\"\n margin: 0 0 var(--m-space-3);\n font-size: var(--m-font-size-xs);\n color: var(--m-color-text-muted);\n text-align: center;\n \"\n >\n Collapsed\n </p>\n <MMenu\n v-model:selected-key=\"selectedKey\"\n :model=\"model\"\n collapsed\n embedded\n :collapsed-width=\"64\"\n />\n </div>\n </div>\n</template>\n```"
12742
12762
  },
12743
12763
  {
12744
12764
  "id": "embed-in-layout-sider",
@@ -12748,7 +12768,7 @@
12748
12768
  {
12749
12769
  "id": "horizontal-mode",
12750
12770
  "title": "Horizontal mode",
12751
- "body": "`mode=\"horizontal\"` for top nav bars. Submenus open in a dropdown flyout and close after selection.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst selectedKey = ref('home')\nconst model = [\n { key: 'home', label: 'Home', icon: 'home' },\n {\n key: 'products',\n label: 'Products',\n icon: 'box',\n items: [\n { key: 'cloud', label: 'Cloud', icon: 'cloud' },\n { key: 'edge', label: 'Edge', icon: 'server' },\n ],\n },\n { key: 'about', label: 'About', icon: 'info-circle' },\n]\n</script>\n\n<template>\n <div\n style=\"\n padding: 0 var(--m-space-2);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <MMenu v-model:selected-key=\"selectedKey\" :model=\"model\" mode=\"horizontal\" embedded />\n </div>\n</template>\n```"
12771
+ "body": "`mode=\"horizontal\"` for top nav bars. Submenus open in a `MPopover` dropdown flyout (teleported, themed scrollbar) and close after selection. Popup mode root menus also use built-in `MScrollbar`.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst selectedKey = ref('home')\nconst model = [\n { key: 'home', label: 'Home', icon: 'home' },\n {\n key: 'products',\n label: 'Products',\n icon: 'box',\n items: [\n { key: 'cloud', label: 'Cloud', icon: 'cloud' },\n { key: 'edge', label: 'Edge', icon: 'server' },\n ],\n },\n { key: 'about', label: 'About', icon: 'info-circle' },\n]\n</script>\n\n<template>\n <div\n style=\"\n padding: 0 var(--m-space-2);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <MMenu v-model:selected-key=\"selectedKey\" :model=\"model\" mode=\"horizontal\" embedded />\n </div>\n</template>\n```"
12752
12772
  },
12753
12773
  {
12754
12774
  "id": "inverted-dark-sider",
@@ -12786,7 +12806,7 @@
12786
12806
  "body": "<h4 id=\"MenuItem\">MenuItem</h4>\n\nEach entry in `model`; supports nesting:\n\n```ts\ninterface MenuItem {\n key?: string\n label?: string\n icon?: string\n to?: string | RouteLocationRaw // RouterLink when vue-router is installed\n command?: () => void\n disabled?: boolean\n separator?: boolean\n items?: MenuItem[]\n}\n```\n\nWhen `key` is omitted, `label` is used as the identifier. `separator: true` renders a divider. See also [API types](/docs/types)."
12787
12807
  }
12788
12808
  ],
12789
- "markdown": "---\ntitle: Menu\ncategory: 04 / NAVIGATION\ndescription: Vertical/horizontal navigation menu with nested items, controlled selection, accordion, and collapsed flyout submenus.\n---\n\n# Menu\n\nNavigation menu rendered from a `model`. Typical uses: admin sidebar, top navigation.\n\n- Nested `items` with controlled `selectedKey`\n- Auto-expand ancestor path when selection changes; optional `accordion`\n- `collapsed` icon rail with right-side flyout submenus (Popover)\n- Non-popup menus default to `embedded` (borderless, full-width in `MLayoutSider`)\n\n> One-level hover submenus: [TieredMenu](/components/TieredMenu). Top bar: [Menubar](/components/Menubar). Action lists: [Dropdown](/components/Dropdown).\n\n## Import\n\n```ts\nimport type {MenuItem} from 'morya-ui';\nimport { MMenu } from 'morya-ui'\n```\n\n## Selection\n\nGive leaf items stable `key` values; sync with routing via `v-model:selected-key`. Emits `select` on click.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst selectedKey = ref('dashboard')\nconst model = [\n { key: 'dashboard', label: 'Dashboard', icon: 'layout-dashboard' },\n { key: 'users', label: 'Users', icon: 'users' },\n { key: 'settings', label: 'Settings', icon: 'settings', disabled: true },\n]\n</script>\n\n<template>\n <div\n style=\"\n width: 15rem;\n padding: var(--m-space-3);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <MMenu\n v-model:selected-key=\"selectedKey\"\n :model=\"model\"\n embedded\n @select=\"(item) => console.log('select', item.key)\"\n />\n </div>\n</template>\n```\n\nIf `key` is omitted, `label` is used as fallback. Prefer explicit keys in production.\n\n## Nested submenus\n\nClick a group to expand/collapse. When a child is selected, the parent shows `child-active` styling.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst selectedKey = ref('reports')\nconst model = [\n {\n key: 'analytics',\n label: 'Analytics',\n icon: 'chart-bar',\n items: [\n { key: 'reports', label: 'Reports', icon: 'file-text' },\n { key: 'monitor', label: 'Monitor', icon: 'activity' },\n ],\n },\n { key: 'settings', label: 'Settings', icon: 'settings' },\n]\n</script>\n\n<template>\n <div\n style=\"\n width: 16rem;\n padding: var(--m-space-3);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <MMenu v-model:selected-key=\"selectedKey\" :model=\"model\" embedded />\n </div>\n</template>\n```\n\n## Accordion & expanded keys\n\n`accordion` keeps at most one top-level group open. Use `defaultExpandedKeys` or `v-model:expanded-keys` for controlled expansion. Changing `selectedKey` auto-expands its ancestor path.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst selectedKey = ref('a1')\nconst model = [\n { key: 'a', label: 'Module A', items: [{ key: 'a1', label: 'A-1' }] },\n { key: 'b', label: 'Module B', items: [{ key: 'b1', label: 'B-1' }] },\n]\n</script>\n\n<template>\n <div\n style=\"\n width: 14rem;\n padding: var(--m-space-3);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <MMenu v-model:selected-key=\"selectedKey\" :model=\"model\" accordion embedded />\n </div>\n</template>\n```\n\n## Collapsed & flyout\n\n`collapsed` hides labels and keeps icons. Hover shows an `MTooltip` with the label; groups also open a right flyout (`.m-menu--flyout`). Set `collapsed-width` to match the sider width for centered icons.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst selectedKey = ref('home')\nconst model = [\n {\n key: 'workspace',\n label: 'Workspace',\n icon: 'folder',\n items: [\n { key: 'home', label: 'Home', icon: 'home' },\n { key: 'docs', label: 'Docs', icon: 'file-text' },\n ],\n },\n { key: 'settings', label: 'Settings', icon: 'settings' },\n]\n</script>\n\n<template>\n <div style=\"display: flex; gap: var(--m-space-4); align-items: stretch\">\n <div\n style=\"\n flex: 1;\n min-width: 0;\n padding: var(--m-space-3);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <p\n style=\"\n margin: 0 0 var(--m-space-3);\n font-size: var(--m-font-size-xs);\n color: var(--m-color-text-muted);\n \"\n >\n Expanded\n </p>\n <MMenu v-model:selected-key=\"selectedKey\" :model=\"model\" embedded />\n </div>\n <div\n style=\"\n width: 4.5rem;\n padding: var(--m-space-3) var(--m-space-2);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <p\n style=\"\n margin: 0 0 var(--m-space-3);\n font-size: var(--m-font-size-xs);\n color: var(--m-color-text-muted);\n text-align: center;\n \"\n >\n Collapsed\n </p>\n <MMenu\n v-model:selected-key=\"selectedKey\"\n :model=\"model\"\n collapsed\n embedded\n :collapsed-width=\"64\"\n />\n </div>\n </div>\n</template>\n```\n\n## Embed in Layout sider\n\nRecommended shell: **global Header + inner `has-sider` Layout**. Bind menu `collapsed` to `MLayoutSider`.\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MLayout,\n MLayoutContent,\n MLayoutHeader,\n MLayoutSider,\n MMenu,\n} from 'morya-ui'\nimport { ref } from 'vue'\n\nconst collapsed = ref(false)\nconst selectedKey = ref('dashboard')\nconst model = [\n { key: 'dashboard', label: 'Dashboard', icon: 'layout-dashboard' },\n {\n key: 'system',\n label: 'System',\n icon: 'settings',\n items: [\n { key: 'users', label: 'Users', icon: 'users' },\n { key: 'roles', label: 'Roles', icon: 'shield' },\n ],\n },\n]\n</script>\n\n<template>\n <MLayout\n style=\"\n height: 14rem;\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n overflow: hidden;\n \"\n >\n <MLayoutHeader\n bordered\n style=\"\n padding: 0 var(--m-space-4);\n display: flex;\n align-items: center;\n min-height: var(--m-layout-header-height);\n \"\n >\n <strong style=\"color: var(--m-color-primary); font-size: var(--m-font-size-md)\">Morya UI</strong>\n </MLayoutHeader>\n <MLayout has-sider>\n <MLayoutSider\n v-model:collapsed=\"collapsed\"\n bordered\n show-trigger=\"arrow-circle\"\n collapse-mode=\"width\"\n :collapsed-width=\"64\"\n >\n <MMenu\n v-model:selected-key=\"selectedKey\"\n :model=\"model\"\n :collapsed=\"collapsed\"\n :collapsed-width=\"64\"\n accordion\n />\n </MLayoutSider>\n <MLayoutContent embedded content-style=\"padding: var(--m-space-4)\">\n <p style=\"margin: 0; color: var(--m-color-text-muted); font-size: var(--m-font-size-sm)\">\n Selected: <strong style=\"color: var(--m-color-text)\">{{ selectedKey }}</strong>\n </p>\n </MLayoutContent>\n </MLayout>\n </MLayout>\n</template>\n```\n\n## Horizontal mode\n\n`mode=\"horizontal\"` for top nav bars. Submenus open in a dropdown flyout and close after selection.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst selectedKey = ref('home')\nconst model = [\n { key: 'home', label: 'Home', icon: 'home' },\n {\n key: 'products',\n label: 'Products',\n icon: 'box',\n items: [\n { key: 'cloud', label: 'Cloud', icon: 'cloud' },\n { key: 'edge', label: 'Edge', icon: 'server' },\n ],\n },\n { key: 'about', label: 'About', icon: 'info-circle' },\n]\n</script>\n\n<template>\n <div\n style=\"\n padding: 0 var(--m-space-2);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <MMenu v-model:selected-key=\"selectedKey\" :model=\"model\" mode=\"horizontal\" embedded />\n </div>\n</template>\n```\n\n## Inverted (dark sider)\n\nUse `inverted` with `MLayoutSider`'s `inverted` on dark backgrounds.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MLayout, MLayoutSider, MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst selectedKey = ref('dashboard')\nconst model = [\n { key: 'dashboard', label: 'Dashboard', icon: 'layout-dashboard' },\n { key: 'users', label: 'Users', icon: 'users' },\n]\n</script>\n\n<template>\n <MLayout\n has-sider\n style=\"\n height: 10rem;\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n overflow: hidden;\n \"\n >\n <MLayoutSider inverted bordered style=\"width: 12rem\">\n <MMenu v-model:selected-key=\"selectedKey\" :model=\"model\" inverted />\n </MLayoutSider>\n </MLayout>\n</template>\n```\n\n## Popup mode\n\n`popup` + `v-model` renders a floating menu, teleported to `body` by default and positioned against the **default-slot trigger** (falls back to the last pointer position when no slot). Closes on outside click or leaf selection.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MButton, MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst open = ref(false)\nconst model = [\n { label: 'Copy', command: () => undefined },\n { separator: true },\n { label: 'Delete', disabled: true },\n]\n</script>\n\n<template>\n <MMenu v-model=\"open\" popup :model=\"model\">\n <MButton label=\"More\" @click=\"open = !open\" />\n </MMenu>\n</template>\n```\n\n## Props\n\n| Prop | Type | Default | Description |\n| --- | --- | --- | --- |\n| `model` | `MenuItem[]` | — | Menu items; may nest `items`. |\n| `popup` | `boolean` | `false` | Overlay mode; use with `v-model`. |\n| `modelValue` | `boolean` | `false` | Popup visibility (`v-model`). |\n| `placement` | `'bottom-start' \\| 'bottom-end' \\| 'top-start' \\| 'top-end'` | `'bottom-start'` | Popup position relative to the trigger. |\n| `selectedKey` | `string \\| null` | `null` | Selected item key (`v-model:selected-key`). |\n| `collapsed` | `boolean` | `false` | Icon-only; submenus in right flyout. |\n| `collapsedWidth` | `number` | `80` | Collapsed width (px) for icon centering. |\n| `indent` | `number` | `12` | Extra padding-left per level (px). |\n| `rootIndent` | `number` | `16` | Root item padding-left (px). |\n| `accordion` | `boolean` | `false` | Only one top-level submenu open at a time. |\n| `defaultExpandedKeys` | `string[]` | `[]` | Initially expanded submenu keys. |\n| `expandedKeys` | `string[]` | — | Controlled expanded keys (`v-model:expanded-keys`). |\n| `defaultExpandAll` | `boolean` | `false` | Expand all submenus initially. |\n| `mode` | `'vertical' \\| 'horizontal'` | `'vertical'` | Layout direction. |\n| `inverted` | `boolean` | `false` | Inverted colors for dark sider. |\n| `embedded` | `boolean` | `!popup` | Embed in layout (no border/min-width). |\n| `teleport` | `boolean` | `true` | Teleport popup to `appendTo`. |\n| `appendTo` | `string \\| HTMLElement \\| 'self' \\| false` | `'body'` | Teleport target; falls back to ConfigProvider. |\n| `pt` | [RootPassThrough](/docs/types#RootPassThrough) `{ root? }` | — | Pass-through; see [Styling & attrs](/docs/attrs). |\n\n\n## Events\n\n| Event | Payload | Description |\n| --- | --- | --- |\n| `update:modelValue` | `boolean` | Popup visibility changed. |\n| `update:selectedKey` | `string \\| null` | Selected item changed. |\n| `update:expandedKeys` | `string[]` | Expanded keys changed. |\n| `select` | `MenuItem` | Leaf clicked (not disabled / separator). |\n\n## Slots\n\n| Slot | Description |\n| --- | --- |\n| `default` | Popup trigger anchor (e.g. a button); menu positions against it. |\n\n## MenuItem\n\n| Field | Type | Description |\n| --- | --- | --- |\n| `key` | `string` | Unique id; falls back to `label`. |\n| `label` | `string` | Display text. |\n| `icon` | `string` | [Tabler icon name](/components/Icon) or character. |\n| `command` | `() => void` | Click handler (also emits `select`). |\n| `disabled` | `boolean` | Disabled state. |\n| `separator` | `boolean` | Separator line (ignores other fields). |\n| `items` | `MenuItem[]` | Child menu items. |\n\n## Types\n\n<h4 id=\"MenuItem\">MenuItem</h4>\n\nEach entry in `model`; supports nesting:\n\n```ts\ninterface MenuItem {\n key?: string\n label?: string\n icon?: string\n to?: string | RouteLocationRaw // RouterLink when vue-router is installed\n command?: () => void\n disabled?: boolean\n separator?: boolean\n items?: MenuItem[]\n}\n```\n\nWhen `key` is omitted, `label` is used as the identifier. `separator: true` renders a divider. See also [API types](/docs/types).\n"
12809
+ "markdown": "---\ntitle: Menu\ncategory: 04 / NAVIGATION\ndescription: Vertical/horizontal navigation menu with nested items, controlled selection, accordion, and collapsed flyout submenus.\n---\n\n# Menu\n\nNavigation menu rendered from a `model`. Typical uses: admin sidebar, top navigation.\n\n- Nested `items` with controlled `selectedKey`\n- Auto-expand ancestor path when selection changes; optional `accordion`\n- `collapsed` icon rail with right-side flyout submenus (Popover)\n- Non-popup menus default to `embedded` (borderless, full-width in `MLayoutSider`)\n\n> One-level hover submenus: [TieredMenu](/components/TieredMenu). Top bar: [Menubar](/components/Menubar). Action lists: [Dropdown](/components/Dropdown).\n\n## Import\n\n```ts\nimport type {MenuItem} from 'morya-ui';\nimport { MMenu } from 'morya-ui'\n```\n\n## Selection\n\nGive leaf items stable `key` values; sync with routing via `v-model:selected-key`. Emits `select` on click.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst selectedKey = ref('dashboard')\nconst model = [\n { key: 'dashboard', label: 'Dashboard', icon: 'layout-dashboard' },\n { key: 'users', label: 'Users', icon: 'users' },\n { key: 'settings', label: 'Settings', icon: 'settings', disabled: true },\n]\n</script>\n\n<template>\n <div\n style=\"\n width: 15rem;\n padding: var(--m-space-3);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <MMenu\n v-model:selected-key=\"selectedKey\"\n :model=\"model\"\n embedded\n @select=\"(item) => console.log('select', item.key)\"\n />\n </div>\n</template>\n```\n\nIf `key` is omitted, `label` is used as fallback. Prefer explicit keys in production.\n\n## Nested submenus\n\nClick a group to expand/collapse. When a child is selected, the parent shows `child-active` styling.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst selectedKey = ref('reports')\nconst model = [\n {\n key: 'analytics',\n label: 'Analytics',\n icon: 'chart-bar',\n items: [\n { key: 'reports', label: 'Reports', icon: 'file-text' },\n { key: 'monitor', label: 'Monitor', icon: 'activity' },\n ],\n },\n { key: 'settings', label: 'Settings', icon: 'settings' },\n]\n</script>\n\n<template>\n <div\n style=\"\n width: 16rem;\n padding: var(--m-space-3);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <MMenu v-model:selected-key=\"selectedKey\" :model=\"model\" embedded />\n </div>\n</template>\n```\n\n## Accordion & expanded keys\n\n`accordion` keeps at most one top-level group open. Use `defaultExpandedKeys` or `v-model:expanded-keys` for controlled expansion. Changing `selectedKey` auto-expands its ancestor path.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst selectedKey = ref('a1')\nconst model = [\n { key: 'a', label: 'Module A', items: [{ key: 'a1', label: 'A-1' }] },\n { key: 'b', label: 'Module B', items: [{ key: 'b1', label: 'B-1' }] },\n]\n</script>\n\n<template>\n <div\n style=\"\n width: 14rem;\n padding: var(--m-space-3);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <MMenu v-model:selected-key=\"selectedKey\" :model=\"model\" accordion embedded />\n </div>\n</template>\n```\n\n## Collapsed & flyout\n\n`collapsed` hides labels and keeps icons. Hover shows an `MTooltip` with the label; groups also open a right flyout (`.m-menu--flyout`) via `MPopover` teleported to `body`, so it is not clipped by sider scroll regions. Set `collapsed-width` to match the sider width for centered icons.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst selectedKey = ref('home')\nconst model = [\n {\n key: 'workspace',\n label: 'Workspace',\n icon: 'folder',\n items: [\n { key: 'home', label: 'Home', icon: 'home' },\n { key: 'docs', label: 'Docs', icon: 'file-text' },\n ],\n },\n { key: 'settings', label: 'Settings', icon: 'settings' },\n]\n</script>\n\n<template>\n <div style=\"display: flex; gap: var(--m-space-4); align-items: stretch\">\n <div\n style=\"\n flex: 1;\n min-width: 0;\n padding: var(--m-space-3);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <p\n style=\"\n margin: 0 0 var(--m-space-3);\n font-size: var(--m-font-size-xs);\n color: var(--m-color-text-muted);\n \"\n >\n Expanded\n </p>\n <MMenu v-model:selected-key=\"selectedKey\" :model=\"model\" embedded />\n </div>\n <div\n style=\"\n width: 4.5rem;\n padding: var(--m-space-3) var(--m-space-2);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <p\n style=\"\n margin: 0 0 var(--m-space-3);\n font-size: var(--m-font-size-xs);\n color: var(--m-color-text-muted);\n text-align: center;\n \"\n >\n Collapsed\n </p>\n <MMenu\n v-model:selected-key=\"selectedKey\"\n :model=\"model\"\n collapsed\n embedded\n :collapsed-width=\"64\"\n />\n </div>\n </div>\n</template>\n```\n\n## Embed in Layout sider\n\nRecommended shell: **global Header + inner `has-sider` Layout**. Bind menu `collapsed` to `MLayoutSider`.\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MLayout,\n MLayoutContent,\n MLayoutHeader,\n MLayoutSider,\n MMenu,\n} from 'morya-ui'\nimport { ref } from 'vue'\n\nconst collapsed = ref(false)\nconst selectedKey = ref('dashboard')\nconst model = [\n { key: 'dashboard', label: 'Dashboard', icon: 'layout-dashboard' },\n {\n key: 'system',\n label: 'System',\n icon: 'settings',\n items: [\n { key: 'users', label: 'Users', icon: 'users' },\n { key: 'roles', label: 'Roles', icon: 'shield' },\n ],\n },\n]\n</script>\n\n<template>\n <MLayout\n style=\"\n height: 14rem;\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n overflow: hidden;\n \"\n >\n <MLayoutHeader\n bordered\n style=\"\n padding: 0 var(--m-space-4);\n display: flex;\n align-items: center;\n min-height: var(--m-layout-header-height);\n \"\n >\n <strong style=\"color: var(--m-color-primary); font-size: var(--m-font-size-md)\">Morya UI</strong>\n </MLayoutHeader>\n <MLayout has-sider>\n <MLayoutSider\n v-model:collapsed=\"collapsed\"\n bordered\n show-trigger=\"arrow-circle\"\n collapse-mode=\"width\"\n :collapsed-width=\"64\"\n >\n <MMenu\n v-model:selected-key=\"selectedKey\"\n :model=\"model\"\n :collapsed=\"collapsed\"\n :collapsed-width=\"64\"\n accordion\n />\n </MLayoutSider>\n <MLayoutContent embedded content-style=\"padding: var(--m-space-4)\">\n <p style=\"margin: 0; color: var(--m-color-text-muted); font-size: var(--m-font-size-sm)\">\n Selected: <strong style=\"color: var(--m-color-text)\">{{ selectedKey }}</strong>\n </p>\n </MLayoutContent>\n </MLayout>\n </MLayout>\n</template>\n```\n\n## Horizontal mode\n\n`mode=\"horizontal\"` for top nav bars. Submenus open in a `MPopover` dropdown flyout (teleported, themed scrollbar) and close after selection. Popup mode root menus also use built-in `MScrollbar`.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst selectedKey = ref('home')\nconst model = [\n { key: 'home', label: 'Home', icon: 'home' },\n {\n key: 'products',\n label: 'Products',\n icon: 'box',\n items: [\n { key: 'cloud', label: 'Cloud', icon: 'cloud' },\n { key: 'edge', label: 'Edge', icon: 'server' },\n ],\n },\n { key: 'about', label: 'About', icon: 'info-circle' },\n]\n</script>\n\n<template>\n <div\n style=\"\n padding: 0 var(--m-space-2);\n background: var(--m-color-surface);\n border: 1px solid var(--m-color-border);\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n \"\n >\n <MMenu v-model:selected-key=\"selectedKey\" :model=\"model\" mode=\"horizontal\" embedded />\n </div>\n</template>\n```\n\n## Inverted (dark sider)\n\nUse `inverted` with `MLayoutSider`'s `inverted` on dark backgrounds.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MLayout, MLayoutSider, MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst selectedKey = ref('dashboard')\nconst model = [\n { key: 'dashboard', label: 'Dashboard', icon: 'layout-dashboard' },\n { key: 'users', label: 'Users', icon: 'users' },\n]\n</script>\n\n<template>\n <MLayout\n has-sider\n style=\"\n height: 10rem;\n border-radius: var(--m-radius-lg);\n box-shadow: var(--m-shadow-sm);\n overflow: hidden;\n \"\n >\n <MLayoutSider inverted bordered style=\"width: 12rem\">\n <MMenu v-model:selected-key=\"selectedKey\" :model=\"model\" inverted />\n </MLayoutSider>\n </MLayout>\n</template>\n```\n\n## Popup mode\n\n`popup` + `v-model` renders a floating menu, teleported to `body` by default and positioned against the **default-slot trigger** (falls back to the last pointer position when no slot). Closes on outside click or leaf selection.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MButton, MMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst open = ref(false)\nconst model = [\n { label: 'Copy', command: () => undefined },\n { separator: true },\n { label: 'Delete', disabled: true },\n]\n</script>\n\n<template>\n <MMenu v-model=\"open\" popup :model=\"model\">\n <MButton label=\"More\" @click=\"open = !open\" />\n </MMenu>\n</template>\n```\n\n## Props\n\n| Prop | Type | Default | Description |\n| --- | --- | --- | --- |\n| `model` | `MenuItem[]` | — | Menu items; may nest `items`. |\n| `popup` | `boolean` | `false` | Overlay mode; use with `v-model`. |\n| `modelValue` | `boolean` | `false` | Popup visibility (`v-model`). |\n| `placement` | `'bottom-start' \\| 'bottom-end' \\| 'top-start' \\| 'top-end'` | `'bottom-start'` | Popup position relative to the trigger. |\n| `selectedKey` | `string \\| null` | `null` | Selected item key (`v-model:selected-key`). |\n| `collapsed` | `boolean` | `false` | Icon-only; submenus in right flyout. |\n| `collapsedWidth` | `number` | `80` | Collapsed width (px) for icon centering. |\n| `indent` | `number` | `12` | Extra padding-left per level (px). |\n| `rootIndent` | `number` | `16` | Root item padding-left (px). |\n| `accordion` | `boolean` | `false` | Only one top-level submenu open at a time. |\n| `defaultExpandedKeys` | `string[]` | `[]` | Initially expanded submenu keys. |\n| `expandedKeys` | `string[]` | — | Controlled expanded keys (`v-model:expanded-keys`). |\n| `defaultExpandAll` | `boolean` | `false` | Expand all submenus initially. |\n| `mode` | `'vertical' \\| 'horizontal'` | `'vertical'` | Layout direction. |\n| `inverted` | `boolean` | `false` | Inverted colors for dark sider. |\n| `embedded` | `boolean` | `!popup` | Embed in layout (no border/min-width). |\n| `teleport` | `boolean` | `true` | Teleport popup to `appendTo`. |\n| `appendTo` | `string \\| HTMLElement \\| 'self' \\| false` | `'body'` | Teleport target; falls back to ConfigProvider. |\n| `pt` | [RootPassThrough](/docs/types#RootPassThrough) `{ root? }` | — | Pass-through; see [Styling & attrs](/docs/attrs). |\n\n\n## Events\n\n| Event | Payload | Description |\n| --- | --- | --- |\n| `update:modelValue` | `boolean` | Popup visibility changed. |\n| `update:selectedKey` | `string \\| null` | Selected item changed. |\n| `update:expandedKeys` | `string[]` | Expanded keys changed. |\n| `select` | `MenuItem` | Leaf clicked (not disabled / separator). |\n\n## Slots\n\n| Slot | Description |\n| --- | --- |\n| `default` | Popup trigger anchor (e.g. a button); menu positions against it. |\n\n## MenuItem\n\n| Field | Type | Description |\n| --- | --- | --- |\n| `key` | `string` | Unique id; falls back to `label`. |\n| `label` | `string` | Display text. |\n| `icon` | `string` | [Tabler icon name](/components/Icon) or character. |\n| `command` | `() => void` | Click handler (also emits `select`). |\n| `disabled` | `boolean` | Disabled state. |\n| `separator` | `boolean` | Separator line (ignores other fields). |\n| `items` | `MenuItem[]` | Child menu items. |\n\n## Types\n\n<h4 id=\"MenuItem\">MenuItem</h4>\n\nEach entry in `model`; supports nesting:\n\n```ts\ninterface MenuItem {\n key?: string\n label?: string\n icon?: string\n to?: string | RouteLocationRaw // RouterLink when vue-router is installed\n command?: () => void\n disabled?: boolean\n separator?: boolean\n items?: MenuItem[]\n}\n```\n\nWhen `key` is omitted, `label` is used as the identifier. `separator: true` renders a divider. See also [API types](/docs/types).\n"
12790
12810
  }
12791
12811
  }
12792
12812
  },
@@ -13553,6 +13573,150 @@
13553
13573
  }
13554
13574
  }
13555
13575
  },
13576
+ {
13577
+ "id": "Page",
13578
+ "name": "Page",
13579
+ "exportName": "MPageContent",
13580
+ "category": "06 / LAYOUT",
13581
+ "description": "业务页面组合区块,封装间距、边框与标题层级,少写或不写页面 CSS。",
13582
+ "descriptionEn": "Composable page sections for spacing, borders, and headings with little or no custom CSS.",
13583
+ "import": "import { MPageContent } from 'morya-ui'",
13584
+ "props": [],
13585
+ "events": [],
13586
+ "slots": [],
13587
+ "examples": [
13588
+ {
13589
+ "id": "引入-1",
13590
+ "section": "引入",
13591
+ "sectionId": "引入",
13592
+ "lang": "ts",
13593
+ "preview": false,
13594
+ "code": "import {\n MPageContent,\n MPageFilters,\n MPageHeader,\n MPagePlaceholder,\n MPageSection,\n MPageStat,\n MPageToolbar,\n} from 'morya-ui'",
13595
+ "locale": "zh-CN"
13596
+ },
13597
+ {
13598
+ "id": "列表页组合-1",
13599
+ "section": "列表页组合",
13600
+ "sectionId": "列表页组合",
13601
+ "lang": "vue",
13602
+ "preview": true,
13603
+ "code": "<script setup lang=\"ts\">\nimport {\n MButton,\n MInput,\n MPageContent,\n MPageFilters,\n MPageToolbar,\n MSpace,\n} from 'morya-ui'\nimport { ref } from 'vue'\n\nconst keyword = ref('')\n</script>\n\n<template>\n <div\n class=\"doc-demo-frame\"\n style=\"border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden;background:var(--m-color-surface)\"\n >\n <MPageContent>\n <MPageFilters aria-label=\"筛选\">\n <MSpace wrap>\n <MInput v-model=\"keyword\" placeholder=\"搜索\" clearable style=\"width:12rem\" />\n <MButton severity=\"primary\">查询</MButton>\n </MSpace>\n </MPageFilters>\n <MPageToolbar title=\"用户管理\">\n <template #actions>\n <MButton severity=\"primary\">新建</MButton>\n </template>\n </MPageToolbar>\n </MPageContent>\n </div>\n</template>",
13604
+ "locale": "zh-CN"
13605
+ },
13606
+ {
13607
+ "id": "表单页组合-1",
13608
+ "section": "表单页组合",
13609
+ "sectionId": "表单页组合",
13610
+ "lang": "vue",
13611
+ "preview": true,
13612
+ "code": "<script setup lang=\"ts\">\nimport {\n MButton,\n MForm,\n MFormItem,\n MInput,\n MPageContent,\n MPageHeader,\n MPageSection,\n MSpace,\n} from 'morya-ui'\nimport { ref } from 'vue'\n\nconst name = ref('')\n</script>\n\n<template>\n <div\n class=\"doc-demo-frame\"\n style=\"border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden;background:var(--m-color-surface)\"\n >\n <MPageContent width=\"narrow\">\n <MPageHeader title=\"新建用户\" description=\"填写基本信息。\" />\n <MPageSection variant=\"form\">\n <MForm @submit.prevent>\n <MFormItem label=\"姓名\" name=\"name\">\n <MInput v-model=\"name\" fluid />\n </MFormItem>\n <MPageSection variant=\"actions\">\n <MSpace>\n <MButton native-type=\"submit\" severity=\"primary\">保存</MButton>\n <MButton severity=\"secondary\">取消</MButton>\n </MSpace>\n </MPageSection>\n </MForm>\n </MPageSection>\n </MPageContent>\n </div>\n</template>",
13613
+ "locale": "zh-CN"
13614
+ },
13615
+ {
13616
+ "id": "仪表盘-kpi-1",
13617
+ "section": "仪表盘 KPI",
13618
+ "sectionId": "仪表盘-kpi",
13619
+ "lang": "vue",
13620
+ "preview": true,
13621
+ "code": "<script setup lang=\"ts\">\nimport { MPageContent, MPageStat } from 'morya-ui'\n</script>\n\n<template>\n <div\n class=\"doc-demo-frame\"\n style=\"border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden;background:var(--m-color-surface)\"\n >\n <MPageContent density=\"compact\">\n <MPageStat label=\"总用户\" value=\"12,480\" trend=\"+8.2%\" icon=\"users\" />\n </MPageContent>\n </div>\n</template>",
13622
+ "locale": "zh-CN"
13623
+ },
13624
+ {
13625
+ "id": "import-1",
13626
+ "section": "Import",
13627
+ "sectionId": "import",
13628
+ "lang": "ts",
13629
+ "preview": false,
13630
+ "code": "import {\n MPageContent,\n MPageFilters,\n MPageHeader,\n MPagePlaceholder,\n MPageSection,\n MPageStat,\n MPageToolbar,\n} from 'morya-ui'",
13631
+ "locale": "en-US"
13632
+ },
13633
+ {
13634
+ "id": "list-page-stack-1",
13635
+ "section": "List page stack",
13636
+ "sectionId": "list-page-stack",
13637
+ "lang": "vue",
13638
+ "preview": true,
13639
+ "code": "<script setup lang=\"ts\">\nimport {\n MButton,\n MInput,\n MPageContent,\n MPageFilters,\n MPageToolbar,\n MSpace,\n} from 'morya-ui'\nimport { ref } from 'vue'\n\nconst keyword = ref('')\n</script>\n\n<template>\n <div\n class=\"doc-demo-frame\"\n style=\"border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden;background:var(--m-color-surface)\"\n >\n <MPageContent>\n <MPageFilters aria-label=\"Filters\">\n <MSpace wrap>\n <MInput v-model=\"keyword\" placeholder=\"Search\" clearable style=\"width:12rem\" />\n <MButton severity=\"primary\">Search</MButton>\n </MSpace>\n </MPageFilters>\n <MPageToolbar title=\"Users\">\n <template #actions>\n <MButton severity=\"primary\">Create</MButton>\n </template>\n </MPageToolbar>\n </MPageContent>\n </div>\n</template>",
13640
+ "locale": "en-US"
13641
+ }
13642
+ ],
13643
+ "locales": {
13644
+ "zh-CN": {
13645
+ "title": "Page",
13646
+ "description": "业务页面组合区块,封装间距、边框与标题层级,少写或不写页面 CSS。",
13647
+ "sections": [
13648
+ {
13649
+ "id": "overview",
13650
+ "title": "",
13651
+ "body": "# Page\n\n业务页面组合组件。与 `MLayout` 搭配使用,把列表页筛选区、工具栏、表单分组、KPI 卡片等常见区块的间距和边框内聚到组件内,**优先用 Page 组件拼装页面,而不是每页手写 scoped CSS**。"
13652
+ },
13653
+ {
13654
+ "id": "引入",
13655
+ "title": "引入",
13656
+ "body": "```ts\nimport {\n MPageContent,\n MPageFilters,\n MPageHeader,\n MPagePlaceholder,\n MPageSection,\n MPageStat,\n MPageToolbar,\n} from 'morya-ui'\n```"
13657
+ },
13658
+ {
13659
+ "id": "列表页组合",
13660
+ "title": "列表页组合",
13661
+ "body": "`MPageContent` 负责内容区 **padding**(默认 `--m-space-6`)与子区块 **gap**(默认 `--m-space-4`),文档预览中外框仅模拟 `MLayoutContent` 边界。\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MButton,\n MInput,\n MPageContent,\n MPageFilters,\n MPageToolbar,\n MSpace,\n} from 'morya-ui'\nimport { ref } from 'vue'\n\nconst keyword = ref('')\n</script>\n\n<template>\n <div\n class=\"doc-demo-frame\"\n style=\"border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden;background:var(--m-color-surface)\"\n >\n <MPageContent>\n <MPageFilters aria-label=\"筛选\">\n <MSpace wrap>\n <MInput v-model=\"keyword\" placeholder=\"搜索\" clearable style=\"width:12rem\" />\n <MButton severity=\"primary\">查询</MButton>\n </MSpace>\n </MPageFilters>\n <MPageToolbar title=\"用户管理\">\n <template #actions>\n <MButton severity=\"primary\">新建</MButton>\n </template>\n </MPageToolbar>\n </MPageContent>\n </div>\n</template>\n```"
13662
+ },
13663
+ {
13664
+ "id": "表单页组合",
13665
+ "title": "表单页组合",
13666
+ "body": "`MPageContent width=\"narrow\"` 限制表单宽度;`MPageSection variant=\"form\"` 提供表单表面;`variant=\"actions\"` 用于底栏操作。\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MButton,\n MForm,\n MFormItem,\n MInput,\n MPageContent,\n MPageHeader,\n MPageSection,\n MSpace,\n} from 'morya-ui'\nimport { ref } from 'vue'\n\nconst name = ref('')\n</script>\n\n<template>\n <div\n class=\"doc-demo-frame\"\n style=\"border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden;background:var(--m-color-surface)\"\n >\n <MPageContent width=\"narrow\">\n <MPageHeader title=\"新建用户\" description=\"填写基本信息。\" />\n <MPageSection variant=\"form\">\n <MForm @submit.prevent>\n <MFormItem label=\"姓名\" name=\"name\">\n <MInput v-model=\"name\" fluid />\n </MFormItem>\n <MPageSection variant=\"actions\">\n <MSpace>\n <MButton native-type=\"submit\" severity=\"primary\">保存</MButton>\n <MButton severity=\"secondary\">取消</MButton>\n </MSpace>\n </MPageSection>\n </MForm>\n </MPageSection>\n </MPageContent>\n </div>\n</template>\n```"
13667
+ },
13668
+ {
13669
+ "id": "仪表盘-kpi",
13670
+ "title": "仪表盘 KPI",
13671
+ "body": "```vue preview\n<script setup lang=\"ts\">\nimport { MPageContent, MPageStat } from 'morya-ui'\n</script>\n\n<template>\n <div\n class=\"doc-demo-frame\"\n style=\"border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden;background:var(--m-color-surface)\"\n >\n <MPageContent density=\"compact\">\n <MPageStat label=\"总用户\" value=\"12,480\" trend=\"+8.2%\" icon=\"users\" />\n </MPageContent>\n </div>\n</template>\n```"
13672
+ },
13673
+ {
13674
+ "id": "组合规则",
13675
+ "title": "组合规则",
13676
+ "body": "| 场景 | 推荐组件 | 避免 |\n| --- | --- | --- |\n| 内容区垂直堆叠 | `MPageContent` | 手写 `gap` / `padding` |\n| 筛选区 | `MPageFilters` + `MSpace` / `MFlex` | 额外包一层 `MCard` |\n| 标题 + 主操作 | `MPageToolbar` | 裸 `div` + `justify-content` |\n| 页面说明 | `MPageHeader` | 无层级的裸 `h1` |\n| 表单主体 | `MPageSection variant=\"form\"` | 重复边框的 `MCard` 嵌套 |\n| 列表表格 | 直接放 `MTable` | `MCard` 再包 `MTable bordered` |\n| KPI | `MPageStat` | 每页自定义 stat CSS |\n| 图表占位 | `MPagePlaceholder` | dashed border 手写样式 |\n\n完整黄金样例见 `docs/golden-pages/`(MCP:`get_golden_page`)。**局部修改**时用 MCP `get_page_snippet`(如 `filters`、`toolbar`、`form-actions`)。"
13677
+ },
13678
+ {
13679
+ "id": "api",
13680
+ "title": "API",
13681
+ "body": "### MPageContent\n\n| Prop | 类型 | 默认 | 说明 |\n| --- | --- | --- | --- |\n| `density` | `'default' \\| 'compact' \\| 'spacious'` | `'default'` | 子区块垂直间距。 |\n| `width` | `'full' \\| 'narrow'` | `'full'` | `narrow` 约 42rem,适合表单页。 |\n\n### MPageFilters\n\n| Prop | 类型 | 默认 | 说明 |\n| --- | --- | --- | --- |\n| `ariaLabel` | `string` | — | 筛选区无障碍名称。 |\n\n### MPageToolbar\n\n| Prop | 类型 | 默认 | 说明 |\n| --- | --- | --- | --- |\n| `title` | `string` | — | 页面标题。 |\n| `headingLevel` | `1–6` | `1` | 标题层级。 |\n\n| Slot | 说明 |\n| --- | --- |\n| `default` | 无 `title` 时的左侧内容。 |\n| `actions` | 右侧操作区。 |\n\n### MPageHeader\n\n| Prop | 类型 | 默认 | 说明 |\n| --- | --- | --- | --- |\n| `title` | `string` | — | 页面标题。 |\n| `description` | `string` | — | 副标题/说明。 |\n| `headingLevel` | `1–6` | `1` | 标题层级。 |\n\n| Slot | 说明 |\n| --- | --- |\n| `actions` | 右侧操作。 |\n\n### MPageSection\n\n| Prop | 类型 | 默认 | 说明 |\n| --- | --- | --- | --- |\n| `variant` | `'default' \\| 'muted' \\| 'form' \\| 'actions'` | `'default'` | 视觉变体。 |\n| `title` | `string` | — | 可选分组标题。 |\n\n### MPageStat\n\n| Prop | 类型 | 默认 | 说明 |\n| --- | --- | --- | --- |\n| `label` | `string` | — | 指标名称。 |\n| `value` | `string \\| number` | — | 主数值。 |\n| `trend` | `string` | — | 变化量或说明。 |\n| `trendSeverity` | `'primary' \\| 'success' \\| 'warn' \\| 'danger' \\| 'secondary'` | `'primary'` | 趋势色。 |\n| `icon` | `string` | — | 右侧图标名。 |\n\n### MPagePlaceholder\n\n| Prop | 类型 | 默认 | 说明 |\n| --- | --- | --- | --- |\n| `description` | `string` | — | 占位说明。 |\n| `ariaLabel` | `string` | `'Placeholder'` | 无障碍标签。 |\n| `minHeight` | `number \\| string` | `'12rem'` | 最小高度。 |"
13682
+ }
13683
+ ],
13684
+ "markdown": "---\ntitle: Page\ncategory: 06 / LAYOUT\ndescription: 业务页面组合区块,封装间距、边框与标题层级,少写或不写页面 CSS。\n---\n\n# Page\n\n业务页面组合组件。与 `MLayout` 搭配使用,把列表页筛选区、工具栏、表单分组、KPI 卡片等常见区块的间距和边框内聚到组件内,**优先用 Page 组件拼装页面,而不是每页手写 scoped CSS**。\n\n## 引入\n\n```ts\nimport {\n MPageContent,\n MPageFilters,\n MPageHeader,\n MPagePlaceholder,\n MPageSection,\n MPageStat,\n MPageToolbar,\n} from 'morya-ui'\n```\n\n## 列表页组合\n\n`MPageContent` 负责内容区 **padding**(默认 `--m-space-6`)与子区块 **gap**(默认 `--m-space-4`),文档预览中外框仅模拟 `MLayoutContent` 边界。\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MButton,\n MInput,\n MPageContent,\n MPageFilters,\n MPageToolbar,\n MSpace,\n} from 'morya-ui'\nimport { ref } from 'vue'\n\nconst keyword = ref('')\n</script>\n\n<template>\n <div\n class=\"doc-demo-frame\"\n style=\"border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden;background:var(--m-color-surface)\"\n >\n <MPageContent>\n <MPageFilters aria-label=\"筛选\">\n <MSpace wrap>\n <MInput v-model=\"keyword\" placeholder=\"搜索\" clearable style=\"width:12rem\" />\n <MButton severity=\"primary\">查询</MButton>\n </MSpace>\n </MPageFilters>\n <MPageToolbar title=\"用户管理\">\n <template #actions>\n <MButton severity=\"primary\">新建</MButton>\n </template>\n </MPageToolbar>\n </MPageContent>\n </div>\n</template>\n```\n\n## 表单页组合\n\n`MPageContent width=\"narrow\"` 限制表单宽度;`MPageSection variant=\"form\"` 提供表单表面;`variant=\"actions\"` 用于底栏操作。\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MButton,\n MForm,\n MFormItem,\n MInput,\n MPageContent,\n MPageHeader,\n MPageSection,\n MSpace,\n} from 'morya-ui'\nimport { ref } from 'vue'\n\nconst name = ref('')\n</script>\n\n<template>\n <div\n class=\"doc-demo-frame\"\n style=\"border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden;background:var(--m-color-surface)\"\n >\n <MPageContent width=\"narrow\">\n <MPageHeader title=\"新建用户\" description=\"填写基本信息。\" />\n <MPageSection variant=\"form\">\n <MForm @submit.prevent>\n <MFormItem label=\"姓名\" name=\"name\">\n <MInput v-model=\"name\" fluid />\n </MFormItem>\n <MPageSection variant=\"actions\">\n <MSpace>\n <MButton native-type=\"submit\" severity=\"primary\">保存</MButton>\n <MButton severity=\"secondary\">取消</MButton>\n </MSpace>\n </MPageSection>\n </MForm>\n </MPageSection>\n </MPageContent>\n </div>\n</template>\n```\n\n## 仪表盘 KPI\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MPageContent, MPageStat } from 'morya-ui'\n</script>\n\n<template>\n <div\n class=\"doc-demo-frame\"\n style=\"border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden;background:var(--m-color-surface)\"\n >\n <MPageContent density=\"compact\">\n <MPageStat label=\"总用户\" value=\"12,480\" trend=\"+8.2%\" icon=\"users\" />\n </MPageContent>\n </div>\n</template>\n```\n\n## 组合规则\n\n| 场景 | 推荐组件 | 避免 |\n| --- | --- | --- |\n| 内容区垂直堆叠 | `MPageContent` | 手写 `gap` / `padding` |\n| 筛选区 | `MPageFilters` + `MSpace` / `MFlex` | 额外包一层 `MCard` |\n| 标题 + 主操作 | `MPageToolbar` | 裸 `div` + `justify-content` |\n| 页面说明 | `MPageHeader` | 无层级的裸 `h1` |\n| 表单主体 | `MPageSection variant=\"form\"` | 重复边框的 `MCard` 嵌套 |\n| 列表表格 | 直接放 `MTable` | `MCard` 再包 `MTable bordered` |\n| KPI | `MPageStat` | 每页自定义 stat CSS |\n| 图表占位 | `MPagePlaceholder` | dashed border 手写样式 |\n\n完整黄金样例见 `docs/golden-pages/`(MCP:`get_golden_page`)。**局部修改**时用 MCP `get_page_snippet`(如 `filters`、`toolbar`、`form-actions`)。\n\n## API\n\n### MPageContent\n\n| Prop | 类型 | 默认 | 说明 |\n| --- | --- | --- | --- |\n| `density` | `'default' \\| 'compact' \\| 'spacious'` | `'default'` | 子区块垂直间距。 |\n| `width` | `'full' \\| 'narrow'` | `'full'` | `narrow` 约 42rem,适合表单页。 |\n\n### MPageFilters\n\n| Prop | 类型 | 默认 | 说明 |\n| --- | --- | --- | --- |\n| `ariaLabel` | `string` | — | 筛选区无障碍名称。 |\n\n### MPageToolbar\n\n| Prop | 类型 | 默认 | 说明 |\n| --- | --- | --- | --- |\n| `title` | `string` | — | 页面标题。 |\n| `headingLevel` | `1–6` | `1` | 标题层级。 |\n\n| Slot | 说明 |\n| --- | --- |\n| `default` | 无 `title` 时的左侧内容。 |\n| `actions` | 右侧操作区。 |\n\n### MPageHeader\n\n| Prop | 类型 | 默认 | 说明 |\n| --- | --- | --- | --- |\n| `title` | `string` | — | 页面标题。 |\n| `description` | `string` | — | 副标题/说明。 |\n| `headingLevel` | `1–6` | `1` | 标题层级。 |\n\n| Slot | 说明 |\n| --- | --- |\n| `actions` | 右侧操作。 |\n\n### MPageSection\n\n| Prop | 类型 | 默认 | 说明 |\n| --- | --- | --- | --- |\n| `variant` | `'default' \\| 'muted' \\| 'form' \\| 'actions'` | `'default'` | 视觉变体。 |\n| `title` | `string` | — | 可选分组标题。 |\n\n### MPageStat\n\n| Prop | 类型 | 默认 | 说明 |\n| --- | --- | --- | --- |\n| `label` | `string` | — | 指标名称。 |\n| `value` | `string \\| number` | — | 主数值。 |\n| `trend` | `string` | — | 变化量或说明。 |\n| `trendSeverity` | `'primary' \\| 'success' \\| 'warn' \\| 'danger' \\| 'secondary'` | `'primary'` | 趋势色。 |\n| `icon` | `string` | — | 右侧图标名。 |\n\n### MPagePlaceholder\n\n| Prop | 类型 | 默认 | 说明 |\n| --- | --- | --- | --- |\n| `description` | `string` | — | 占位说明。 |\n| `ariaLabel` | `string` | `'Placeholder'` | 无障碍标签。 |\n| `minHeight` | `number \\| string` | `'12rem'` | 最小高度。 |\n"
13685
+ },
13686
+ "en-US": {
13687
+ "title": "Page",
13688
+ "description": "Composable page sections for spacing, borders, and headings with little or no custom CSS.",
13689
+ "sections": [
13690
+ {
13691
+ "id": "overview",
13692
+ "title": "",
13693
+ "body": "# Page\n\nPage composition components. Use them with `MLayout` to encode filter bars, toolbars, form surfaces, KPI cards, and other admin-page blocks **without rewriting scoped CSS on every page**."
13694
+ },
13695
+ {
13696
+ "id": "import",
13697
+ "title": "Import",
13698
+ "body": "```ts\nimport {\n MPageContent,\n MPageFilters,\n MPageHeader,\n MPagePlaceholder,\n MPageSection,\n MPageStat,\n MPageToolbar,\n} from 'morya-ui'\n```"
13699
+ },
13700
+ {
13701
+ "id": "list-page-stack",
13702
+ "title": "List page stack",
13703
+ "body": "`MPageContent` owns content **padding** (default `--m-space-6`) and section **gap** (default `--m-space-4`). The bordered frame in previews simulates `MLayoutContent` only.\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MButton,\n MInput,\n MPageContent,\n MPageFilters,\n MPageToolbar,\n MSpace,\n} from 'morya-ui'\nimport { ref } from 'vue'\n\nconst keyword = ref('')\n</script>\n\n<template>\n <div\n class=\"doc-demo-frame\"\n style=\"border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden;background:var(--m-color-surface)\"\n >\n <MPageContent>\n <MPageFilters aria-label=\"Filters\">\n <MSpace wrap>\n <MInput v-model=\"keyword\" placeholder=\"Search\" clearable style=\"width:12rem\" />\n <MButton severity=\"primary\">Search</MButton>\n </MSpace>\n </MPageFilters>\n <MPageToolbar title=\"Users\">\n <template #actions>\n <MButton severity=\"primary\">Create</MButton>\n </template>\n </MPageToolbar>\n </MPageContent>\n </div>\n</template>\n```"
13704
+ },
13705
+ {
13706
+ "id": "form-page-stack",
13707
+ "title": "Form page stack",
13708
+ "body": "Use `MPageContent width=\"narrow\"`, `MPageSection variant=\"form\"`, and `variant=\"actions\"` for the footer."
13709
+ },
13710
+ {
13711
+ "id": "composition-rules",
13712
+ "title": "Composition rules",
13713
+ "body": "| Scenario | Use | Avoid |\n| --- | --- | --- |\n| Vertical page stack | `MPageContent` | Hand-written `gap` / `padding` |\n| Filters | `MPageFilters` | Extra bordered `MCard` wrapper |\n| Title + actions | `MPageToolbar` | Raw flex divs |\n| Form surface | `MPageSection variant=\"form\"` | Nested bordered cards |\n| Data table | `MTable` directly | `MCard` around bordered table |\n| KPI metric | `MPageStat` | Custom stat CSS per page |\n\nGolden references: `docs/golden-pages/` (MCP: `get_golden_page`)."
13714
+ }
13715
+ ],
13716
+ "markdown": "---\ntitle: Page\ncategory: 06 / LAYOUT\ndescription: Composable page sections for spacing, borders, and headings with little or no custom CSS.\n---\n\n# Page\n\nPage composition components. Use them with `MLayout` to encode filter bars, toolbars, form surfaces, KPI cards, and other admin-page blocks **without rewriting scoped CSS on every page**.\n\n## Import\n\n```ts\nimport {\n MPageContent,\n MPageFilters,\n MPageHeader,\n MPagePlaceholder,\n MPageSection,\n MPageStat,\n MPageToolbar,\n} from 'morya-ui'\n```\n\n## List page stack\n\n`MPageContent` owns content **padding** (default `--m-space-6`) and section **gap** (default `--m-space-4`). The bordered frame in previews simulates `MLayoutContent` only.\n\n```vue preview\n<script setup lang=\"ts\">\nimport {\n MButton,\n MInput,\n MPageContent,\n MPageFilters,\n MPageToolbar,\n MSpace,\n} from 'morya-ui'\nimport { ref } from 'vue'\n\nconst keyword = ref('')\n</script>\n\n<template>\n <div\n class=\"doc-demo-frame\"\n style=\"border:1px solid var(--m-color-border);border-radius:var(--m-radius-md);overflow:hidden;background:var(--m-color-surface)\"\n >\n <MPageContent>\n <MPageFilters aria-label=\"Filters\">\n <MSpace wrap>\n <MInput v-model=\"keyword\" placeholder=\"Search\" clearable style=\"width:12rem\" />\n <MButton severity=\"primary\">Search</MButton>\n </MSpace>\n </MPageFilters>\n <MPageToolbar title=\"Users\">\n <template #actions>\n <MButton severity=\"primary\">Create</MButton>\n </template>\n </MPageToolbar>\n </MPageContent>\n </div>\n</template>\n```\n\n## Form page stack\n\nUse `MPageContent width=\"narrow\"`, `MPageSection variant=\"form\"`, and `variant=\"actions\"` for the footer.\n\n## Composition rules\n\n| Scenario | Use | Avoid |\n| --- | --- | --- |\n| Vertical page stack | `MPageContent` | Hand-written `gap` / `padding` |\n| Filters | `MPageFilters` | Extra bordered `MCard` wrapper |\n| Title + actions | `MPageToolbar` | Raw flex divs |\n| Form surface | `MPageSection variant=\"form\"` | Nested bordered cards |\n| Data table | `MTable` directly | `MCard` around bordered table |\n| KPI metric | `MPageStat` | Custom stat CSS per page |\n\nGolden references: `docs/golden-pages/` (MCP: `get_golden_page`).\n"
13717
+ }
13718
+ }
13719
+ },
13556
13720
  {
13557
13721
  "id": "Pagination",
13558
13722
  "name": "Pagination",
@@ -15504,7 +15668,7 @@
15504
15668
  "sectionId": "基础用法",
15505
15669
  "lang": "vue",
15506
15670
  "preview": true,
15507
- "code": "<script setup lang=\"ts\">\nimport { MScrollbar } from 'morya-ui'\n</script>\n\n<template>\n <MScrollbar height=\"240px\" always style=\"width: 200px\">\n <p\n v-for=\"item in 20\"\n :key=\"item\"\n style=\"\n display:flex;align-items:center;justify-content:center;\n height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-primary) 12%, transparent);\n color:var(--m-color-primary);\n \"\n >\n {{ item }}\n </p>\n </MScrollbar>\n</template>",
15671
+ "code": "<script setup lang=\"ts\">\nimport { MScrollbar } from 'morya-ui'\n</script>\n\n<template>\n <MScrollbar height=\"240px\" always style=\"width: 100%\">\n <p\n v-for=\"item in 20\"\n :key=\"item\"\n style=\"\n display:flex;align-items:center;justify-content:center;\n height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-primary) 12%, transparent);\n color:var(--m-color-primary);\n \"\n >\n {{ item }}\n </p>\n </MScrollbar>\n</template>",
15508
15672
  "locale": "zh-CN"
15509
15673
  },
15510
15674
  {
@@ -15513,7 +15677,7 @@
15513
15677
  "sectionId": "max-height",
15514
15678
  "lang": "vue",
15515
15679
  "preview": true,
15516
- "code": "<script setup lang=\"ts\">\nimport { MButton, MScrollbar } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst count = ref(3)\n</script>\n\n<template>\n <div style=\"display:grid;gap:0.75rem\">\n <div style=\"display:flex;gap:0.5rem\">\n <MButton label=\"Add\" size=\"small\" @click=\"count++\" />\n <MButton label=\"Remove\" severity=\"secondary\" size=\"small\" @click=\"count = Math.max(0, count - 1)\" />\n </div>\n <MScrollbar max-height=\"220px\" always style=\"width: 200px\">\n <p\n v-for=\"item in count\"\n :key=\"item\"\n style=\"\n display:flex;align-items:center;justify-content:center;\n height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-info) 12%, transparent);\n color:var(--m-color-info);\n \"\n >\n Item {{ item }}\n </p>\n </MScrollbar>\n </div>\n</template>",
15680
+ "code": "<script setup lang=\"ts\">\nimport { MButton, MScrollbar } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst count = ref(3)\n</script>\n\n<template>\n <div style=\"display:grid;gap:0.75rem\">\n <div style=\"display:flex;gap:0.5rem\">\n <MButton label=\"Add\" size=\"small\" @click=\"count++\" />\n <MButton label=\"Remove\" severity=\"secondary\" size=\"small\" @click=\"count = Math.max(0, count - 1)\" />\n </div>\n <MScrollbar max-height=\"220px\" always style=\"width: 100%\">\n <p\n v-for=\"item in count\"\n :key=\"item\"\n style=\"\n display:flex;align-items:center;justify-content:center;\n height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-info) 12%, transparent);\n color:var(--m-color-info);\n \"\n >\n Item {{ item }}\n </p>\n </MScrollbar>\n </div>\n</template>",
15517
15681
  "locale": "zh-CN"
15518
15682
  },
15519
15683
  {
@@ -15585,7 +15749,7 @@
15585
15749
  "sectionId": "basic",
15586
15750
  "lang": "vue",
15587
15751
  "preview": true,
15588
- "code": "<script setup lang=\"ts\">\nimport { MScrollbar } from 'morya-ui'\n</script>\n\n<template>\n <MScrollbar height=\"240px\" always style=\"width: 200px\">\n <p\n v-for=\"item in 20\"\n :key=\"item\"\n style=\"\n display:flex;align-items:center;justify-content:center;\n height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-primary) 12%, transparent);\n color:var(--m-color-primary);\n \"\n >\n {{ item }}\n </p>\n </MScrollbar>\n</template>",
15752
+ "code": "<script setup lang=\"ts\">\nimport { MScrollbar } from 'morya-ui'\n</script>\n\n<template>\n <MScrollbar height=\"240px\" always style=\"width: 100%\">\n <p\n v-for=\"item in 20\"\n :key=\"item\"\n style=\"\n display:flex;align-items:center;justify-content:center;\n height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-primary) 12%, transparent);\n color:var(--m-color-primary);\n \"\n >\n {{ item }}\n </p>\n </MScrollbar>\n</template>",
15589
15753
  "locale": "en-US"
15590
15754
  },
15591
15755
  {
@@ -15594,7 +15758,7 @@
15594
15758
  "sectionId": "max-height",
15595
15759
  "lang": "vue",
15596
15760
  "preview": true,
15597
- "code": "<script setup lang=\"ts\">\nimport { MButton, MScrollbar } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst count = ref(3)\n</script>\n\n<template>\n <div style=\"display:grid;gap:0.75rem\">\n <div style=\"display:flex;gap:0.5rem\">\n <MButton label=\"Add\" size=\"small\" @click=\"count++\" />\n <MButton label=\"Remove\" severity=\"secondary\" size=\"small\" @click=\"count = Math.max(0, count - 1)\" />\n </div>\n <MScrollbar max-height=\"220px\" always style=\"width: 200px\">\n <p\n v-for=\"item in count\"\n :key=\"item\"\n style=\"\n display:flex;align-items:center;justify-content:center;\n height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-info) 12%, transparent);\n color:var(--m-color-info);\n \"\n >\n Item {{ item }}\n </p>\n </MScrollbar>\n </div>\n</template>",
15761
+ "code": "<script setup lang=\"ts\">\nimport { MButton, MScrollbar } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst count = ref(3)\n</script>\n\n<template>\n <div style=\"display:grid;gap:0.75rem\">\n <div style=\"display:flex;gap:0.5rem\">\n <MButton label=\"Add\" size=\"small\" @click=\"count++\" />\n <MButton label=\"Remove\" severity=\"secondary\" size=\"small\" @click=\"count = Math.max(0, count - 1)\" />\n </div>\n <MScrollbar max-height=\"220px\" always style=\"width: 100%\">\n <p\n v-for=\"item in count\"\n :key=\"item\"\n style=\"\n display:flex;align-items:center;justify-content:center;\n height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-info) 12%, transparent);\n color:var(--m-color-info);\n \"\n >\n Item {{ item }}\n </p>\n </MScrollbar>\n </div>\n</template>",
15598
15762
  "locale": "en-US"
15599
15763
  },
15600
15764
  {
@@ -15652,17 +15816,17 @@
15652
15816
  {
15653
15817
  "id": "基础用法",
15654
15818
  "title": "基础用法",
15655
- "body": "用 `height` 固定可视区域高度;不设时跟随父容器高度。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MScrollbar } from 'morya-ui'\n</script>\n\n<template>\n <MScrollbar height=\"240px\" always style=\"width: 200px\">\n <p\n v-for=\"item in 20\"\n :key=\"item\"\n style=\"\n display:flex;align-items:center;justify-content:center;\n height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-primary) 12%, transparent);\n color:var(--m-color-primary);\n \"\n >\n {{ item }}\n </p>\n </MScrollbar>\n</template>\n```"
15819
+ "body": "用 `height` 固定可视区域高度;不设时跟随父容器高度。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MScrollbar } from 'morya-ui'\n</script>\n\n<template>\n <MScrollbar height=\"240px\" always style=\"width: 100%\">\n <p\n v-for=\"item in 20\"\n :key=\"item\"\n style=\"\n display:flex;align-items:center;justify-content:center;\n height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-primary) 12%, transparent);\n color:var(--m-color-primary);\n \"\n >\n {{ item }}\n </p>\n </MScrollbar>\n</template>\n```"
15656
15820
  },
15657
15821
  {
15658
15822
  "id": "max-height",
15659
15823
  "title": "Max height",
15660
- "body": "仅当内容超出 `max-height` 时出现滚动条。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MButton, MScrollbar } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst count = ref(3)\n</script>\n\n<template>\n <div style=\"display:grid;gap:0.75rem\">\n <div style=\"display:flex;gap:0.5rem\">\n <MButton label=\"Add\" size=\"small\" @click=\"count++\" />\n <MButton label=\"Remove\" severity=\"secondary\" size=\"small\" @click=\"count = Math.max(0, count - 1)\" />\n </div>\n <MScrollbar max-height=\"220px\" always style=\"width: 200px\">\n <p\n v-for=\"item in count\"\n :key=\"item\"\n style=\"\n display:flex;align-items:center;justify-content:center;\n height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-info) 12%, transparent);\n color:var(--m-color-info);\n \"\n >\n Item {{ item }}\n </p>\n </MScrollbar>\n </div>\n</template>\n```"
15824
+ "body": "仅当内容超出 `max-height` 时出现滚动条。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MButton, MScrollbar } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst count = ref(3)\n</script>\n\n<template>\n <div style=\"display:grid;gap:0.75rem\">\n <div style=\"display:flex;gap:0.5rem\">\n <MButton label=\"Add\" size=\"small\" @click=\"count++\" />\n <MButton label=\"Remove\" severity=\"secondary\" size=\"small\" @click=\"count = Math.max(0, count - 1)\" />\n </div>\n <MScrollbar max-height=\"220px\" always style=\"width: 100%\">\n <p\n v-for=\"item in count\"\n :key=\"item\"\n style=\"\n display:flex;align-items:center;justify-content:center;\n height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-info) 12%, transparent);\n color:var(--m-color-info);\n \"\n >\n Item {{ item }}\n </p>\n </MScrollbar>\n </div>\n</template>\n```"
15661
15825
  },
15662
15826
  {
15663
15827
  "id": "horizontal",
15664
15828
  "title": "Horizontal",
15665
- "body": "内容宽度超出容器时显示横向滚动条。`trigger=\"none\"` 与 `always` 都会常显滑块;默认 `trigger=\"hover\"` 在悬停时显示。\n\n`MLayout` `native-scrollbar={false}` 时会接入本组件。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MScrollbar } from 'morya-ui'\n</script>\n\n<template>\n <MScrollbar always>\n <div style=\"display:flex;width:fit-content\">\n <p\n v-for=\"item in 30\"\n :key=\"item\"\n style=\"\n flex-shrink:0;display:flex;align-items:center;justify-content:center;\n width:96px;height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-danger) 12%, transparent);\n color:var(--m-color-danger);\n \"\n >\n {{ item }}\n </p>\n </div>\n </MScrollbar>\n</template>\n```"
15829
+ "body": "内容宽度超出容器时显示横向滚动条。`trigger=\"none\"` 与 `always` 都会常显滑块;默认 `trigger=\"hover\"` 在悬停时显示。\n\n`MLayout` / `MLayoutContent` / `MLayoutSider`、`MDialog`、`MConfirmDialog`、`MDrawer`、`MSplitter`、`MTable`、`MSelect`、`MTreeSelect`、`MDropdown`、`MContextMenu`、`MPopover`、`MConfirmPopup`、`MMenu`(popup)、`MMenubar`、`MTieredMenu`、`MTabs`、`MGallery`、`MTimeline`(horizontal)、`MTextarea`(autosize `maxRows`)、`MTerminal`、`MOrderList`、`MPickList`、`MTreeTable`、`MVirtualScroller` 等组件已内置本组件。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MScrollbar } from 'morya-ui'\n</script>\n\n<template>\n <MScrollbar always>\n <div style=\"display:flex;width:fit-content\">\n <p\n v-for=\"item in 30\"\n :key=\"item\"\n style=\"\n flex-shrink:0;display:flex;align-items:center;justify-content:center;\n width:96px;height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-danger) 12%, transparent);\n color:var(--m-color-danger);\n \"\n >\n {{ item }}\n </p>\n </div>\n </MScrollbar>\n</template>\n```"
15666
15830
  },
15667
15831
  {
15668
15832
  "id": "always-native",
@@ -15700,7 +15864,7 @@
15700
15864
  "body": "<h4 id=\"ScrollbarAriaOrientation\">ScrollbarAriaOrientation</h4>\n\n完整定义见源码 `types.ts`。\n\n```ts\ntype ScrollbarAriaOrientation = 'horizontal' | 'vertical'\n```\n\n<h4 id=\"ScrollbarClassValue\">ScrollbarClassValue</h4>\n\n完整定义见源码 `types.ts`。\n\n```ts\ntype ScrollbarClassValue = | string\n | string[]\n | Record<string, boolean>\n | Array<string | Record<string, boolean> | null | undefined | false>\n```"
15701
15865
  }
15702
15866
  ],
15703
- "markdown": "---\ntitle: Scrollbar\ncategory: 01 / BASIC\ndescription: 可换肤自定义滚动条,提供一致的滚动体验。\n---\n\n# Scrollbar\n\n用于替换浏览器原生滚动条,提供跨浏览器一致的可换肤滚动体验。\n\n## 引入\n\n```ts\nimport { MScrollbar } from 'morya-ui'\n```\n\n## 基础用法\n\n用 `height` 固定可视区域高度;不设时跟随父容器高度。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MScrollbar } from 'morya-ui'\n</script>\n\n<template>\n <MScrollbar height=\"240px\" always style=\"width: 200px\">\n <p\n v-for=\"item in 20\"\n :key=\"item\"\n style=\"\n display:flex;align-items:center;justify-content:center;\n height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-primary) 12%, transparent);\n color:var(--m-color-primary);\n \"\n >\n {{ item }}\n </p>\n </MScrollbar>\n</template>\n```\n\n## Max height\n\n仅当内容超出 `max-height` 时出现滚动条。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MButton, MScrollbar } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst count = ref(3)\n</script>\n\n<template>\n <div style=\"display:grid;gap:0.75rem\">\n <div style=\"display:flex;gap:0.5rem\">\n <MButton label=\"Add\" size=\"small\" @click=\"count++\" />\n <MButton label=\"Remove\" severity=\"secondary\" size=\"small\" @click=\"count = Math.max(0, count - 1)\" />\n </div>\n <MScrollbar max-height=\"220px\" always style=\"width: 200px\">\n <p\n v-for=\"item in count\"\n :key=\"item\"\n style=\"\n display:flex;align-items:center;justify-content:center;\n height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-info) 12%, transparent);\n color:var(--m-color-info);\n \"\n >\n Item {{ item }}\n </p>\n </MScrollbar>\n </div>\n</template>\n```\n\n## Horizontal\n\n内容宽度超出容器时显示横向滚动条。`trigger=\"none\"` 与 `always` 都会常显滑块;默认 `trigger=\"hover\"` 在悬停时显示。\n\n`MLayout` `native-scrollbar={false}` 时会接入本组件。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MScrollbar } from 'morya-ui'\n</script>\n\n<template>\n <MScrollbar always>\n <div style=\"display:flex;width:fit-content\">\n <p\n v-for=\"item in 30\"\n :key=\"item\"\n style=\"\n flex-shrink:0;display:flex;align-items:center;justify-content:center;\n width:96px;height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-danger) 12%, transparent);\n color:var(--m-color-danger);\n \"\n >\n {{ item }}\n </p>\n </div>\n </MScrollbar>\n</template>\n```\n\n## Always / Native\n\n`always` 常显自定义滑块;`native` 使用浏览器原生滚动条。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MScrollbar } from 'morya-ui'\n</script>\n\n<template>\n <div style=\"display:grid;gap:1.25rem;grid-template-columns:1fr 1fr\">\n <div>\n <p style=\"margin:0 0 0.5rem;color:var(--m-color-text-muted)\">\n always\n </p>\n <MScrollbar height=\"160px\" always>\n <p v-for=\"n in 12\" :key=\"n\" style=\"margin:0.5rem 0\">\n Line {{ n }}\n </p>\n </MScrollbar>\n </div>\n <div>\n <p style=\"margin:0 0 0.5rem;color:var(--m-color-text-muted)\">\n native\n </p>\n <MScrollbar height=\"160px\" native style=\"width: 200px\">\n <p v-for=\"n in 12\" :key=\"n\" style=\"margin:0.5rem 0\">\n Line {{ n }}\n </p>\n </MScrollbar>\n </div>\n </div>\n</template>\n```\n\n## Manual scroll\n\n通过实例方法 `setScrollTop` / `setScrollLeft` / `scrollTo` / `update` 控制滚动。\n\n```vue preview\n<script setup lang=\"ts\">\nimport type { ScrollbarInstance } from 'morya-ui'\nimport { MButton, MScrollbar } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst scrollbarRef = ref<ScrollbarInstance>()\nconst scrollTop = ref(0)\n\nfunction jump(top: number) {\n scrollbarRef.value?.setScrollTop(top)\n}\n\nfunction onScroll(payload: { scrollTop: number }) {\n scrollTop.value = Math.round(payload.scrollTop)\n}\n</script>\n\n<template>\n <div style=\"display:grid;gap:0.75rem\">\n <div style=\"display:flex;flex-wrap:wrap;gap:0.5rem;align-items:center\">\n <MButton label=\"Top\" size=\"small\" @click=\"jump(0)\" />\n <MButton label=\"Mid\" size=\"small\" severity=\"secondary\" @click=\"jump(200)\" />\n <MButton label=\"Bottom\" size=\"small\" severity=\"secondary\" @click=\"jump(9999)\" />\n <span style=\"color:var(--m-color-text-muted);font-size:0.875rem\">scrollTop: {{ scrollTop }}</span>\n </div>\n <MScrollbar ref=\"scrollbarRef\" height=\"200px\" style=\"width: 200px\" always @scroll=\"onScroll\">\n <p\n v-for=\"item in 24\"\n :key=\"item\"\n style=\"\n display:flex;align-items:center;justify-content:center;\n height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-success) 12%, transparent);\n color:var(--m-color-success);\n \"\n >\n {{ item }}\n </p>\n </MScrollbar>\n </div>\n</template>\n```\n\n## Infinite scroll\n\n滚动到边缘时触发 `end-reached`,可用于无限加载。\n\n```vue preview\n<script setup lang=\"ts\">\nimport type { ScrollbarDirection } from 'morya-ui'\nimport { MScrollbar } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst num = ref(20)\n\nfunction loadMore(direction: ScrollbarDirection) {\n if (direction === 'bottom') num.value += 5\n}\n</script>\n\n<template>\n <MScrollbar height=\"220px\" always style=\"width: 200px\" @end-reached=\"loadMore\">\n <p\n v-for=\"item in num\"\n :key=\"item\"\n style=\"\n display:flex;align-items:center;justify-content:center;\n height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-primary) 12%, transparent);\n color:var(--m-color-primary);\n \"\n >\n {{ item }}\n </p>\n </MScrollbar>\n</template>\n```\n\n## API\n\n### Props\n\n| 参数 | 类型 | 默认值 | 说明 |\n| --- | --- | --- | --- |\n| `always` | `boolean` | — | — |\n| `ariaLabel` | `string` | — | — |\n| `ariaOrientation` | `ScrollbarAriaOrientation` | — | — |\n| `distance` | `number` | — | — |\n| `height` | `string \\| number` | — | — |\n| `id` | `string` | — | — |\n| `maxHeight` | `string \\| number` | — | — |\n| `minSize` | `number` | — | — |\n| `native` | `boolean` | — | — |\n| `noresize` | `boolean` | — | — |\n| `role` | `string` | — | — |\n| `tabindex` | `number \\| string` | — | — |\n| `tag` | `string` | — | — |\n| `trigger` | `'hover' \\| 'none'` | — | — |\n| `viewClass` | `ScrollbarClassValue` | — | — |\n| `viewStyle` | `StyleValue` | — | — |\n| `wrapClass` | `ScrollbarClassValue` | — | — |\n| `wrapStyle` | `StyleValue` | — | — |\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| height | `string \\| number` | — | 可视区域高度 |\n| maxHeight | `string \\| number` | — | 最大高度 |\n| fitContent | `boolean` | `false` | 随内容增高,配合根节点 CSS `max-height` 使用(下拉面板) |\n| native | `boolean` | `false` | 使用原生滚动条 |\n| wrapStyle / wrapClass | style / class | — | wrap 容器样式 |\n| viewStyle / viewClass | style / class | — | 内容区样式 |\n| noresize | `boolean` | `false` | 不监听尺寸变化 |\n| tag | `string` | `div` | 内容区标签 |\n| always | `boolean` | `false` | 始终显示滑块 |\n| trigger | `'hover' \\| 'none'` | `'hover'` | `none` 常显滑块;`always` 为 true 时仍常显 |\n| minSize | `number` | `20` | 滑块最小尺寸 |\n| id / role / ariaLabel / ariaOrientation | a11y | — | 内容区无障碍属性 |\n| tabindex | `number \\| string` | — | wrap 的 tabindex |\n| distance | `number` | `0` | 触发 `end-reached` 的边缘距离 |\n\n### Events\n\n| Name | Payload |\n| --- | --- |\n| scroll | `{ scrollTop, scrollLeft }` |\n| end-reached | `'top' \\| 'bottom' \\| 'left' \\| 'right'` |\n\n### Expose\n\n`wrapRef`、`update`、`scrollTo`、`setScrollTop`、`setScrollLeft`、`handleScroll`\n\n## Events\n\n| 事件名 | 参数 | 说明 |\n| --- | --- | --- |\n| `scroll` | `{ scrollTop, scrollLeft }` | 滚动位置变化。 |\n| `end-reached` | `'top' \\| 'bottom' \\| 'left' \\| 'right'` | 滚动到边缘。 |\n\n## Slots\n\n| 插槽名 | 说明 |\n| --- | --- |\n| `default` | 可滚动内容。 |\n\n## 类型\n\n<h4 id=\"ScrollbarAriaOrientation\">ScrollbarAriaOrientation</h4>\n\n完整定义见源码 `types.ts`。\n\n```ts\ntype ScrollbarAriaOrientation = 'horizontal' | 'vertical'\n```\n\n<h4 id=\"ScrollbarClassValue\">ScrollbarClassValue</h4>\n\n完整定义见源码 `types.ts`。\n\n```ts\ntype ScrollbarClassValue = | string\n | string[]\n | Record<string, boolean>\n | Array<string | Record<string, boolean> | null | undefined | false>\n```\n"
15867
+ "markdown": "---\ntitle: Scrollbar\ncategory: 01 / BASIC\ndescription: 可换肤自定义滚动条,提供一致的滚动体验。\n---\n\n# Scrollbar\n\n用于替换浏览器原生滚动条,提供跨浏览器一致的可换肤滚动体验。\n\n## 引入\n\n```ts\nimport { MScrollbar } from 'morya-ui'\n```\n\n## 基础用法\n\n用 `height` 固定可视区域高度;不设时跟随父容器高度。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MScrollbar } from 'morya-ui'\n</script>\n\n<template>\n <MScrollbar height=\"240px\" always style=\"width: 100%\">\n <p\n v-for=\"item in 20\"\n :key=\"item\"\n style=\"\n display:flex;align-items:center;justify-content:center;\n height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-primary) 12%, transparent);\n color:var(--m-color-primary);\n \"\n >\n {{ item }}\n </p>\n </MScrollbar>\n</template>\n```\n\n## Max height\n\n仅当内容超出 `max-height` 时出现滚动条。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MButton, MScrollbar } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst count = ref(3)\n</script>\n\n<template>\n <div style=\"display:grid;gap:0.75rem\">\n <div style=\"display:flex;gap:0.5rem\">\n <MButton label=\"Add\" size=\"small\" @click=\"count++\" />\n <MButton label=\"Remove\" severity=\"secondary\" size=\"small\" @click=\"count = Math.max(0, count - 1)\" />\n </div>\n <MScrollbar max-height=\"220px\" always style=\"width: 100%\">\n <p\n v-for=\"item in count\"\n :key=\"item\"\n style=\"\n display:flex;align-items:center;justify-content:center;\n height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-info) 12%, transparent);\n color:var(--m-color-info);\n \"\n >\n Item {{ item }}\n </p>\n </MScrollbar>\n </div>\n</template>\n```\n\n## Horizontal\n\n内容宽度超出容器时显示横向滚动条。`trigger=\"none\"` 与 `always` 都会常显滑块;默认 `trigger=\"hover\"` 在悬停时显示。\n\n`MLayout` / `MLayoutContent` / `MLayoutSider`、`MDialog`、`MConfirmDialog`、`MDrawer`、`MSplitter`、`MTable`、`MSelect`、`MTreeSelect`、`MDropdown`、`MContextMenu`、`MPopover`、`MConfirmPopup`、`MMenu`(popup)、`MMenubar`、`MTieredMenu`、`MTabs`、`MGallery`、`MTimeline`(horizontal)、`MTextarea`(autosize `maxRows`)、`MTerminal`、`MOrderList`、`MPickList`、`MTreeTable`、`MVirtualScroller` 等组件已内置本组件。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MScrollbar } from 'morya-ui'\n</script>\n\n<template>\n <MScrollbar always>\n <div style=\"display:flex;width:fit-content\">\n <p\n v-for=\"item in 30\"\n :key=\"item\"\n style=\"\n flex-shrink:0;display:flex;align-items:center;justify-content:center;\n width:96px;height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-danger) 12%, transparent);\n color:var(--m-color-danger);\n \"\n >\n {{ item }}\n </p>\n </div>\n </MScrollbar>\n</template>\n```\n\n## Always / Native\n\n`always` 常显自定义滑块;`native` 使用浏览器原生滚动条。\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MScrollbar } from 'morya-ui'\n</script>\n\n<template>\n <div style=\"display:grid;gap:1.25rem;grid-template-columns:1fr 1fr\">\n <div>\n <p style=\"margin:0 0 0.5rem;color:var(--m-color-text-muted)\">\n always\n </p>\n <MScrollbar height=\"160px\" always>\n <p v-for=\"n in 12\" :key=\"n\" style=\"margin:0.5rem 0\">\n Line {{ n }}\n </p>\n </MScrollbar>\n </div>\n <div>\n <p style=\"margin:0 0 0.5rem;color:var(--m-color-text-muted)\">\n native\n </p>\n <MScrollbar height=\"160px\" native style=\"width: 200px\">\n <p v-for=\"n in 12\" :key=\"n\" style=\"margin:0.5rem 0\">\n Line {{ n }}\n </p>\n </MScrollbar>\n </div>\n </div>\n</template>\n```\n\n## Manual scroll\n\n通过实例方法 `setScrollTop` / `setScrollLeft` / `scrollTo` / `update` 控制滚动。\n\n```vue preview\n<script setup lang=\"ts\">\nimport type { ScrollbarInstance } from 'morya-ui'\nimport { MButton, MScrollbar } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst scrollbarRef = ref<ScrollbarInstance>()\nconst scrollTop = ref(0)\n\nfunction jump(top: number) {\n scrollbarRef.value?.setScrollTop(top)\n}\n\nfunction onScroll(payload: { scrollTop: number }) {\n scrollTop.value = Math.round(payload.scrollTop)\n}\n</script>\n\n<template>\n <div style=\"display:grid;gap:0.75rem\">\n <div style=\"display:flex;flex-wrap:wrap;gap:0.5rem;align-items:center\">\n <MButton label=\"Top\" size=\"small\" @click=\"jump(0)\" />\n <MButton label=\"Mid\" size=\"small\" severity=\"secondary\" @click=\"jump(200)\" />\n <MButton label=\"Bottom\" size=\"small\" severity=\"secondary\" @click=\"jump(9999)\" />\n <span style=\"color:var(--m-color-text-muted);font-size:0.875rem\">scrollTop: {{ scrollTop }}</span>\n </div>\n <MScrollbar ref=\"scrollbarRef\" height=\"200px\" style=\"width: 200px\" always @scroll=\"onScroll\">\n <p\n v-for=\"item in 24\"\n :key=\"item\"\n style=\"\n display:flex;align-items:center;justify-content:center;\n height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-success) 12%, transparent);\n color:var(--m-color-success);\n \"\n >\n {{ item }}\n </p>\n </MScrollbar>\n </div>\n</template>\n```\n\n## Infinite scroll\n\n滚动到边缘时触发 `end-reached`,可用于无限加载。\n\n```vue preview\n<script setup lang=\"ts\">\nimport type { ScrollbarDirection } from 'morya-ui'\nimport { MScrollbar } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst num = ref(20)\n\nfunction loadMore(direction: ScrollbarDirection) {\n if (direction === 'bottom') num.value += 5\n}\n</script>\n\n<template>\n <MScrollbar height=\"220px\" always style=\"width: 200px\" @end-reached=\"loadMore\">\n <p\n v-for=\"item in num\"\n :key=\"item\"\n style=\"\n display:flex;align-items:center;justify-content:center;\n height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-primary) 12%, transparent);\n color:var(--m-color-primary);\n \"\n >\n {{ item }}\n </p>\n </MScrollbar>\n</template>\n```\n\n## API\n\n### Props\n\n| 参数 | 类型 | 默认值 | 说明 |\n| --- | --- | --- | --- |\n| `always` | `boolean` | — | — |\n| `ariaLabel` | `string` | — | — |\n| `ariaOrientation` | `ScrollbarAriaOrientation` | — | — |\n| `distance` | `number` | — | — |\n| `height` | `string \\| number` | — | — |\n| `id` | `string` | — | — |\n| `maxHeight` | `string \\| number` | — | — |\n| `minSize` | `number` | — | — |\n| `native` | `boolean` | — | — |\n| `noresize` | `boolean` | — | — |\n| `role` | `string` | — | — |\n| `tabindex` | `number \\| string` | — | — |\n| `tag` | `string` | — | — |\n| `trigger` | `'hover' \\| 'none'` | — | — |\n| `viewClass` | `ScrollbarClassValue` | — | — |\n| `viewStyle` | `StyleValue` | — | — |\n| `wrapClass` | `ScrollbarClassValue` | — | — |\n| `wrapStyle` | `StyleValue` | — | — |\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| height | `string \\| number` | — | 可视区域高度 |\n| maxHeight | `string \\| number` | — | 最大高度 |\n| fitContent | `boolean` | `false` | 随内容增高,配合根节点 CSS `max-height` 使用(下拉面板) |\n| native | `boolean` | `false` | 使用原生滚动条 |\n| wrapStyle / wrapClass | style / class | — | wrap 容器样式 |\n| viewStyle / viewClass | style / class | — | 内容区样式 |\n| noresize | `boolean` | `false` | 不监听尺寸变化 |\n| tag | `string` | `div` | 内容区标签 |\n| always | `boolean` | `false` | 始终显示滑块 |\n| trigger | `'hover' \\| 'none'` | `'hover'` | `none` 常显滑块;`always` 为 true 时仍常显 |\n| minSize | `number` | `20` | 滑块最小尺寸 |\n| id / role / ariaLabel / ariaOrientation | a11y | — | 内容区无障碍属性 |\n| tabindex | `number \\| string` | — | wrap 的 tabindex |\n| distance | `number` | `0` | 触发 `end-reached` 的边缘距离 |\n\n### Events\n\n| Name | Payload |\n| --- | --- |\n| scroll | `{ scrollTop, scrollLeft }` |\n| end-reached | `'top' \\| 'bottom' \\| 'left' \\| 'right'` |\n\n### Expose\n\n`wrapRef`、`update`、`scrollTo`、`setScrollTop`、`setScrollLeft`、`handleScroll`\n\n## Events\n\n| 事件名 | 参数 | 说明 |\n| --- | --- | --- |\n| `scroll` | `{ scrollTop, scrollLeft }` | 滚动位置变化。 |\n| `end-reached` | `'top' \\| 'bottom' \\| 'left' \\| 'right'` | 滚动到边缘。 |\n\n## Slots\n\n| 插槽名 | 说明 |\n| --- | --- |\n| `default` | 可滚动内容。 |\n\n## 类型\n\n<h4 id=\"ScrollbarAriaOrientation\">ScrollbarAriaOrientation</h4>\n\n完整定义见源码 `types.ts`。\n\n```ts\ntype ScrollbarAriaOrientation = 'horizontal' | 'vertical'\n```\n\n<h4 id=\"ScrollbarClassValue\">ScrollbarClassValue</h4>\n\n完整定义见源码 `types.ts`。\n\n```ts\ntype ScrollbarClassValue = | string\n | string[]\n | Record<string, boolean>\n | Array<string | Record<string, boolean> | null | undefined | false>\n```\n"
15704
15868
  },
15705
15869
  "en-US": {
15706
15870
  "title": "Scrollbar",
@@ -15719,17 +15883,17 @@
15719
15883
  {
15720
15884
  "id": "basic",
15721
15885
  "title": "Basic",
15722
- "body": "Use `height` to fix the viewport height. If omitted, it follows the parent height.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MScrollbar } from 'morya-ui'\n</script>\n\n<template>\n <MScrollbar height=\"240px\" always style=\"width: 200px\">\n <p\n v-for=\"item in 20\"\n :key=\"item\"\n style=\"\n display:flex;align-items:center;justify-content:center;\n height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-primary) 12%, transparent);\n color:var(--m-color-primary);\n \"\n >\n {{ item }}\n </p>\n </MScrollbar>\n</template>\n```"
15886
+ "body": "Use `height` to fix the viewport height. If omitted, it follows the parent height.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MScrollbar } from 'morya-ui'\n</script>\n\n<template>\n <MScrollbar height=\"240px\" always style=\"width: 100%\">\n <p\n v-for=\"item in 20\"\n :key=\"item\"\n style=\"\n display:flex;align-items:center;justify-content:center;\n height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-primary) 12%, transparent);\n color:var(--m-color-primary);\n \"\n >\n {{ item }}\n </p>\n </MScrollbar>\n</template>\n```"
15723
15887
  },
15724
15888
  {
15725
15889
  "id": "max-height",
15726
15890
  "title": "Max height",
15727
- "body": "The scrollbar appears only when content exceeds `max-height`.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MButton, MScrollbar } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst count = ref(3)\n</script>\n\n<template>\n <div style=\"display:grid;gap:0.75rem\">\n <div style=\"display:flex;gap:0.5rem\">\n <MButton label=\"Add\" size=\"small\" @click=\"count++\" />\n <MButton label=\"Remove\" severity=\"secondary\" size=\"small\" @click=\"count = Math.max(0, count - 1)\" />\n </div>\n <MScrollbar max-height=\"220px\" always style=\"width: 200px\">\n <p\n v-for=\"item in count\"\n :key=\"item\"\n style=\"\n display:flex;align-items:center;justify-content:center;\n height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-info) 12%, transparent);\n color:var(--m-color-info);\n \"\n >\n Item {{ item }}\n </p>\n </MScrollbar>\n </div>\n</template>\n```"
15891
+ "body": "The scrollbar appears only when content exceeds `max-height`.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MButton, MScrollbar } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst count = ref(3)\n</script>\n\n<template>\n <div style=\"display:grid;gap:0.75rem\">\n <div style=\"display:flex;gap:0.5rem\">\n <MButton label=\"Add\" size=\"small\" @click=\"count++\" />\n <MButton label=\"Remove\" severity=\"secondary\" size=\"small\" @click=\"count = Math.max(0, count - 1)\" />\n </div>\n <MScrollbar max-height=\"220px\" always style=\"width: 100%\">\n <p\n v-for=\"item in count\"\n :key=\"item\"\n style=\"\n display:flex;align-items:center;justify-content:center;\n height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-info) 12%, transparent);\n color:var(--m-color-info);\n \"\n >\n Item {{ item }}\n </p>\n </MScrollbar>\n </div>\n</template>\n```"
15728
15892
  },
15729
15893
  {
15730
15894
  "id": "horizontal",
15731
15895
  "title": "Horizontal",
15732
- "body": "A horizontal scrollbar appears when content is wider than the container. `trigger=\"none\"` and `always` keep the thumb visible; the default `trigger=\"hover\"` shows it on hover.\n\n`MLayout` uses this component when `native-scrollbar={false}`.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MScrollbar } from 'morya-ui'\n</script>\n\n<template>\n <MScrollbar always>\n <div style=\"display:flex;width:fit-content\">\n <p\n v-for=\"item in 30\"\n :key=\"item\"\n style=\"\n flex-shrink:0;display:flex;align-items:center;justify-content:center;\n width:96px;height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-danger) 12%, transparent);\n color:var(--m-color-danger);\n \"\n >\n {{ item }}\n </p>\n </div>\n </MScrollbar>\n</template>\n```"
15896
+ "body": "A horizontal scrollbar appears when content is wider than the container. `trigger=\"none\"` and `always` keep the thumb visible; the default `trigger=\"hover\"` shows it on hover.\n\n`MLayout`, `MLayoutContent`, `MLayoutSider`, `MDialog`, `MConfirmDialog`, `MDrawer`, `MSplitter`, `MTable`, `MSelect`, `MTreeSelect`, `MDropdown`, `MContextMenu`, `MPopover`, `MConfirmPopup`, `MMenu` (popup), `MMenubar`, `MTieredMenu`, `MTabs`, `MGallery`, `MTimeline` (horizontal), `MTextarea` (autosize `maxRows`), `MTerminal`, `MOrderList`, `MPickList`, `MTreeTable`, and `MVirtualScroller` integrate this component internally.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MScrollbar } from 'morya-ui'\n</script>\n\n<template>\n <MScrollbar always>\n <div style=\"display:flex;width:fit-content\">\n <p\n v-for=\"item in 30\"\n :key=\"item\"\n style=\"\n flex-shrink:0;display:flex;align-items:center;justify-content:center;\n width:96px;height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-danger) 12%, transparent);\n color:var(--m-color-danger);\n \"\n >\n {{ item }}\n </p>\n </div>\n </MScrollbar>\n</template>\n```"
15733
15897
  },
15734
15898
  {
15735
15899
  "id": "always-native",
@@ -15762,7 +15926,7 @@
15762
15926
  "body": "| Slot | Description |\n| --- | --- |\n| `default` | Scrollable content. |"
15763
15927
  }
15764
15928
  ],
15765
- "markdown": "---\ntitle: Scrollbar\ncategory: 01 / BASIC\ndescription: Themeable custom scrollbar for a consistent scrolling experience.\n---\n\n# Scrollbar\n\nReplaces the native browser scrollbar with a themeable, cross-browser scrolling experience.\n\n## Import\n\n```ts\nimport { MScrollbar } from 'morya-ui'\n```\n\n## Basic\n\nUse `height` to fix the viewport height. If omitted, it follows the parent height.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MScrollbar } from 'morya-ui'\n</script>\n\n<template>\n <MScrollbar height=\"240px\" always style=\"width: 200px\">\n <p\n v-for=\"item in 20\"\n :key=\"item\"\n style=\"\n display:flex;align-items:center;justify-content:center;\n height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-primary) 12%, transparent);\n color:var(--m-color-primary);\n \"\n >\n {{ item }}\n </p>\n </MScrollbar>\n</template>\n```\n\n## Max height\n\nThe scrollbar appears only when content exceeds `max-height`.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MButton, MScrollbar } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst count = ref(3)\n</script>\n\n<template>\n <div style=\"display:grid;gap:0.75rem\">\n <div style=\"display:flex;gap:0.5rem\">\n <MButton label=\"Add\" size=\"small\" @click=\"count++\" />\n <MButton label=\"Remove\" severity=\"secondary\" size=\"small\" @click=\"count = Math.max(0, count - 1)\" />\n </div>\n <MScrollbar max-height=\"220px\" always style=\"width: 200px\">\n <p\n v-for=\"item in count\"\n :key=\"item\"\n style=\"\n display:flex;align-items:center;justify-content:center;\n height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-info) 12%, transparent);\n color:var(--m-color-info);\n \"\n >\n Item {{ item }}\n </p>\n </MScrollbar>\n </div>\n</template>\n```\n\n## Horizontal\n\nA horizontal scrollbar appears when content is wider than the container. `trigger=\"none\"` and `always` keep the thumb visible; the default `trigger=\"hover\"` shows it on hover.\n\n`MLayout` uses this component when `native-scrollbar={false}`.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MScrollbar } from 'morya-ui'\n</script>\n\n<template>\n <MScrollbar always>\n <div style=\"display:flex;width:fit-content\">\n <p\n v-for=\"item in 30\"\n :key=\"item\"\n style=\"\n flex-shrink:0;display:flex;align-items:center;justify-content:center;\n width:96px;height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-danger) 12%, transparent);\n color:var(--m-color-danger);\n \"\n >\n {{ item }}\n </p>\n </div>\n </MScrollbar>\n</template>\n```\n\n## Always / Native\n\n`always` keeps the custom thumb visible. `native` uses the browser scrollbar.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MScrollbar } from 'morya-ui'\n</script>\n\n<template>\n <div style=\"display:grid;gap:1.25rem;grid-template-columns:1fr 1fr\">\n <div>\n <p style=\"margin:0 0 0.5rem;color:var(--m-color-text-muted)\">\n always\n </p>\n <MScrollbar height=\"160px\" always>\n <p v-for=\"n in 12\" :key=\"n\" style=\"margin:0.5rem 0\">\n Line {{ n }}\n </p>\n </MScrollbar>\n </div>\n <div>\n <p style=\"margin:0 0 0.5rem;color:var(--m-color-text-muted)\">\n native\n </p>\n <MScrollbar height=\"160px\" native style=\"width: 200px\">\n <p v-for=\"n in 12\" :key=\"n\" style=\"margin:0.5rem 0\">\n Line {{ n }}\n </p>\n </MScrollbar>\n </div>\n </div>\n</template>\n```\n\n## Manual scroll\n\nControl scrolling with instance methods `setScrollTop` / `setScrollLeft` / `scrollTo` / `update`.\n\n```vue preview\n<script setup lang=\"ts\">\nimport type { ScrollbarInstance } from 'morya-ui'\nimport { MButton, MScrollbar } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst scrollbarRef = ref<ScrollbarInstance>()\nconst scrollTop = ref(0)\n\nfunction jump(top: number) {\n scrollbarRef.value?.setScrollTop(top)\n}\n\nfunction onScroll(payload: { scrollTop: number }) {\n scrollTop.value = Math.round(payload.scrollTop)\n}\n</script>\n\n<template>\n <div style=\"display:grid;gap:0.75rem\">\n <div style=\"display:flex;flex-wrap:wrap;gap:0.5rem;align-items:center\">\n <MButton label=\"Top\" size=\"small\" @click=\"jump(0)\" />\n <MButton label=\"Mid\" size=\"small\" severity=\"secondary\" @click=\"jump(200)\" />\n <MButton label=\"Bottom\" size=\"small\" severity=\"secondary\" @click=\"jump(9999)\" />\n <span style=\"color:var(--m-color-text-muted);font-size:0.875rem\">scrollTop: {{ scrollTop }}</span>\n </div>\n <MScrollbar ref=\"scrollbarRef\" height=\"200px\" always style=\"width: 200px\" @scroll=\"onScroll\">\n <p\n v-for=\"item in 24\"\n :key=\"item\"\n style=\"\n display:flex;align-items:center;justify-content:center;\n height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-success) 12%, transparent);\n color:var(--m-color-success);\n \"\n >\n {{ item }}\n </p>\n </MScrollbar>\n </div>\n</template>\n```\n\n## Infinite scroll\n\nEmits `end-reached` at the edge. Use it for infinite loading.\n\n```vue preview\n<script setup lang=\"ts\">\nimport type { ScrollbarDirection } from 'morya-ui'\nimport { MScrollbar } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst num = ref(20)\n\nfunction loadMore(direction: ScrollbarDirection) {\n if (direction === 'bottom') num.value += 5\n}\n</script>\n\n<template>\n <MScrollbar height=\"220px\" style=\"width: 200px\" always @end-reached=\"loadMore\">\n <p\n v-for=\"item in num\"\n :key=\"item\"\n style=\"\n display:flex;align-items:center;justify-content:center;\n height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-primary) 12%, transparent);\n color:var(--m-color-primary);\n \"\n >\n {{ item }}\n </p>\n </MScrollbar>\n</template>\n```\n\n## API\n\n### Props\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| height | `string \\| number` | — | Viewport height |\n| maxHeight | `string \\| number` | — | Maximum height |\n| fitContent | `boolean` | `false` | Grow with content; pair with CSS `max-height` on the root (dropdown panels) |\n| native | `boolean` | `false` | Use the native scrollbar |\n| wrapStyle / wrapClass | style / class | — | Wrap container styles |\n| viewStyle / viewClass | style / class | — | Content area styles |\n| noresize | `boolean` | `false` | Do not listen for size changes |\n| tag | `string` | `div` | Content area tag |\n| always | `boolean` | `false` | Always show the thumb |\n| trigger | `'hover' \\| 'none'` | `'hover'` | `none` keeps thumbs visible; `always` still wins |\n| minSize | `number` | `20` | Minimum thumb size |\n| id / role / ariaLabel / ariaOrientation | a11y | — | Accessible attributes for the content area |\n| tabindex | `number \\| string` | — | tabindex on the wrap |\n| distance | `number` | `0` | Edge distance that triggers `end-reached` |\n\n### Events\n\n| Name | Payload |\n| --- | --- |\n| scroll | `{ scrollTop, scrollLeft }` |\n| end-reached | `'top' \\| 'bottom' \\| 'left' \\| 'right'` |\n\n### Expose\n\n`wrapRef`, `update`, `scrollTo`, `setScrollTop`, `setScrollLeft`, `handleScroll`\n\n## Events\n\n| Event | Payload | Description |\n| --- | --- | --- |\n| `scroll` | `{ scrollTop, scrollLeft }` | Scroll position change. |\n| `end-reached` | `'top' \\| 'bottom' \\| 'left' \\| 'right'` | Scroll boundary reached. |\n\n## Slots\n\n| Slot | Description |\n| --- | --- |\n| `default` | Scrollable content. |\n"
15929
+ "markdown": "---\ntitle: Scrollbar\ncategory: 01 / BASIC\ndescription: Themeable custom scrollbar for a consistent scrolling experience.\n---\n\n# Scrollbar\n\nReplaces the native browser scrollbar with a themeable, cross-browser scrolling experience.\n\n## Import\n\n```ts\nimport { MScrollbar } from 'morya-ui'\n```\n\n## Basic\n\nUse `height` to fix the viewport height. If omitted, it follows the parent height.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MScrollbar } from 'morya-ui'\n</script>\n\n<template>\n <MScrollbar height=\"240px\" always style=\"width: 100%\">\n <p\n v-for=\"item in 20\"\n :key=\"item\"\n style=\"\n display:flex;align-items:center;justify-content:center;\n height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-primary) 12%, transparent);\n color:var(--m-color-primary);\n \"\n >\n {{ item }}\n </p>\n </MScrollbar>\n</template>\n```\n\n## Max height\n\nThe scrollbar appears only when content exceeds `max-height`.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MButton, MScrollbar } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst count = ref(3)\n</script>\n\n<template>\n <div style=\"display:grid;gap:0.75rem\">\n <div style=\"display:flex;gap:0.5rem\">\n <MButton label=\"Add\" size=\"small\" @click=\"count++\" />\n <MButton label=\"Remove\" severity=\"secondary\" size=\"small\" @click=\"count = Math.max(0, count - 1)\" />\n </div>\n <MScrollbar max-height=\"220px\" always style=\"width: 100%\">\n <p\n v-for=\"item in count\"\n :key=\"item\"\n style=\"\n display:flex;align-items:center;justify-content:center;\n height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-info) 12%, transparent);\n color:var(--m-color-info);\n \"\n >\n Item {{ item }}\n </p>\n </MScrollbar>\n </div>\n</template>\n```\n\n## Horizontal\n\nA horizontal scrollbar appears when content is wider than the container. `trigger=\"none\"` and `always` keep the thumb visible; the default `trigger=\"hover\"` shows it on hover.\n\n`MLayout`, `MLayoutContent`, `MLayoutSider`, `MDialog`, `MConfirmDialog`, `MDrawer`, `MSplitter`, `MTable`, `MSelect`, `MTreeSelect`, `MDropdown`, `MContextMenu`, `MPopover`, `MConfirmPopup`, `MMenu` (popup), `MMenubar`, `MTieredMenu`, `MTabs`, `MGallery`, `MTimeline` (horizontal), `MTextarea` (autosize `maxRows`), `MTerminal`, `MOrderList`, `MPickList`, `MTreeTable`, and `MVirtualScroller` integrate this component internally.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MScrollbar } from 'morya-ui'\n</script>\n\n<template>\n <MScrollbar always>\n <div style=\"display:flex;width:fit-content\">\n <p\n v-for=\"item in 30\"\n :key=\"item\"\n style=\"\n flex-shrink:0;display:flex;align-items:center;justify-content:center;\n width:96px;height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-danger) 12%, transparent);\n color:var(--m-color-danger);\n \"\n >\n {{ item }}\n </p>\n </div>\n </MScrollbar>\n</template>\n```\n\n## Always / Native\n\n`always` keeps the custom thumb visible. `native` uses the browser scrollbar.\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MScrollbar } from 'morya-ui'\n</script>\n\n<template>\n <div style=\"display:grid;gap:1.25rem;grid-template-columns:1fr 1fr\">\n <div>\n <p style=\"margin:0 0 0.5rem;color:var(--m-color-text-muted)\">\n always\n </p>\n <MScrollbar height=\"160px\" always>\n <p v-for=\"n in 12\" :key=\"n\" style=\"margin:0.5rem 0\">\n Line {{ n }}\n </p>\n </MScrollbar>\n </div>\n <div>\n <p style=\"margin:0 0 0.5rem;color:var(--m-color-text-muted)\">\n native\n </p>\n <MScrollbar height=\"160px\" native style=\"width: 200px\">\n <p v-for=\"n in 12\" :key=\"n\" style=\"margin:0.5rem 0\">\n Line {{ n }}\n </p>\n </MScrollbar>\n </div>\n </div>\n</template>\n```\n\n## Manual scroll\n\nControl scrolling with instance methods `setScrollTop` / `setScrollLeft` / `scrollTo` / `update`.\n\n```vue preview\n<script setup lang=\"ts\">\nimport type { ScrollbarInstance } from 'morya-ui'\nimport { MButton, MScrollbar } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst scrollbarRef = ref<ScrollbarInstance>()\nconst scrollTop = ref(0)\n\nfunction jump(top: number) {\n scrollbarRef.value?.setScrollTop(top)\n}\n\nfunction onScroll(payload: { scrollTop: number }) {\n scrollTop.value = Math.round(payload.scrollTop)\n}\n</script>\n\n<template>\n <div style=\"display:grid;gap:0.75rem\">\n <div style=\"display:flex;flex-wrap:wrap;gap:0.5rem;align-items:center\">\n <MButton label=\"Top\" size=\"small\" @click=\"jump(0)\" />\n <MButton label=\"Mid\" size=\"small\" severity=\"secondary\" @click=\"jump(200)\" />\n <MButton label=\"Bottom\" size=\"small\" severity=\"secondary\" @click=\"jump(9999)\" />\n <span style=\"color:var(--m-color-text-muted);font-size:0.875rem\">scrollTop: {{ scrollTop }}</span>\n </div>\n <MScrollbar ref=\"scrollbarRef\" height=\"200px\" always style=\"width: 200px\" @scroll=\"onScroll\">\n <p\n v-for=\"item in 24\"\n :key=\"item\"\n style=\"\n display:flex;align-items:center;justify-content:center;\n height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-success) 12%, transparent);\n color:var(--m-color-success);\n \"\n >\n {{ item }}\n </p>\n </MScrollbar>\n </div>\n</template>\n```\n\n## Infinite scroll\n\nEmits `end-reached` at the edge. Use it for infinite loading.\n\n```vue preview\n<script setup lang=\"ts\">\nimport type { ScrollbarDirection } from 'morya-ui'\nimport { MScrollbar } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst num = ref(20)\n\nfunction loadMore(direction: ScrollbarDirection) {\n if (direction === 'bottom') num.value += 5\n}\n</script>\n\n<template>\n <MScrollbar height=\"220px\" style=\"width: 200px\" always @end-reached=\"loadMore\">\n <p\n v-for=\"item in num\"\n :key=\"item\"\n style=\"\n display:flex;align-items:center;justify-content:center;\n height:48px;margin:8px;border-radius:6px;\n background:color-mix(in srgb, var(--m-color-primary) 12%, transparent);\n color:var(--m-color-primary);\n \"\n >\n {{ item }}\n </p>\n </MScrollbar>\n</template>\n```\n\n## API\n\n### Props\n\n| Name | Type | Default | Description |\n| --- | --- | --- | --- |\n| height | `string \\| number` | — | Viewport height |\n| maxHeight | `string \\| number` | — | Maximum height |\n| fitContent | `boolean` | `false` | Grow with content; pair with CSS `max-height` on the root (dropdown panels) |\n| native | `boolean` | `false` | Use the native scrollbar |\n| wrapStyle / wrapClass | style / class | — | Wrap container styles |\n| viewStyle / viewClass | style / class | — | Content area styles |\n| noresize | `boolean` | `false` | Do not listen for size changes |\n| tag | `string` | `div` | Content area tag |\n| always | `boolean` | `false` | Always show the thumb |\n| trigger | `'hover' \\| 'none'` | `'hover'` | `none` keeps thumbs visible; `always` still wins |\n| minSize | `number` | `20` | Minimum thumb size |\n| id / role / ariaLabel / ariaOrientation | a11y | — | Accessible attributes for the content area |\n| tabindex | `number \\| string` | — | tabindex on the wrap |\n| distance | `number` | `0` | Edge distance that triggers `end-reached` |\n\n### Events\n\n| Name | Payload |\n| --- | --- |\n| scroll | `{ scrollTop, scrollLeft }` |\n| end-reached | `'top' \\| 'bottom' \\| 'left' \\| 'right'` |\n\n### Expose\n\n`wrapRef`, `update`, `scrollTo`, `setScrollTop`, `setScrollLeft`, `handleScroll`\n\n## Events\n\n| Event | Payload | Description |\n| --- | --- | --- |\n| `scroll` | `{ scrollTop, scrollLeft }` | Scroll position change. |\n| `end-reached` | `'top' \\| 'bottom' \\| 'left' \\| 'right'` | Scroll boundary reached. |\n\n## Slots\n\n| Slot | Description |\n| --- | --- |\n| `default` | Scrollable content. |\n"
15766
15930
  }
15767
15931
  }
15768
15932
  },
@@ -15841,7 +16005,7 @@
15841
16005
  "sectionId": "基础用法",
15842
16006
  "lang": "vue",
15843
16007
  "preview": true,
15844
- "code": "<script setup lang=\"ts\">\nimport { MScrollTop } from 'morya-ui'\n</script>\n\n<template>\n <div style=\"height: 8rem; overflow: auto; position: relative\">\n <div style=\"height: 40rem\">\n 向下滚动…\n </div>\n <MScrollTop :threshold=\"80\" target=\"parent\" :right=\"16\" :bottom=\"16\" />\n </div>\n</template>",
16008
+ "code": "<script setup lang=\"ts\">\nimport { MScrollTop, MScrollbar } from 'morya-ui'\n</script>\n\n<template>\n <div style=\"height: 8rem; position: relative\">\n <MScrollbar height=\"8rem\">\n <div style=\"height: 40rem; padding: var(--m-space-3)\">\n 向下滚动…\n </div>\n <MScrollTop :threshold=\"80\" target=\"parent\" :right=\"16\" :bottom=\"16\" />\n </MScrollbar>\n </div>\n</template>",
15845
16009
  "locale": "zh-CN"
15846
16010
  },
15847
16011
  {
@@ -15859,7 +16023,7 @@
15859
16023
  "sectionId": "basic",
15860
16024
  "lang": "vue",
15861
16025
  "preview": true,
15862
- "code": "<script setup lang=\"ts\">\nimport { MScrollTop } from 'morya-ui'\n</script>\n\n<template>\n <div style=\"height: 8rem; overflow: auto; position: relative\">\n <div style=\"height: 40rem\">\n Scroll down…\n </div>\n <MScrollTop :threshold=\"80\" target=\"parent\" :right=\"16\" :bottom=\"16\" />\n </div>\n</template>",
16026
+ "code": "<script setup lang=\"ts\">\nimport { MScrollTop, MScrollbar } from 'morya-ui'\n</script>\n\n<template>\n <div style=\"height: 8rem; position: relative\">\n <MScrollbar height=\"8rem\">\n <div style=\"height: 40rem; padding: var(--m-space-3)\">\n Scroll down…\n </div>\n <MScrollTop :threshold=\"80\" target=\"parent\" :right=\"16\" :bottom=\"16\" />\n </MScrollbar>\n </div>\n</template>",
15863
16027
  "locale": "en-US"
15864
16028
  }
15865
16029
  ],
@@ -15881,7 +16045,12 @@
15881
16045
  {
15882
16046
  "id": "基础用法",
15883
16047
  "title": "基础用法",
15884
- "body": "```vue preview\n<script setup lang=\"ts\">\nimport { MScrollTop } from 'morya-ui'\n</script>\n\n<template>\n <div style=\"height: 8rem; overflow: auto; position: relative\">\n <div style=\"height: 40rem\">\n 向下滚动…\n </div>\n <MScrollTop :threshold=\"80\" target=\"parent\" :right=\"16\" :bottom=\"16\" />\n </div>\n</template>\n```"
16048
+ "body": "```vue preview\n<script setup lang=\"ts\">\nimport { MScrollTop, MScrollbar } from 'morya-ui'\n</script>\n\n<template>\n <div style=\"height: 8rem; position: relative\">\n <MScrollbar height=\"8rem\">\n <div style=\"height: 40rem; padding: var(--m-space-3)\">\n 向下滚动…\n </div>\n <MScrollTop :threshold=\"80\" target=\"parent\" :right=\"16\" :bottom=\"16\" />\n </MScrollbar>\n </div>\n</template>\n```"
16049
+ },
16050
+ {
16051
+ "id": "与-mscrollbar-配合",
16052
+ "title": "与 MScrollbar 配合",
16053
+ "body": "`target=\"parent\"` 时会向上查找最近的 `.m-scrollbar__wrap`(或原生 overflow 容器)作为滚动源。推荐将 `MScrollTop` 放在 `MScrollbar` 内容 slot 内,并保留 `teleport`(默认)以便按钮固定在视口角落。"
15885
16054
  },
15886
16055
  {
15887
16056
  "id": "props",
@@ -15899,7 +16068,7 @@
15899
16068
  "body": "| 插槽名 | 说明 |\n| --- | --- |\n| `default` | 默认内容。 |"
15900
16069
  }
15901
16070
  ],
15902
- "markdown": "---\ntitle: ScrollTop\ncategory: 04 / NAVIGATION\ndescription: 滚动超过阈值后显示回到顶部按钮。\n---\n\n# ScrollTop\n\n监听窗口或父容器滚动,一键回到顶部。\n\n## 引入\n\n```ts\nimport { MScrollTop } from 'morya-ui'\n```\n\n## 基础用法\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MScrollTop } from 'morya-ui'\n</script>\n\n<template>\n <div style=\"height: 8rem; overflow: auto; position: relative\">\n <div style=\"height: 40rem\">\n 向下滚动…\n </div>\n <MScrollTop :threshold=\"80\" target=\"parent\" :right=\"16\" :bottom=\"16\" />\n </div>\n</template>\n```\n\n## Props\n\n| 参数 | 类型 | 默认值 | 说明 |\n| --- | --- | --- | --- |\n| `threshold` | `number` | `400` | 显示阈值(px)。 |\n| `target` | `'window' \\| 'parent'` | `'window'` | 滚动目标。 |\n| `right` | `string \\| number` | — | 距右边缘;数字为 px。 |\n| `bottom` | `string \\| number` | — | 距底边缘;数字为 px。 |\n| `teleport` | `boolean` | `true` | 浮层 Teleport;默认挂到 `body`。 |\n| `appendTo` | `string \\| HTMLElement \\| 'self' \\| false` | `'body'` | 挂载目标;`'self'` / `false` 就地渲染。 |\n| `pt` | [RootPassThrough](/docs/types#RootPassThrough) `{ root? }` | — | DOM 透传,见 [样式与 attrs](/docs/attrs). |\n\n\n## Events\n\n无自定义事件。\n\n## Slots\n\n| 插槽名 | 说明 |\n| --- | --- |\n| `default` | 默认内容。 |\n\n"
16071
+ "markdown": "---\ntitle: ScrollTop\ncategory: 04 / NAVIGATION\ndescription: 滚动超过阈值后显示回到顶部按钮。\n---\n\n# ScrollTop\n\n监听窗口或父容器滚动,一键回到顶部。\n\n## 引入\n\n```ts\nimport { MScrollTop } from 'morya-ui'\n```\n\n## 基础用法\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MScrollTop, MScrollbar } from 'morya-ui'\n</script>\n\n<template>\n <div style=\"height: 8rem; position: relative\">\n <MScrollbar height=\"8rem\">\n <div style=\"height: 40rem; padding: var(--m-space-3)\">\n 向下滚动…\n </div>\n <MScrollTop :threshold=\"80\" target=\"parent\" :right=\"16\" :bottom=\"16\" />\n </MScrollbar>\n </div>\n</template>\n```\n\n## 与 MScrollbar 配合\n\n`target=\"parent\"` 时会向上查找最近的 `.m-scrollbar__wrap`(或原生 overflow 容器)作为滚动源。推荐将 `MScrollTop` 放在 `MScrollbar` 内容 slot 内,并保留 `teleport`(默认)以便按钮固定在视口角落。\n\n## Props\n\n| 参数 | 类型 | 默认值 | 说明 |\n| --- | --- | --- | --- |\n| `threshold` | `number` | `400` | 显示阈值(px)。 |\n| `target` | `'window' \\| 'parent'` | `'window'` | 滚动目标。 |\n| `right` | `string \\| number` | — | 距右边缘;数字为 px。 |\n| `bottom` | `string \\| number` | — | 距底边缘;数字为 px。 |\n| `teleport` | `boolean` | `true` | 浮层 Teleport;默认挂到 `body`。 |\n| `appendTo` | `string \\| HTMLElement \\| 'self' \\| false` | `'body'` | 挂载目标;`'self'` / `false` 就地渲染。 |\n| `pt` | [RootPassThrough](/docs/types#RootPassThrough) `{ root? }` | — | DOM 透传,见 [样式与 attrs](/docs/attrs). |\n\n\n## Events\n\n无自定义事件。\n\n## Slots\n\n| 插槽名 | 说明 |\n| --- | --- |\n| `default` | 默认内容。 |\n\n"
15903
16072
  },
15904
16073
  "en-US": {
15905
16074
  "title": "ScrollTop",
@@ -15918,7 +16087,12 @@
15918
16087
  {
15919
16088
  "id": "basic",
15920
16089
  "title": "Basic",
15921
- "body": "```vue preview\n<script setup lang=\"ts\">\nimport { MScrollTop } from 'morya-ui'\n</script>\n\n<template>\n <div style=\"height: 8rem; overflow: auto; position: relative\">\n <div style=\"height: 40rem\">\n Scroll down…\n </div>\n <MScrollTop :threshold=\"80\" target=\"parent\" :right=\"16\" :bottom=\"16\" />\n </div>\n</template>\n```"
16090
+ "body": "```vue preview\n<script setup lang=\"ts\">\nimport { MScrollTop, MScrollbar } from 'morya-ui'\n</script>\n\n<template>\n <div style=\"height: 8rem; position: relative\">\n <MScrollbar height=\"8rem\">\n <div style=\"height: 40rem; padding: var(--m-space-3)\">\n Scroll down…\n </div>\n <MScrollTop :threshold=\"80\" target=\"parent\" :right=\"16\" :bottom=\"16\" />\n </MScrollbar>\n </div>\n</template>\n```"
16091
+ },
16092
+ {
16093
+ "id": "with-mscrollbar",
16094
+ "title": "With MScrollbar",
16095
+ "body": "When `target=\"parent\"`, ScrollTop walks up the DOM for the nearest `.m-scrollbar__wrap` (or native overflow container). Place `MScrollTop` inside the `MScrollbar` default slot and keep `teleport` enabled (default) so the button stays pinned to the viewport corner."
15922
16096
  },
15923
16097
  {
15924
16098
  "id": "props",
@@ -15936,7 +16110,7 @@
15936
16110
  "body": "No slots."
15937
16111
  }
15938
16112
  ],
15939
- "markdown": "---\ntitle: ScrollTop\ncategory: 04 / NAVIGATION\ndescription: Shows a back-to-top button after scrolling past a threshold.\n---\n\n# ScrollTop\n\nListens to window or parent scroll and jumps back to the top.\n\n## Import\n\n```ts\nimport { MScrollTop } from 'morya-ui'\n```\n\n## Basic\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MScrollTop } from 'morya-ui'\n</script>\n\n<template>\n <div style=\"height: 8rem; overflow: auto; position: relative\">\n <div style=\"height: 40rem\">\n Scroll down…\n </div>\n <MScrollTop :threshold=\"80\" target=\"parent\" :right=\"16\" :bottom=\"16\" />\n </div>\n</template>\n```\n\n## Props\n\n| Prop | Type | Default | Description |\n| --- | --- | --- | --- |\n| `threshold` | `number` | `400` | Show threshold in px. |\n| `target` | `'window' \\| 'parent'` | `'window'` | Scroll target. |\n| `right` | `string \\| number` | — | Distance from the right edge; a number is pixels. |\n| `bottom` | `string \\| number` | — | Distance from the bottom edge; a number is pixels. |\n| `teleport` | `boolean` | `true` | Overlay Teleport. Mounts to `body` by default. |\n| `appendTo` | `string \\| HTMLElement \\| 'self' \\| false` | `'body'` | Mount target. `'self'` / `false` renders in place. |\n| `pt` | [RootPassThrough](/docs/types#RootPassThrough) `{ root? }` | — | Pass-through; see [Styling & attrs](/docs/attrs). |\n\n\n## Events\n\nNo custom events.\n\n## Slots\n\nNo slots.\n"
16113
+ "markdown": "---\ntitle: ScrollTop\ncategory: 04 / NAVIGATION\ndescription: Shows a back-to-top button after scrolling past a threshold.\n---\n\n# ScrollTop\n\nListens to window or parent scroll and jumps back to the top.\n\n## Import\n\n```ts\nimport { MScrollTop } from 'morya-ui'\n```\n\n## Basic\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MScrollTop, MScrollbar } from 'morya-ui'\n</script>\n\n<template>\n <div style=\"height: 8rem; position: relative\">\n <MScrollbar height=\"8rem\">\n <div style=\"height: 40rem; padding: var(--m-space-3)\">\n Scroll down…\n </div>\n <MScrollTop :threshold=\"80\" target=\"parent\" :right=\"16\" :bottom=\"16\" />\n </MScrollbar>\n </div>\n</template>\n```\n\n## With MScrollbar\n\nWhen `target=\"parent\"`, ScrollTop walks up the DOM for the nearest `.m-scrollbar__wrap` (or native overflow container). Place `MScrollTop` inside the `MScrollbar` default slot and keep `teleport` enabled (default) so the button stays pinned to the viewport corner.\n\n## Props\n\n| Prop | Type | Default | Description |\n| --- | --- | --- | --- |\n| `threshold` | `number` | `400` | Show threshold in px. |\n| `target` | `'window' \\| 'parent'` | `'window'` | Scroll target. |\n| `right` | `string \\| number` | — | Distance from the right edge; a number is pixels. |\n| `bottom` | `string \\| number` | — | Distance from the bottom edge; a number is pixels. |\n| `teleport` | `boolean` | `true` | Overlay Teleport. Mounts to `body` by default. |\n| `appendTo` | `string \\| HTMLElement \\| 'self' \\| false` | `'body'` | Mount target. `'self'` / `false` renders in place. |\n| `pt` | [RootPassThrough](/docs/types#RootPassThrough) `{ root? }` | — | Pass-through; see [Styling & attrs](/docs/attrs). |\n\n\n## Events\n\nNo custom events.\n\n## Slots\n\nNo slots.\n"
15940
16114
  }
15941
16115
  }
15942
16116
  },
@@ -21204,6 +21378,11 @@
21204
21378
  "title": "弹出模式",
21205
21379
  "body": "`popup` + `v-model` 控制浮层显隐,适合工具栏按钮触发:\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MButton, MTieredMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst open = ref(false)\nconst model = [{ label: '复制' }, { label: '粘贴' }]\n</script>\n\n<template>\n <MButton label=\"操作\" @click=\"open = true\" />\n <MTieredMenu v-model=\"open\" popup :model=\"model\" />\n</template>\n```"
21206
21380
  },
21381
+ {
21382
+ "id": "滚动与子菜单",
21383
+ "title": "滚动与子菜单",
21384
+ "body": "主列表与侧向子菜单使用内置 `MScrollbar`(`max-height: min(18rem, 45vh)`)。子菜单 Teleport 到 `body` 相对触发项定位,长列表滚动时不会被主面板裁剪。"
21385
+ },
21207
21386
  {
21208
21387
  "id": "菜单项结构",
21209
21388
  "title": "菜单项结构",
@@ -21230,7 +21409,7 @@
21230
21409
  "body": "<h4 id=\"TieredMenuItem\">TieredMenuItem</h4>\n\n完整定义见源码 `types.ts`。\n\n```ts\ninterface TieredMenuItem {\n label?: string\n command?: () => void\n disabled?: boolean\n separator?: boolean\n items?: TieredMenuItem[]\n}\n```"
21231
21410
  }
21232
21411
  ],
21233
- "markdown": "---\ntitle: TieredMenu\ncategory: 04 / NAVIGATION\ndescription: 带一层子菜单的垂直分层菜单。\n---\n\n# TieredMenu\n\n垂直菜单,支持悬停或点击展开**一层**子菜单。`popup` 模式下可作为浮层弹出。\n\n## 引入\n\n```ts\nimport { MTieredMenu, type TieredMenuItem } from 'morya-ui'\n```\n\n## 基础用法\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MTieredMenu } from 'morya-ui'\n\nconst model = [\n {\n label: '文件',\n items: [\n { label: '新建', command: () => window.alert('新建') },\n { label: '导出' },\n ],\n },\n { separator: true },\n { label: '帮助' },\n]\n</script>\n\n<template>\n <MTieredMenu :model=\"model\" />\n</template>\n```\n\n## 弹出模式\n\n`popup` + `v-model` 控制浮层显隐,适合工具栏按钮触发:\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MButton, MTieredMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst open = ref(false)\nconst model = [{ label: '复制' }, { label: '粘贴' }]\n</script>\n\n<template>\n <MButton label=\"操作\" @click=\"open = true\" />\n <MTieredMenu v-model=\"open\" popup :model=\"model\" />\n</template>\n```\n\n## 菜单项结构\n\n| 字段 | 类型 | 说明 |\n| --- | --- | --- |\n| `label` | `string` | 展示文案。 |\n| `command` | `() => void` | 点击叶子项时执行。 |\n| `disabled` | `boolean` | 禁用项。 |\n| `separator` | `boolean` | 渲染分隔线(忽略其他字段)。 |\n| `items` | `TieredMenuItem[]` | 一层子菜单。 |\n\n## Props\n\n| 参数 | 类型 | 默认值 | 说明 |\n| --- | --- | --- | --- |\n| `model` | `TieredMenuItem[]` | — | 菜单项。 |\n| `popup` | `boolean` | `false` | 弹出模式。 |\n| `modelValue` | `boolean` | `false` | popup 可见性(`v-model`)。 |\n| `teleport` | `boolean` | `true` | popup 时 Teleport;默认 `body`。 |\n| `appendTo` | `string \\| HTMLElement \\| 'self' \\| false` | `'body'` | 挂载目标。 |\n| `placement` | `FloatingOverlayPlacement` | — | — |\n| `pt` | [RootPassThrough](/docs/types#RootPassThrough) `{ root? }` | — | DOM 透传,见 [样式与 attrs](/docs/attrs). |\n\n\n## Events\n\n| 事件名 | 参数 | 说明 |\n| --- | --- | --- |\n| `update:modelValue` | `boolean` | popup 可见性变化。 |\n\n## Slots\n\n无插槽。\n\n## 类型\n\n<h4 id=\"TieredMenuItem\">TieredMenuItem</h4>\n\n完整定义见源码 `types.ts`。\n\n```ts\ninterface TieredMenuItem {\n label?: string\n command?: () => void\n disabled?: boolean\n separator?: boolean\n items?: TieredMenuItem[]\n}\n```\n"
21412
+ "markdown": "---\ntitle: TieredMenu\ncategory: 04 / NAVIGATION\ndescription: 带一层子菜单的垂直分层菜单。\n---\n\n# TieredMenu\n\n垂直菜单,支持悬停或点击展开**一层**子菜单。`popup` 模式下可作为浮层弹出。\n\n## 引入\n\n```ts\nimport { MTieredMenu, type TieredMenuItem } from 'morya-ui'\n```\n\n## 基础用法\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MTieredMenu } from 'morya-ui'\n\nconst model = [\n {\n label: '文件',\n items: [\n { label: '新建', command: () => window.alert('新建') },\n { label: '导出' },\n ],\n },\n { separator: true },\n { label: '帮助' },\n]\n</script>\n\n<template>\n <MTieredMenu :model=\"model\" />\n</template>\n```\n\n## 弹出模式\n\n`popup` + `v-model` 控制浮层显隐,适合工具栏按钮触发:\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MButton, MTieredMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst open = ref(false)\nconst model = [{ label: '复制' }, { label: '粘贴' }]\n</script>\n\n<template>\n <MButton label=\"操作\" @click=\"open = true\" />\n <MTieredMenu v-model=\"open\" popup :model=\"model\" />\n</template>\n```\n\n## 滚动与子菜单\n\n主列表与侧向子菜单使用内置 `MScrollbar`(`max-height: min(18rem, 45vh)`)。子菜单 Teleport 到 `body` 相对触发项定位,长列表滚动时不会被主面板裁剪。\n\n## 菜单项结构\n\n| 字段 | 类型 | 说明 |\n| --- | --- | --- |\n| `label` | `string` | 展示文案。 |\n| `command` | `() => void` | 点击叶子项时执行。 |\n| `disabled` | `boolean` | 禁用项。 |\n| `separator` | `boolean` | 渲染分隔线(忽略其他字段)。 |\n| `items` | `TieredMenuItem[]` | 一层子菜单。 |\n\n## Props\n\n| 参数 | 类型 | 默认值 | 说明 |\n| --- | --- | --- | --- |\n| `model` | `TieredMenuItem[]` | — | 菜单项。 |\n| `popup` | `boolean` | `false` | 弹出模式。 |\n| `modelValue` | `boolean` | `false` | popup 可见性(`v-model`)。 |\n| `teleport` | `boolean` | `true` | popup 时 Teleport;默认 `body`。 |\n| `appendTo` | `string \\| HTMLElement \\| 'self' \\| false` | `'body'` | 挂载目标。 |\n| `placement` | `FloatingOverlayPlacement` | — | — |\n| `pt` | [RootPassThrough](/docs/types#RootPassThrough) `{ root? }` | — | DOM 透传,见 [样式与 attrs](/docs/attrs). |\n\n\n## Events\n\n| 事件名 | 参数 | 说明 |\n| --- | --- | --- |\n| `update:modelValue` | `boolean` | popup 可见性变化。 |\n\n## Slots\n\n无插槽。\n\n## 类型\n\n<h4 id=\"TieredMenuItem\">TieredMenuItem</h4>\n\n完整定义见源码 `types.ts`。\n\n```ts\ninterface TieredMenuItem {\n label?: string\n command?: () => void\n disabled?: boolean\n separator?: boolean\n items?: TieredMenuItem[]\n}\n```\n"
21234
21413
  },
21235
21414
  "en-US": {
21236
21415
  "title": "TieredMenu",
@@ -21256,6 +21435,11 @@
21256
21435
  "title": "Popup mode",
21257
21436
  "body": "Combine `popup` with `v-model` for toolbar triggers:\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MButton, MTieredMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst open = ref(false)\nconst model = [{ label: 'Copy' }, { label: 'Paste' }]\n</script>\n\n<template>\n <MButton label=\"Actions\" @click=\"open = true\" />\n <MTieredMenu v-model=\"open\" popup :model=\"model\" />\n</template>\n```"
21258
21437
  },
21438
+ {
21439
+ "id": "scrolling-submenu-flyout",
21440
+ "title": "Scrolling & submenu flyout",
21441
+ "body": "The root list and side submenu use built-in `MScrollbar` (`max-height: min(18rem, 45vh)`). Submenus teleport to `body` and anchor to the trigger so they stay visible when the root menu scrolls."
21442
+ },
21259
21443
  {
21260
21444
  "id": "item-shape",
21261
21445
  "title": "Item shape",
@@ -21282,7 +21466,7 @@
21282
21466
  "body": "<h4 id=\"TieredMenuItem\">TieredMenuItem</h4>\n\nSee source `types.ts` for the full definition.\n\n```ts\ninterface TieredMenuItem {\n label?: string\n command?: () => void\n disabled?: boolean\n separator?: boolean\n items?: TieredMenuItem[]\n}\n```"
21283
21467
  }
21284
21468
  ],
21285
- "markdown": "---\ntitle: TieredMenu\ncategory: 04 / NAVIGATION\ndescription: A vertical layered menu with one submenu level.\n---\n\n# TieredMenu\n\nVertical menu with **one** submenu level on hover or click. Use `popup` for overlay mode.\n\n## Import\n\n```ts\nimport { MTieredMenu, type TieredMenuItem } from 'morya-ui'\n```\n\n## Basic usage\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MTieredMenu } from 'morya-ui'\n\nconst model = [\n {\n label: 'File',\n items: [\n { label: 'New', command: () => window.alert('New') },\n { label: 'Export' },\n ],\n },\n { separator: true },\n { label: 'Help' },\n]\n</script>\n\n<template>\n <MTieredMenu :model=\"model\" />\n</template>\n```\n\n## Popup mode\n\nCombine `popup` with `v-model` for toolbar triggers:\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MButton, MTieredMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst open = ref(false)\nconst model = [{ label: 'Copy' }, { label: 'Paste' }]\n</script>\n\n<template>\n <MButton label=\"Actions\" @click=\"open = true\" />\n <MTieredMenu v-model=\"open\" popup :model=\"model\" />\n</template>\n```\n\n## Item shape\n\n| Field | Type | Description |\n| --- | --- | --- |\n| `label` | `string` | Display text. |\n| `command` | `() => void` | Runs when a leaf item is activated. |\n| `disabled` | `boolean` | Disabled item. |\n| `separator` | `boolean` | Renders a divider (ignores other fields). |\n| `items` | `TieredMenuItem[]` | One submenu level. |\n\n## Props\n\n| Prop | Type | Default | Description |\n| --- | --- | --- | --- |\n| `model` | `TieredMenuItem[]` | — | Menu items. |\n| `popup` | `boolean` | `false` | Popup overlay mode. |\n| `modelValue` | `boolean` | `false` | Popup visibility (`v-model`). |\n| `teleport` | `boolean` | `true` | Teleport when `popup`; defaults to `body`. |\n| `appendTo` | `string \\| HTMLElement \\| 'self' \\| false` | `'body'` | Mount target. |\n| `pt` | [RootPassThrough](/docs/types#RootPassThrough) `{ root? }` | — | Pass-through; see [Styling & attrs](/docs/attrs). |\n\n\n## Events\n\n| Event | Payload | Description |\n| --- | --- | --- |\n| `update:modelValue` | `boolean` | Popup visibility change. |\n\n## Slots\n\nNo slots.\n\n## Types\n\n<h4 id=\"TieredMenuItem\">TieredMenuItem</h4>\n\nSee source `types.ts` for the full definition.\n\n```ts\ninterface TieredMenuItem {\n label?: string\n command?: () => void\n disabled?: boolean\n separator?: boolean\n items?: TieredMenuItem[]\n}\n```\n"
21469
+ "markdown": "---\ntitle: TieredMenu\ncategory: 04 / NAVIGATION\ndescription: A vertical layered menu with one submenu level.\n---\n\n# TieredMenu\n\nVertical menu with **one** submenu level on hover or click. Use `popup` for overlay mode.\n\n## Import\n\n```ts\nimport { MTieredMenu, type TieredMenuItem } from 'morya-ui'\n```\n\n## Basic usage\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MTieredMenu } from 'morya-ui'\n\nconst model = [\n {\n label: 'File',\n items: [\n { label: 'New', command: () => window.alert('New') },\n { label: 'Export' },\n ],\n },\n { separator: true },\n { label: 'Help' },\n]\n</script>\n\n<template>\n <MTieredMenu :model=\"model\" />\n</template>\n```\n\n## Popup mode\n\nCombine `popup` with `v-model` for toolbar triggers:\n\n```vue preview\n<script setup lang=\"ts\">\nimport { MButton, MTieredMenu } from 'morya-ui'\nimport { ref } from 'vue'\n\nconst open = ref(false)\nconst model = [{ label: 'Copy' }, { label: 'Paste' }]\n</script>\n\n<template>\n <MButton label=\"Actions\" @click=\"open = true\" />\n <MTieredMenu v-model=\"open\" popup :model=\"model\" />\n</template>\n```\n\n## Scrolling & submenu flyout\n\nThe root list and side submenu use built-in `MScrollbar` (`max-height: min(18rem, 45vh)`). Submenus teleport to `body` and anchor to the trigger so they stay visible when the root menu scrolls.\n\n## Item shape\n\n| Field | Type | Description |\n| --- | --- | --- |\n| `label` | `string` | Display text. |\n| `command` | `() => void` | Runs when a leaf item is activated. |\n| `disabled` | `boolean` | Disabled item. |\n| `separator` | `boolean` | Renders a divider (ignores other fields). |\n| `items` | `TieredMenuItem[]` | One submenu level. |\n\n## Props\n\n| Prop | Type | Default | Description |\n| --- | --- | --- | --- |\n| `model` | `TieredMenuItem[]` | — | Menu items. |\n| `popup` | `boolean` | `false` | Popup overlay mode. |\n| `modelValue` | `boolean` | `false` | Popup visibility (`v-model`). |\n| `teleport` | `boolean` | `true` | Teleport when `popup`; defaults to `body`. |\n| `appendTo` | `string \\| HTMLElement \\| 'self' \\| false` | `'body'` | Mount target. |\n| `pt` | [RootPassThrough](/docs/types#RootPassThrough) `{ root? }` | — | Pass-through; see [Styling & attrs](/docs/attrs). |\n\n\n## Events\n\n| Event | Payload | Description |\n| --- | --- | --- |\n| `update:modelValue` | `boolean` | Popup visibility change. |\n\n## Slots\n\nNo slots.\n\n## Types\n\n<h4 id=\"TieredMenuItem\">TieredMenuItem</h4>\n\nSee source `types.ts` for the full definition.\n\n```ts\ninterface TieredMenuItem {\n label?: string\n command?: () => void\n disabled?: boolean\n separator?: boolean\n items?: TieredMenuItem[]\n}\n```\n"
21286
21470
  }
21287
21471
  }
21288
21472
  },
@@ -24417,7 +24601,7 @@
24417
24601
  "zh-CN": {
24418
24602
  "title": "MCP",
24419
24603
  "description": "可选的 MCP 服务,供支持 Model Context Protocol 的 AI 客户端检索本库文档。",
24420
- "markdown": "---\ntitle: MCP\norder: 10\ndescription: 可选的 MCP 服务,供支持 Model Context Protocol 的 AI 客户端检索本库文档。\n---\n\n# MCP\n\n[`@morya-ui/mcp`](https://www.npmjs.com/package/@morya-ui/mcp) 是可选的 [Model Context Protocol](https://modelcontextprotocol.io/)(stdio)服务。它把本站组件文档、示例与指南做成可检索工具,方便 **支持 MCP 的 AI 客户端** 按真实 API 生成代码。\n\n日常使用组件库 **不需要** 安装或配置 MCP。应用里仍然只依赖:\n\n```bash\npnpm add morya-ui\n```\n\n```ts\nimport 'morya-ui/styles.css'\n```\n\n## 接入方式\n\nMCP 客户端通过 stdio 启动本包即可:\n\n```bash\nnpx -y @morya-ui/mcp\n```\n\n通用写法:\n\n```json\n{\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@morya-ui/mcp\"]\n}\n```\n\n字段名因客户端而异,只要支持 MCP stdio 即可接入。\n\n### 常见客户端配置示例\n\n以下为常见产品的配置片段,键名可能随版本变化,以各产品官方文档为准。\n\n**Cursor**(`.cursor/mcp.json` 或用户级 MCP 配置):\n\n```json\n{\n \"mcpServers\": {\n \"morya-ui\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@morya-ui/mcp\"]\n }\n }\n}\n```\n\n**Claude Desktop / Claude Code**(`claude_desktop_config.json` 等):\n\n```json\n{\n \"mcpServers\": {\n \"morya-ui\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@morya-ui/mcp\"]\n }\n }\n}\n```\n\n**Windsurf**(MCP 设置中的 servers 配置):\n\n```json\n{\n \"mcpServers\": {\n \"morya-ui\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@morya-ui/mcp\"]\n }\n }\n}\n```\n\n**Cline**(VS Code 扩展设置中的 MCP servers):\n\n```json\n{\n \"mcpServers\": {\n \"morya-ui\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@morya-ui/mcp\"]\n }\n }\n}\n```\n\n**Zed**(`settings.json` → `context_servers`):\n\n```json\n{\n \"context_servers\": {\n \"morya-ui\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@morya-ui/mcp\"]\n }\n }\n}\n```\n\n**Continue**(`config.json` / YAML 中的 MCP servers,字段名以当前版本为准):\n\n```json\n{\n \"mcpServers\": [\n {\n \"name\": \"morya-ui\",\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@morya-ui/mcp\"]\n }\n ]\n}\n```\n\n## 可用工具\n\n### 基础 — 查组件文档\n\n| 工具 | 作用 |\n| --- | --- |\n| `list` | 列出组件 / 指南 / 示例 / 分类 / 页面模式 |\n| `search` | 搜索文档、示例、页面模式与组件选型指南 |\n| `get_component` | 读取组件说明与 API |\n| `get_example` | 获取源码示例 |\n| `get_guide` | 读取指南 |\n| `get_setup` | 安装与初始化说明 |\n| `validate_usage` | 对照文档粗检 props / events 用法 |\n| `version` | 版本与目录状态 |\n\n### 高级 — 页面组合(可选)\n\n| 工具 | 作用 |\n| --- | --- |\n| `list_patterns` | 列出可复用的页面组合模式 |\n| `get_pattern` | 读取模式的结构、布局与交互规则 |\n| `recommend_page` | 根据页面意图推荐模式;可附带 starter 脚手架 |\n| `get_design_rules` | 设计令牌与组合规则 |\n| `recommend_component` | 列出、阅读或推荐组件选型指南 |\n\n多数工具支持 `mode`:`zh`(默认)或 `en`。\n\n### 推荐工作流\n\n**查单个组件:** `search` / `get_component` → `get_example` → `validate_usage`\n\n**规划整页:** `recommend_page` → `get_pattern` → `get_component` / `get_example` → `get_design_rules`;组件取舍不明时调用 `recommend_component`\n\n需要 starter 代码时,给 `recommend_page` 传 `includeScaffold: true`:\n\n```json\n{\n \"intent\": \"油井管理列表\",\n \"pageType\": \"list\",\n \"features\": [\"筛选\", \"新增\", \"分页\"],\n \"includeScaffold\": true\n}\n```\n\n`recommend_component` 用法:\n\n- 不传 `query` 和 `decision` → 列出全部选型指南\n- 只传 `decision`(如 `overlay-choice`)→ 阅读某一指南\n- 传 `query` → 根据问题推荐组件\n\n## 对话示例\n\n接入后,可直接让助手调用本服务,例如:\n\n> 用 morya-ui 的 MCP 查一下 Dialog 的 props,并给一个带确认 / 取消按钮的示例。\n\n> 搜索和「日期」相关的组件,选一个适合表单的,按文档写出最小用法。\n\n> 根据 MCP 里 Button 的文档,写一个 `severity=\"danger\"` 的删除按钮,并校验 props 是否合法。\n\n助手应先调用工具,再基于返回内容生成类似:\n\n```vue\n<script setup lang=\"ts\">\nimport { MButton } from 'morya-ui'\n</script>\n\n<template>\n <MButton label=\"删除\" severity=\"danger\" />\n</template>\n```\n\n## 与文档站的关系\n\n目录与本站同源(组件 `docs/` + 指南 Markdown)。官网文档更新后,维护者重新发布 `@morya-ui/mcp`,客户端通过 `npx -y` 即可拿到新版本。\n\n更多实现细节见仓库内 [packages/ui-mcp/README.md](https://github.com/morya-space/morya-ui/tree/main/packages/ui-mcp)。\n\n## 下一步\n\n- [快速上手](/docs/quick-start):在应用中安装并使用组件\n- [组件](/components):浏览全部组件与交互示例\n",
24604
+ "markdown": "---\ntitle: MCP\norder: 10\ndescription: 可选的 MCP 服务,供支持 Model Context Protocol 的 AI 客户端检索本库文档。\n---\n\n# MCP\n\n[`@morya-ui/mcp`](https://www.npmjs.com/package/@morya-ui/mcp) 是可选的 [Model Context Protocol](https://modelcontextprotocol.io/)(stdio)服务。它把本站组件文档、示例与指南做成可检索工具,方便 **支持 MCP 的 AI 客户端** 按真实 API 生成代码。\n\n日常使用组件库 **不需要** 安装或配置 MCP。应用里仍然只依赖:\n\n```bash\npnpm add morya-ui\n```\n\n```ts\nimport 'morya-ui/styles.css'\n```\n\n## 接入方式\n\nMCP 客户端通过 stdio 启动本包即可:\n\n```bash\nnpx -y @morya-ui/mcp\n```\n\n通用写法:\n\n```json\n{\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@morya-ui/mcp\"]\n}\n```\n\n字段名因客户端而异,只要支持 MCP stdio 即可接入。\n\n### 常见客户端配置示例\n\n以下为常见产品的配置片段,键名可能随版本变化,以各产品官方文档为准。\n\n**Cursor**(`.cursor/mcp.json` 或用户级 MCP 配置):\n\n```json\n{\n \"mcpServers\": {\n \"morya-ui\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@morya-ui/mcp\"]\n }\n }\n}\n```\n\n**Claude Desktop / Claude Code**(`claude_desktop_config.json` 等):\n\n```json\n{\n \"mcpServers\": {\n \"morya-ui\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@morya-ui/mcp\"]\n }\n }\n}\n```\n\n**Windsurf**(MCP 设置中的 servers 配置):\n\n```json\n{\n \"mcpServers\": {\n \"morya-ui\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@morya-ui/mcp\"]\n }\n }\n}\n```\n\n**Cline**(VS Code 扩展设置中的 MCP servers):\n\n```json\n{\n \"mcpServers\": {\n \"morya-ui\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@morya-ui/mcp\"]\n }\n }\n}\n```\n\n**Zed**(`settings.json` → `context_servers`):\n\n```json\n{\n \"context_servers\": {\n \"morya-ui\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@morya-ui/mcp\"]\n }\n }\n}\n```\n\n**Continue**(`config.json` / YAML 中的 MCP servers,字段名以当前版本为准):\n\n```json\n{\n \"mcpServers\": [\n {\n \"name\": \"morya-ui\",\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@morya-ui/mcp\"]\n }\n ]\n}\n```\n\n## 可用工具\n\n### 基础 — 查组件文档\n\n| 工具 | 作用 |\n| --- | --- |\n| `list` | 列出组件 / 指南 / 示例 / 分类 / 页面模式 |\n| `search` | 搜索文档、示例、页面模式与组件选型指南 |\n| `get_component` | 读取组件说明与 API |\n| `get_example` | 获取源码示例 |\n| `get_guide` | 读取指南 |\n| `get_setup` | 安装与初始化说明 |\n| `validate_usage` | 对照文档粗检 props / events 用法 |\n| `version` | 版本与目录状态 |\n\n### 高级 — 页面组合(可选)\n\n| 工具 | 作用 |\n| --- | --- |\n| `list_patterns` | 列出可复用的页面组合模式 |\n| `get_pattern` | 读取模式的结构、布局与交互规则 |\n| `recommend_page` | 根据页面意图推荐模式;可附带 starter 脚手架 |\n| `get_design_rules` | 设计令牌与 MPage* 组合配方 |\n| `recommend_component` | 列出、阅读或推荐组件选型指南 |\n| `list_golden_pages` | 列出黄金样例页面 |\n| `get_golden_page` | 读取黄金样例 Vue 源码 |\n| `list_page_snippets` | 列出可复用的页面区块 snippet |\n| `get_page_snippet` | 读取局部区块 snippet(筛选区、工具栏等) |\n| `validate_page` | 校验页面组合、间距与双边框问题 |\n\n多数工具支持 `mode`:`zh`(默认)或 `en`。\n\n### 推荐工作流\n\n**查单个组件:** `search` / `get_component` → `get_example` → `validate_usage`\n\n**规划整页:** `recommend_page` → `get_golden_page` → `get_pattern` → `get_design_rules` → `get_component` / `get_example` → `validate_page`\n\n**改局部区块:** `get_page_snippet(section)` → `get_component` / `get_example` → `validate_usage` → `validate_page`\n\n需要 starter 代码时,给 `recommend_page` 传 `includeScaffold: true`:\n\n```json\n{\n \"intent\": \"油井管理列表\",\n \"pageType\": \"list\",\n \"features\": [\"筛选\", \"新增\", \"分页\"],\n \"includeScaffold\": true\n}\n```\n\n`recommend_component` 用法:\n\n- 不传 `query` 和 `decision` → 列出全部选型指南\n- 只传 `decision`(如 `overlay-choice`)→ 阅读某一指南\n- 传 `query` → 根据问题推荐组件\n\n## 对话示例\n\n接入后,可直接让助手调用本服务,例如:\n\n> 用 morya-ui 的 MCP 查一下 Dialog 的 props,并给一个带确认 / 取消按钮的示例。\n\n> 搜索和「日期」相关的组件,选一个适合表单的,按文档写出最小用法。\n\n> 根据 MCP 里 Button 的文档,写一个 `severity=\"danger\"` 的删除按钮,并校验 props 是否合法。\n\n助手应先调用工具,再基于返回内容生成类似:\n\n```vue\n<script setup lang=\"ts\">\nimport { MButton } from 'morya-ui'\n</script>\n\n<template>\n <MButton label=\"删除\" severity=\"danger\" />\n</template>\n```\n\n## 与文档站的关系\n\n目录与本站同源(组件 `docs/` + 指南 Markdown)。官网文档更新后,维护者重新发布 `@morya-ui/mcp`,客户端通过 `npx -y` 即可拿到新版本。\n\n更多实现细节见仓库内 [packages/ui-mcp/README.md](https://github.com/morya-space/morya-ui/tree/main/packages/ui-mcp)。\n\n## 下一步\n\n- [快速上手](/docs/quick-start):在应用中安装并使用组件\n- [组件](/components):浏览全部组件与交互示例\n",
24421
24605
  "sections": [
24422
24606
  {
24423
24607
  "title": "",
@@ -24432,7 +24616,7 @@
24432
24616
  {
24433
24617
  "title": "可用工具",
24434
24618
  "id": "可用工具",
24435
- "body": "### 基础 — 查组件文档\n\n| 工具 | 作用 |\n| --- | --- |\n| `list` | 列出组件 / 指南 / 示例 / 分类 / 页面模式 |\n| `search` | 搜索文档、示例、页面模式与组件选型指南 |\n| `get_component` | 读取组件说明与 API |\n| `get_example` | 获取源码示例 |\n| `get_guide` | 读取指南 |\n| `get_setup` | 安装与初始化说明 |\n| `validate_usage` | 对照文档粗检 props / events 用法 |\n| `version` | 版本与目录状态 |\n\n### 高级 — 页面组合(可选)\n\n| 工具 | 作用 |\n| --- | --- |\n| `list_patterns` | 列出可复用的页面组合模式 |\n| `get_pattern` | 读取模式的结构、布局与交互规则 |\n| `recommend_page` | 根据页面意图推荐模式;可附带 starter 脚手架 |\n| `get_design_rules` | 设计令牌与组合规则 |\n| `recommend_component` | 列出、阅读或推荐组件选型指南 |\n\n多数工具支持 `mode`:`zh`(默认)或 `en`。\n\n### 推荐工作流\n\n**查单个组件:** `search` / `get_component` → `get_example` → `validate_usage`\n\n**规划整页:** `recommend_page` → `get_pattern` → `get_component` / `get_example` → `get_design_rules`;组件取舍不明时调用 `recommend_component`\n\n需要 starter 代码时,给 `recommend_page` 传 `includeScaffold: true`:\n\n```json\n{\n \"intent\": \"油井管理列表\",\n \"pageType\": \"list\",\n \"features\": [\"筛选\", \"新增\", \"分页\"],\n \"includeScaffold\": true\n}\n```\n\n`recommend_component` 用法:\n\n- 不传 `query` 和 `decision` → 列出全部选型指南\n- 只传 `decision`(如 `overlay-choice`)→ 阅读某一指南\n- 传 `query` → 根据问题推荐组件"
24619
+ "body": "### 基础 — 查组件文档\n\n| 工具 | 作用 |\n| --- | --- |\n| `list` | 列出组件 / 指南 / 示例 / 分类 / 页面模式 |\n| `search` | 搜索文档、示例、页面模式与组件选型指南 |\n| `get_component` | 读取组件说明与 API |\n| `get_example` | 获取源码示例 |\n| `get_guide` | 读取指南 |\n| `get_setup` | 安装与初始化说明 |\n| `validate_usage` | 对照文档粗检 props / events 用法 |\n| `version` | 版本与目录状态 |\n\n### 高级 — 页面组合(可选)\n\n| 工具 | 作用 |\n| --- | --- |\n| `list_patterns` | 列出可复用的页面组合模式 |\n| `get_pattern` | 读取模式的结构、布局与交互规则 |\n| `recommend_page` | 根据页面意图推荐模式;可附带 starter 脚手架 |\n| `get_design_rules` | 设计令牌与 MPage* 组合配方 |\n| `recommend_component` | 列出、阅读或推荐组件选型指南 |\n| `list_golden_pages` | 列出黄金样例页面 |\n| `get_golden_page` | 读取黄金样例 Vue 源码 |\n| `list_page_snippets` | 列出可复用的页面区块 snippet |\n| `get_page_snippet` | 读取局部区块 snippet(筛选区、工具栏等) |\n| `validate_page` | 校验页面组合、间距与双边框问题 |\n\n多数工具支持 `mode`:`zh`(默认)或 `en`。\n\n### 推荐工作流\n\n**查单个组件:** `search` / `get_component` → `get_example` → `validate_usage`\n\n**规划整页:** `recommend_page` → `get_golden_page` → `get_pattern` → `get_design_rules` → `get_component` / `get_example` → `validate_page`\n\n**改局部区块:** `get_page_snippet(section)` → `get_component` / `get_example` → `validate_usage` → `validate_page`\n\n需要 starter 代码时,给 `recommend_page` 传 `includeScaffold: true`:\n\n```json\n{\n \"intent\": \"油井管理列表\",\n \"pageType\": \"list\",\n \"features\": [\"筛选\", \"新增\", \"分页\"],\n \"includeScaffold\": true\n}\n```\n\n`recommend_component` 用法:\n\n- 不传 `query` 和 `decision` → 列出全部选型指南\n- 只传 `decision`(如 `overlay-choice`)→ 阅读某一指南\n- 传 `query` → 根据问题推荐组件"
24436
24620
  },
24437
24621
  {
24438
24622
  "title": "对话示例",