@morya-ui/mcp 0.4.5 → 0.5.1
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 +481 -326
- package/data/example-coverage.json +56 -48
- package/data/golden-pages/dashboard-page.vue +20 -14
- package/data/golden-pages/detail-page.vue +3 -2
- package/data/golden-pages/empty-state.vue +7 -6
- package/data/golden-pages/form-in-dialog.vue +4 -5
- package/data/golden-pages/form-page.vue +1 -1
- package/data/golden-pages/landing-page.vue +8 -41
- package/data/golden-pages/list-page-dense.vue +12 -14
- package/data/golden-pages/list-page-rail.vue +16 -82
- package/data/golden-pages/list-page.vue +10 -8
- package/data/golden-pages/login-page.vue +10 -29
- package/data/golden-pages/result-page.vue +7 -18
- package/data/golden-pages/settings-page.vue +3 -3
- package/data/golden-pages/wizard-form.vue +2 -2
- package/dist/__tests__/tools.test.js +24 -1
- package/dist/decisions.js +1299 -6
- package/dist/index.js +1 -1
- package/dist/page-snippets.d.ts +4 -0
- package/dist/page-snippets.js +5 -0
- package/dist/tools.js +16 -0
- package/package.json +1 -1
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* 黄金样例:列表页 · rail(侧栏强调)
|
|
4
4
|
* structure: list · style: rail
|
|
5
|
-
*
|
|
5
|
+
* 结构与 list-page.vue 完全相同;差异只在「侧栏 chrome」一处(品牌区 + 侧栏氛围),
|
|
6
|
+
* 内容区(筛选/表格/空态)与 list-page 一致。氛围跟 styleDirection / 参考走。
|
|
6
7
|
* @see list-page.vue · style-presets.md · visual-craft § Atmosphere
|
|
7
8
|
*/
|
|
8
9
|
import {
|
|
@@ -17,22 +18,18 @@ import {
|
|
|
17
18
|
MLayoutSider,
|
|
18
19
|
MMenu,
|
|
19
20
|
MPageContent,
|
|
20
|
-
MPageFilterChips,
|
|
21
21
|
MPageFilters,
|
|
22
22
|
MPageHeader,
|
|
23
23
|
MSelect,
|
|
24
24
|
MSpace,
|
|
25
25
|
MStatus,
|
|
26
26
|
MTable,
|
|
27
|
-
MTag,
|
|
28
27
|
zhCN,
|
|
29
28
|
} from 'morya-ui'
|
|
30
|
-
import {
|
|
29
|
+
import { ref } from 'vue'
|
|
31
30
|
|
|
32
31
|
const keyword = ref('')
|
|
33
32
|
const status = ref<string | undefined>()
|
|
34
|
-
const department = ref<string | null>(null)
|
|
35
|
-
const filtersExpanded = ref(false)
|
|
36
33
|
const siderCollapsed = ref(false)
|
|
37
34
|
|
|
38
35
|
const menuModel = [
|
|
@@ -47,37 +44,9 @@ const statusOptions = [
|
|
|
47
44
|
{ label: '停用', value: 'inactive' },
|
|
48
45
|
]
|
|
49
46
|
|
|
50
|
-
const departmentOptions = [
|
|
51
|
-
{ label: '研发', value: '研发' },
|
|
52
|
-
{ label: '运营', value: '运营' },
|
|
53
|
-
{ label: '市场', value: '市场' },
|
|
54
|
-
]
|
|
55
|
-
|
|
56
|
-
const activeFilters = computed(() => {
|
|
57
|
-
const items: Array<{ key: string; label: string }> = []
|
|
58
|
-
if (keyword.value.trim()) {
|
|
59
|
-
items.push({ key: 'keyword', label: `关键词:${keyword.value.trim()}` })
|
|
60
|
-
}
|
|
61
|
-
if (status.value) {
|
|
62
|
-
const label = statusOptions.find((o) => o.value === status.value)?.label ?? status.value
|
|
63
|
-
items.push({ key: 'status', label: `状态:${label}` })
|
|
64
|
-
}
|
|
65
|
-
if (department.value) {
|
|
66
|
-
items.push({ key: 'department', label: `部门:${department.value}` })
|
|
67
|
-
}
|
|
68
|
-
return items
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
function clearFilter(key: string) {
|
|
72
|
-
if (key === 'keyword') keyword.value = ''
|
|
73
|
-
if (key === 'status') status.value = undefined
|
|
74
|
-
if (key === 'department') department.value = null
|
|
75
|
-
}
|
|
76
|
-
|
|
77
47
|
function resetFilters() {
|
|
78
48
|
keyword.value = ''
|
|
79
49
|
status.value = undefined
|
|
80
|
-
department.value = null
|
|
81
50
|
}
|
|
82
51
|
|
|
83
52
|
const columns = [
|
|
@@ -91,14 +60,13 @@ const rows = [
|
|
|
91
60
|
{ id: '1', name: '示例项目 A', status: 'active', updatedAt: '2026-09-01' },
|
|
92
61
|
{ id: '2', name: '示例项目 B', status: 'inactive', updatedAt: '2026-08-28' },
|
|
93
62
|
{ id: '3', name: '示例项目 C', status: 'active', updatedAt: '2026-08-25' },
|
|
94
|
-
{ id: '4', name: '示例项目 D', status: 'active', updatedAt: '2026-08-20' },
|
|
95
|
-
{ id: '5', name: '示例项目 E', status: 'inactive', updatedAt: '2026-08-15' },
|
|
96
63
|
]
|
|
97
64
|
</script>
|
|
98
65
|
|
|
99
66
|
<template>
|
|
100
67
|
<MConfigProvider :locale="zhCN">
|
|
101
68
|
<MLayout has-sider fill-viewport>
|
|
69
|
+
<!-- rail 关键:侧栏承载品牌区 + 侧栏氛围;内容区保持克制 -->
|
|
102
70
|
<MLayoutSider
|
|
103
71
|
v-model:collapsed="siderCollapsed"
|
|
104
72
|
bordered
|
|
@@ -107,7 +75,7 @@ const rows = [
|
|
|
107
75
|
>
|
|
108
76
|
<div class="gp-rail-sider__brand">
|
|
109
77
|
<span class="gp-rail-sider__mark" aria-hidden="true" />
|
|
110
|
-
<span v-if="!siderCollapsed" class="gp-rail-sider__name">
|
|
78
|
+
<span v-if="!siderCollapsed" class="gp-rail-sider__name">{{品牌名}}</span>
|
|
111
79
|
</div>
|
|
112
80
|
<MMenu
|
|
113
81
|
:model="menuModel"
|
|
@@ -122,22 +90,18 @@ const rows = [
|
|
|
122
90
|
<MBreadcrumb :model="[{ label: '首页', to: '/' }, { label: '用户管理' }]" />
|
|
123
91
|
</MLayoutHeader>
|
|
124
92
|
|
|
93
|
+
<!-- 内容区与 list-page 一致;视觉(variant/内联宽度)跟 styleDirection -->
|
|
125
94
|
<MLayoutContent>
|
|
126
95
|
<MPageContent fill>
|
|
127
|
-
<MPageHeader title="
|
|
96
|
+
<MPageHeader title="{{页面标题}}" description="{{页面说明}}">
|
|
128
97
|
<template #actions>
|
|
129
98
|
<MButton type="primary">
|
|
130
|
-
|
|
99
|
+
{{主操作}}
|
|
131
100
|
</MButton>
|
|
132
101
|
</template>
|
|
133
102
|
</MPageHeader>
|
|
134
103
|
|
|
135
|
-
<MPageFilters
|
|
136
|
-
v-model:expanded="filtersExpanded"
|
|
137
|
-
aria-label="筛选"
|
|
138
|
-
variant="filled"
|
|
139
|
-
collapsible
|
|
140
|
-
>
|
|
104
|
+
<MPageFilters aria-label="筛选" collapsible>
|
|
141
105
|
<MSpace wrap>
|
|
142
106
|
<MInput v-model="keyword" placeholder="搜索名称" clearable style="width: 14rem" />
|
|
143
107
|
<MSelect
|
|
@@ -156,41 +120,16 @@ const rows = [
|
|
|
156
120
|
重置
|
|
157
121
|
</MButton>
|
|
158
122
|
</template>
|
|
159
|
-
<template #advanced>
|
|
160
|
-
<MSpace wrap>
|
|
161
|
-
<MSelect
|
|
162
|
-
v-model="department"
|
|
163
|
-
:options="departmentOptions"
|
|
164
|
-
placeholder="部门"
|
|
165
|
-
clearable
|
|
166
|
-
style="width: 10rem"
|
|
167
|
-
/>
|
|
168
|
-
</MSpace>
|
|
169
|
-
</template>
|
|
170
123
|
</MPageFilters>
|
|
171
124
|
|
|
172
|
-
<MPageFilterChips v-if="activeFilters.length" label="已选" aria-label="已选筛选">
|
|
173
|
-
<MTag
|
|
174
|
-
v-for="item in activeFilters"
|
|
175
|
-
:key="item.key"
|
|
176
|
-
:value="item.label"
|
|
177
|
-
size="small"
|
|
178
|
-
bordered
|
|
179
|
-
closable
|
|
180
|
-
@close="clearFilter(item.key)"
|
|
181
|
-
/>
|
|
182
|
-
</MPageFilterChips>
|
|
183
|
-
|
|
184
125
|
<MTable
|
|
185
126
|
:columns="columns"
|
|
186
127
|
:rows="rows"
|
|
187
128
|
:rows-per-page="5"
|
|
188
129
|
fill
|
|
189
130
|
paginator
|
|
190
|
-
striped
|
|
191
|
-
bordered
|
|
192
131
|
row-key="id"
|
|
193
|
-
aria-label="
|
|
132
|
+
aria-label="{{列表}}"
|
|
194
133
|
>
|
|
195
134
|
<template #cell-status="{ value }">
|
|
196
135
|
<MStatus
|
|
@@ -210,13 +149,13 @@ const rows = [
|
|
|
210
149
|
</template>
|
|
211
150
|
<template #empty>
|
|
212
151
|
<MEmpty
|
|
213
|
-
title="
|
|
214
|
-
description="
|
|
152
|
+
title="{{空态标题}}"
|
|
153
|
+
description="{{空态说明}}"
|
|
215
154
|
icon="user"
|
|
216
155
|
>
|
|
217
156
|
<template #extra>
|
|
218
157
|
<MButton type="primary">
|
|
219
|
-
|
|
158
|
+
{{空态主操作}}
|
|
220
159
|
</MButton>
|
|
221
160
|
</template>
|
|
222
161
|
</MEmpty>
|
|
@@ -230,13 +169,9 @@ const rows = [
|
|
|
230
169
|
</template>
|
|
231
170
|
|
|
232
171
|
<style scoped>
|
|
172
|
+
/* rail 关键:侧栏氛围(token-only,不默认渐变/发光) */
|
|
233
173
|
.gp-rail-sider {
|
|
234
|
-
background:
|
|
235
|
-
linear-gradient(
|
|
236
|
-
180deg,
|
|
237
|
-
color-mix(in srgb, var(--m-color-primary) 14%, var(--m-color-surface)) 0%,
|
|
238
|
-
var(--m-color-surface) 42%
|
|
239
|
-
);
|
|
174
|
+
background: var(--m-color-surface);
|
|
240
175
|
}
|
|
241
176
|
|
|
242
177
|
.gp-rail-sider__brand {
|
|
@@ -267,6 +202,5 @@ const rows = [
|
|
|
267
202
|
|
|
268
203
|
.gp-rail-header {
|
|
269
204
|
border-bottom: 1px solid var(--m-color-border);
|
|
270
|
-
box-shadow: inset 0 -1px 0 color-mix(in srgb, var(--m-color-primary) 18%, transparent);
|
|
271
205
|
}
|
|
272
|
-
</style>
|
|
206
|
+
</style>
|
|
@@ -116,7 +116,7 @@ const rows = [
|
|
|
116
116
|
|
|
117
117
|
<MLayoutContent>
|
|
118
118
|
<MPageContent fill>
|
|
119
|
-
<MPageHeader title="
|
|
119
|
+
<MPageHeader title="{{页面标题}}" description="{{页面说明}}">
|
|
120
120
|
<template #actions>
|
|
121
121
|
<MButton type="primary">
|
|
122
122
|
新建用户
|
|
@@ -124,13 +124,14 @@ const rows = [
|
|
|
124
124
|
</template>
|
|
125
125
|
</MPageHeader>
|
|
126
126
|
|
|
127
|
+
<!-- 筛选区:四选一(list-filters / collapsible / stack / dense)。variant 跟密度,不默认 filled。 -->
|
|
127
128
|
<MPageFilters
|
|
128
129
|
v-model:expanded="filtersExpanded"
|
|
129
130
|
aria-label="筛选"
|
|
130
|
-
variant="filled"
|
|
131
131
|
collapsible
|
|
132
132
|
>
|
|
133
133
|
<MSpace wrap>
|
|
134
|
+
<!-- 控件宽度可局部 inline;其余视觉跟 styleDirection -->
|
|
134
135
|
<MInput v-model="keyword" placeholder="搜索名称" clearable style="width: 14rem" />
|
|
135
136
|
<MSelect
|
|
136
137
|
v-model="status"
|
|
@@ -161,6 +162,7 @@ const rows = [
|
|
|
161
162
|
</template>
|
|
162
163
|
</MPageFilters>
|
|
163
164
|
|
|
165
|
+
<!-- 用折叠筛选栈时通常不再叠加独立 chips;以下为演示已选摘要的独立用法 -->
|
|
164
166
|
<MPageFilterChips v-if="activeFilters.length" label="已选" aria-label="已选筛选">
|
|
165
167
|
<MTag
|
|
166
168
|
v-for="item in activeFilters"
|
|
@@ -173,14 +175,13 @@ const rows = [
|
|
|
173
175
|
/>
|
|
174
176
|
</MPageFilterChips>
|
|
175
177
|
|
|
178
|
+
<!-- 全视口主列表:fill + paginator 成对;striped/bordered 跟密度,非默认 -->
|
|
176
179
|
<MTable
|
|
177
180
|
:columns="columns"
|
|
178
181
|
:rows="rows"
|
|
179
182
|
:rows-per-page="5"
|
|
180
183
|
fill
|
|
181
184
|
paginator
|
|
182
|
-
striped
|
|
183
|
-
bordered
|
|
184
185
|
row-key="id"
|
|
185
186
|
aria-label="用户列表"
|
|
186
187
|
>
|
|
@@ -201,14 +202,15 @@ const rows = [
|
|
|
201
202
|
</MSpace>
|
|
202
203
|
</template>
|
|
203
204
|
<template #empty>
|
|
205
|
+
<!-- 空态说明“接下来做什么” + 唯一主操作 -->
|
|
204
206
|
<MEmpty
|
|
205
|
-
title="
|
|
206
|
-
description="
|
|
207
|
+
title="{{空态标题}}"
|
|
208
|
+
description="{{空态说明}}"
|
|
207
209
|
icon="user"
|
|
208
210
|
>
|
|
209
211
|
<template #extra>
|
|
210
212
|
<MButton type="primary">
|
|
211
|
-
|
|
213
|
+
{{空态主操作}}
|
|
212
214
|
</MButton>
|
|
213
215
|
</template>
|
|
214
216
|
</MEmpty>
|
|
@@ -219,4 +221,4 @@ const rows = [
|
|
|
219
221
|
</MLayout>
|
|
220
222
|
</MLayout>
|
|
221
223
|
</MConfigProvider>
|
|
222
|
-
</template>
|
|
224
|
+
</template>
|
|
@@ -25,9 +25,9 @@ const model = reactive({
|
|
|
25
25
|
password: '',
|
|
26
26
|
})
|
|
27
27
|
|
|
28
|
-
/** Brand seed for Account surfaces — prefer themeConfig over page-level hex. */
|
|
28
|
+
/** Brand seed for Account surfaces — prefer themeConfig over page-level hex. 品牌色改主题覆盖,不写页面 hex。 */
|
|
29
29
|
const themeConfig = {
|
|
30
|
-
seed: { colorPrimary: '#0b6e4f' },
|
|
30
|
+
seed: { colorPrimary: '#0b6e4f' }, // 演示:实际项目用 --m-color-primary 主题覆盖
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
async function onSubmit() {
|
|
@@ -51,12 +51,13 @@ async function onSubmit() {
|
|
|
51
51
|
input-variant="filled"
|
|
52
52
|
:theme-config="themeConfig"
|
|
53
53
|
>
|
|
54
|
+
<!-- 品牌区渐变/氛围跟 styleDirection;结构:品牌区 + 表单面板 -->
|
|
54
55
|
<div class="login-shell">
|
|
55
56
|
<aside class="login-brand" aria-label="品牌">
|
|
56
|
-
<p class="login-brand__mark"
|
|
57
|
-
<h1 class="login-brand__title"
|
|
57
|
+
<p class="login-brand__mark">{{品牌名}}</p>
|
|
58
|
+
<h1 class="login-brand__title">{{品牌主张}}</h1>
|
|
58
59
|
<p class="login-brand__lead">
|
|
59
|
-
|
|
60
|
+
{{品牌导语}}
|
|
60
61
|
</p>
|
|
61
62
|
</aside>
|
|
62
63
|
|
|
@@ -64,10 +65,10 @@ async function onSubmit() {
|
|
|
64
65
|
<div class="login-panel">
|
|
65
66
|
<header class="login-panel__header">
|
|
66
67
|
<MTitle :level="3">
|
|
67
|
-
|
|
68
|
+
{{登录标题}}
|
|
68
69
|
</MTitle>
|
|
69
70
|
<MParagraph type="secondary">
|
|
70
|
-
|
|
71
|
+
{{登录说明}}
|
|
71
72
|
</MParagraph>
|
|
72
73
|
</header>
|
|
73
74
|
|
|
@@ -123,31 +124,11 @@ async function onSubmit() {
|
|
|
123
124
|
gap: var(--m-space-4);
|
|
124
125
|
padding: clamp(2rem, 6vw, 4.5rem);
|
|
125
126
|
overflow: hidden;
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
80% 60% at 10% 20%,
|
|
129
|
-
color-mix(in srgb, var(--m-color-primary) 22%, transparent),
|
|
130
|
-
transparent 55%
|
|
131
|
-
),
|
|
132
|
-
linear-gradient(
|
|
133
|
-
165deg,
|
|
134
|
-
color-mix(in srgb, var(--m-color-primary) 16%, var(--m-color-surface)) 0%,
|
|
135
|
-
var(--m-color-surface) 55%,
|
|
136
|
-
color-mix(in srgb, var(--m-color-border) 35%, var(--m-color-surface)) 100%
|
|
137
|
-
);
|
|
127
|
+
/* 品牌氛围(渐变/插画)跟 styleDirection;默认 token-only 平壳 */
|
|
128
|
+
background: var(--m-color-surface);
|
|
138
129
|
border-right: 1px solid var(--m-color-border);
|
|
139
130
|
}
|
|
140
131
|
|
|
141
|
-
.login-brand::after {
|
|
142
|
-
content: '';
|
|
143
|
-
position: absolute;
|
|
144
|
-
inset: auto -10% -20% 40%;
|
|
145
|
-
height: 55%;
|
|
146
|
-
border-radius: 50%;
|
|
147
|
-
background: color-mix(in srgb, var(--m-color-primary) 12%, transparent);
|
|
148
|
-
pointer-events: none;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
132
|
.login-brand__mark,
|
|
152
133
|
.login-brand__title,
|
|
153
134
|
.login-brand__lead {
|
|
@@ -34,20 +34,21 @@ import {
|
|
|
34
34
|
|
|
35
35
|
<MLayoutContent>
|
|
36
36
|
<MPageContent>
|
|
37
|
+
<!-- 结果壳:视觉(渐变/虚线边框)跟 styleDirection;此处保留 token-only 极简壳 -->
|
|
37
38
|
<div class="result-shell">
|
|
38
39
|
<MResult
|
|
39
40
|
status="403"
|
|
40
|
-
title="
|
|
41
|
-
description="
|
|
41
|
+
title="{{结果标题}}"
|
|
42
|
+
description="{{结果说明}}"
|
|
42
43
|
size="large"
|
|
43
44
|
>
|
|
44
45
|
<template #footer>
|
|
45
46
|
<MSpace>
|
|
46
47
|
<MButton type="primary">
|
|
47
|
-
|
|
48
|
+
{{主操作}}
|
|
48
49
|
</MButton>
|
|
49
50
|
<MButton type="text">
|
|
50
|
-
|
|
51
|
+
{{次操作}}
|
|
51
52
|
</MButton>
|
|
52
53
|
</MSpace>
|
|
53
54
|
</template>
|
|
@@ -66,19 +67,7 @@ import {
|
|
|
66
67
|
min-height: min(28rem, 70vh);
|
|
67
68
|
padding: var(--m-space-8) var(--m-space-4);
|
|
68
69
|
border-radius: var(--m-radius-lg);
|
|
69
|
-
background:
|
|
70
|
-
|
|
71
|
-
70% 55% at 50% 0%,
|
|
72
|
-
color-mix(in srgb, var(--m-color-primary) 10%, transparent),
|
|
73
|
-
transparent 60%
|
|
74
|
-
),
|
|
75
|
-
var(--m-color-surface);
|
|
76
|
-
border: 1px dashed color-mix(in srgb, var(--m-color-border) 80%, transparent);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
@media (prefers-reduced-motion: reduce) {
|
|
80
|
-
.result-shell {
|
|
81
|
-
background: var(--m-color-surface);
|
|
82
|
-
}
|
|
70
|
+
background: var(--m-color-surface);
|
|
71
|
+
border: 1px solid var(--m-color-border);
|
|
83
72
|
}
|
|
84
73
|
</style>
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
MBreadcrumb,
|
|
9
9
|
MButton,
|
|
10
10
|
MConfigProvider,
|
|
11
|
+
message,
|
|
11
12
|
MForm,
|
|
12
13
|
MFormItem,
|
|
13
14
|
MInput,
|
|
@@ -21,7 +22,6 @@ import {
|
|
|
21
22
|
MSpace,
|
|
22
23
|
MSwitch,
|
|
23
24
|
MTabs,
|
|
24
|
-
message,
|
|
25
25
|
zhCN,
|
|
26
26
|
} from 'morya-ui'
|
|
27
27
|
import { reactive, ref } from 'vue'
|
|
@@ -82,8 +82,8 @@ async function saveNotify() {
|
|
|
82
82
|
<MLayoutContent>
|
|
83
83
|
<MPageContent width="narrow">
|
|
84
84
|
<MPageHeader
|
|
85
|
-
title="
|
|
86
|
-
description="
|
|
85
|
+
title="{{设置页标题}}"
|
|
86
|
+
description="{{设置页说明}}"
|
|
87
87
|
/>
|
|
88
88
|
|
|
89
89
|
<MTabs v-model="activeTab" :tabs="tabs">
|
|
@@ -89,8 +89,8 @@ async function finish() {
|
|
|
89
89
|
<MLayoutContent>
|
|
90
90
|
<MPageContent width="narrow">
|
|
91
91
|
<MPageHeader
|
|
92
|
-
title="
|
|
93
|
-
description="
|
|
92
|
+
title="{{向导标题}}"
|
|
93
|
+
description="{{向导说明}}"
|
|
94
94
|
/>
|
|
95
95
|
|
|
96
96
|
<MStepper v-model="activeStep" :steps="steps" linear />
|
|
@@ -251,7 +251,9 @@ describe('@morya-ui/mcp handlers', () => {
|
|
|
251
251
|
const rail = read(handlers.getGoldenPage({ page: 'list-page-rail' }));
|
|
252
252
|
expect(rail.id).toBe('list-page-rail');
|
|
253
253
|
expect(rail.style).toBe('rail');
|
|
254
|
-
|
|
254
|
+
// rail 变体差异集中在侧栏品牌 chrome;token-only,不再默认渐变
|
|
255
|
+
expect(rail.source).toContain('gp-rail-sider');
|
|
256
|
+
expect(rail.source).toContain('--m-color-primary');
|
|
255
257
|
});
|
|
256
258
|
it('exposes style direction rules without a preset catalog', () => {
|
|
257
259
|
const result = read(handlers.getStyleDirection({ mode: 'zh-CN' }));
|
|
@@ -343,6 +345,27 @@ describe('@morya-ui/mcp handlers', () => {
|
|
|
343
345
|
expect(result.ok).toBe(true);
|
|
344
346
|
expect(result.suggestions.some((item) => item.type === 'status-cell-tag')).toBe(true);
|
|
345
347
|
});
|
|
348
|
+
it('suggests a single filter region when multiple MPageFilters appear', () => {
|
|
349
|
+
const result = read(handlers.validatePage({
|
|
350
|
+
code: '<MPageFilters /><MPageFilters collapsible />',
|
|
351
|
+
}));
|
|
352
|
+
expect(result.ok).toBe(true);
|
|
353
|
+
expect(result.craft.some((item) => item.type === 'multiple-filter-regions' && item.severity === 'craft')).toBe(true);
|
|
354
|
+
});
|
|
355
|
+
it('suggests dropping standalone chips when the collapsible stack already renders them', () => {
|
|
356
|
+
const result = read(handlers.validatePage({
|
|
357
|
+
code: '<MPageFilters collapsible><MInput /></MPageFilters><MPageFilterChips />',
|
|
358
|
+
}));
|
|
359
|
+
expect(result.ok).toBe(true);
|
|
360
|
+
expect(result.craft.some((item) => item.type === 'redundant-filter-chips')).toBe(true);
|
|
361
|
+
});
|
|
362
|
+
it('suggests consistent density when compact and spacious are mixed', () => {
|
|
363
|
+
const result = read(handlers.validatePage({
|
|
364
|
+
code: '<MTable density="compact" /><MCard density="spacious" />',
|
|
365
|
+
}));
|
|
366
|
+
expect(result.ok).toBe(true);
|
|
367
|
+
expect(result.craft.some((item) => item.type === 'mixed-density')).toBe(true);
|
|
368
|
+
});
|
|
346
369
|
it('returns detail-page and form-in-dialog golden samples', () => {
|
|
347
370
|
const detail = read(handlers.getGoldenPage({ page: 'detail-page' }));
|
|
348
371
|
expect(detail.id).toBe('detail-page');
|