@veltra/desktop 1.1.9 → 1.1.10

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.
Files changed (44) hide show
  1. package/dist/components/action/action-group.vue.d.ts +1 -1
  2. package/dist/components/action/action.vue.d.ts +1 -1
  3. package/dist/components/button/style2.css +75 -11
  4. package/dist/components/card/style2.css +4 -6
  5. package/dist/components/cascade/cascade.vue.d.ts +2 -2
  6. package/dist/components/checkbox/checkbox-button.vue.d.ts +1 -1
  7. package/dist/components/collapse/collapse-item.js +8 -23
  8. package/dist/components/collapse/collapse-item.js.map +1 -1
  9. package/dist/components/collapse/collapse-item.vue.d.ts +5 -9
  10. package/dist/components/collapse/collapse.js +22 -44
  11. package/dist/components/collapse/collapse.js.map +1 -1
  12. package/dist/components/collapse/collapse.vue.d.ts +3 -4
  13. package/dist/components/collapse/di.js.map +1 -1
  14. package/dist/components/collapse/style2.css +36 -38
  15. package/dist/components/date-panel/date-panel.vue.d.ts +1 -1
  16. package/dist/components/date-picker/date-picker.vue.d.ts +1 -1
  17. package/dist/components/date-range-picker/date-range-picker.vue.d.ts +1 -1
  18. package/dist/components/drawer/drawer.vue.d.ts +2 -2
  19. package/dist/components/file-viewer/file-viewer.vue.d.ts +3 -3
  20. package/dist/components/multi-select/multi-select.vue.d.ts +1 -1
  21. package/dist/components/multi-tree-select/multi-tree-select.vue.d.ts +1 -1
  22. package/dist/components/number-range-input/number-range-input.vue.d.ts +1 -1
  23. package/dist/components/pop-confirm/pop-confirm.vue.d.ts +1 -1
  24. package/dist/components/progress-nodes/progress-nodes.vue.d.ts +2 -2
  25. package/dist/components/tabs/style.js +1 -0
  26. package/dist/components/tabs/tabs-horizontal.vue.d.ts +2 -2
  27. package/dist/components/tabs/tabs-vertical.vue.d.ts +2 -2
  28. package/dist/components/tabs/tabs.vue.d.ts +2 -2
  29. package/dist/components/tip/tip.vue.d.ts +1 -1
  30. package/dist/components/tree/tree.vue.d.ts +1 -1
  31. package/dist/components/tree-select/tree-select.vue.d.ts +1 -1
  32. package/dist/index.d.ts +3 -3
  33. package/dist/types/collapse.d.ts +5 -28
  34. package/dist/types/index.d.ts +2 -2
  35. package/package.json +11 -11
  36. package/src/components/button/style.scss +26 -7
  37. package/src/components/card/style.scss +4 -18
  38. package/src/components/collapse/__test__/collapse.test.ts +178 -0
  39. package/src/components/collapse/collapse-item.vue +3 -12
  40. package/src/components/collapse/collapse.vue +42 -52
  41. package/src/components/collapse/di.ts +3 -2
  42. package/src/components/collapse/style.scss +32 -80
  43. package/src/components/tabs/style.ts +1 -0
  44. package/src/types/collapse.ts +4 -32
@@ -8,15 +8,14 @@ $root-name: button;
8
8
  @include m.b($root-name) {
9
9
  color: fn.component-var(button, default-color, fn.use-var(text-color, main));
10
10
  box-sizing: border-box;
11
-
11
+ border-radius: 9999px;
12
12
  line-height: 1;
13
13
  outline: none;
14
14
  text-decoration: none;
15
15
  white-space: nowrap;
16
16
  vertical-align: middle;
17
- border-radius: fn.use-var(radius, default);
18
17
  cursor: pointer;
19
- padding: 0 0.8em;
18
+ padding: 0 1em;
20
19
  -webkit-appearance: none;
21
20
  border: 1px solid fn.component-var(button, default-border, transparent);
22
21
  background-color: fn.component-var(button, default-bg, fn.use-var(color, default));
@@ -50,6 +49,7 @@ $root-name: button;
50
49
  @include m.is(plain) {
51
50
  background-color: fn.use-var(bg-color, top);
52
51
  border-color: fn.use-var(color, default, 'dark-1');
52
+ box-shadow: none;
53
53
 
54
54
  &:hover {
55
55
  border-color: fn.use-var(color, default, 'dark-1');
@@ -90,6 +90,7 @@ $root-name: button;
90
90
 
91
91
  @include m.m(color-#{$type}) {
92
92
  background-color: fn.use-var(color, $type);
93
+ border-color: fn.component-var(button, #{$type}-border, fn.use-var(color, $type));
93
94
  color: fn.component-var(button, #{$type}-text-color, fn.use-var(text-color, white));
94
95
 
95
96
  &:hover {
@@ -98,6 +99,11 @@ $root-name: button;
98
99
  #{$type}-hover-bg,
99
100
  fn.use-var(color, $type, dark-1)
100
101
  );
102
+ border-color: fn.component-var(
103
+ button,
104
+ #{$type}-hover-border,
105
+ fn.use-var(color, $type, dark-1)
106
+ );
101
107
  color: fn.component-var(
102
108
  button,
103
109
  #{$type}-hover-color,
@@ -144,8 +150,20 @@ $root-name: button;
144
150
  #{$type}-plain-bg,
145
151
  fn.use-var(color, $type, light-9)
146
152
  );
147
- box-shadow: 0px 0px 2px 1px
148
- fn.component-var(button, #{$type}-plain-shadow, fn.use-var(color, $type, light-5));
153
+ border-color: fn.component-var(
154
+ button,
155
+ #{$type}-plain-border,
156
+ fn.use-var(color, $type, light-5)
157
+ );
158
+ box-shadow: none;
159
+
160
+ @include m.is-not(disabled, loading) {
161
+ &:hover {
162
+ color: fn.use-var(color, $type);
163
+ border-color: fn.use-var(color, $type);
164
+ background-color: fn.use-var(color, $type, light-9);
165
+ }
166
+ }
149
167
 
150
168
  &:active {
151
169
  box-shadow: none;
@@ -153,6 +171,7 @@ $root-name: button;
153
171
 
154
172
  &.is-disabled {
155
173
  color: fn.use-var(color, $type, light-5);
174
+ border-color: fn.use-var(color, $type, light-8);
156
175
  }
157
176
  }
158
177
  }
@@ -179,10 +198,10 @@ $root-name: button;
179
198
  border-radius: 0;
180
199
 
181
200
  &:first-child {
182
- border-radius: fn.use-var(radius, default) 0 0 fn.use-var(radius, default);
201
+ border-radius: 9999px 0 0 9999px;
183
202
  }
184
203
  &:last-child {
185
- border-radius: 0 fn.use-var(radius, default) fn.use-var(radius, default) 0;
204
+ border-radius: 0 9999px 9999px 0;
186
205
  }
187
206
  }
188
207
  }
@@ -14,15 +14,12 @@ $root-name: card;
14
14
 
15
15
  @include m.is(integrate) {
16
16
  box-shadow: none;
17
+ --u-card-header-bg: transparent;
18
+ --u-card-action-bg: transparent;
17
19
  & > {
18
20
  @include m.bem($root-name, header) {
19
- border-bottom: none;
20
21
  padding-bottom: 0;
21
22
  }
22
-
23
- @include m.bem($root-name, action) {
24
- border-top: none;
25
- }
26
23
  }
27
24
  }
28
25
 
@@ -40,7 +37,7 @@ $root-name: card;
40
37
  }
41
38
 
42
39
  @include m.e(header) {
43
- border-bottom: fn.use-var(border);
40
+ background-color: fn.use-var(header-bg, transparent);
44
41
  color: fn.use-var(text-color, title);
45
42
  }
46
43
 
@@ -66,21 +63,10 @@ $root-name: card;
66
63
  }
67
64
 
68
65
  @include m.e(action) {
69
- border-top: fn.use-var(border);
66
+ background-color: fn.use-var(action-bg, transparent);
70
67
 
71
68
  #{fn.bem(button)} + #{fn.bem(button)} {
72
69
  margin-left: fn.use-var(gap, default);
73
70
  }
74
71
  }
75
-
76
- // @include m.is(always-shadow) {
77
- // box-shadow: fn.use-var(shadow);
78
- // }
79
-
80
- // @include m.is(hover-shadow) {
81
- // transition: box-shadow 0.25s ease;
82
- // &:hover {
83
- // box-shadow: fn.use-var(shadow);
84
- // }
85
- // }
86
72
  }
@@ -0,0 +1,178 @@
1
+ // @vitest-environment happy-dom
2
+ import { createApp, h, nextTick, ref } from 'vue'
3
+
4
+ import UCollapseItem from '../collapse-item.vue'
5
+ import UCollapse from '../collapse.vue'
6
+
7
+ function mountCollapse(
8
+ props: Record<string, unknown> = {},
9
+ items: Array<{ value: string; title: string }> = [
10
+ { value: 'a', title: 'Title A' },
11
+ { value: 'b', title: 'Title B' }
12
+ ]
13
+ ) {
14
+ const host = document.createElement('div')
15
+ document.body.appendChild(host)
16
+
17
+ const model = ref(props.modelValue)
18
+ const app = createApp({
19
+ render() {
20
+ return h(
21
+ UCollapse,
22
+ {
23
+ ...props,
24
+ modelValue: model.value,
25
+ 'onUpdate:modelValue': (value: any) => {
26
+ model.value = value
27
+ }
28
+ },
29
+ () =>
30
+ items.map((item) =>
31
+ h(
32
+ UCollapseItem,
33
+ { value: item.value, title: item.title },
34
+ () => `Content ${item.value}`
35
+ )
36
+ )
37
+ )
38
+ }
39
+ })
40
+
41
+ app.mount(host)
42
+
43
+ return {
44
+ host,
45
+ model,
46
+ unmount() {
47
+ app.unmount()
48
+ host.remove()
49
+ }
50
+ }
51
+ }
52
+
53
+ describe('Collapse', () => {
54
+ it('defaultCollapseAll defaults to false, which expands all items when no initial modelValue is provided', async () => {
55
+ const { host, model, unmount } = mountCollapse()
56
+
57
+ try {
58
+ await nextTick()
59
+ // 默认展开全部:modelValue 应该包含所有项的 value
60
+ expect(model.value).toEqual(['a', 'b'])
61
+
62
+ const activeItems = host.querySelectorAll('.u-collapse__item.is-active')
63
+ expect(activeItems.length).toBe(2)
64
+ } finally {
65
+ unmount()
66
+ }
67
+ })
68
+
69
+ it('defaultCollapseAll sets to true collapses all items', async () => {
70
+ const { host, model, unmount } = mountCollapse({ defaultCollapseAll: true })
71
+
72
+ try {
73
+ await nextTick()
74
+ // 全部折叠:modelValue 应该为空或未定义,没有任何项具有 is-active 样式
75
+ expect(model.value).toBeUndefined()
76
+
77
+ const activeItems = host.querySelectorAll('.u-collapse__item.is-active')
78
+ expect(activeItems.length).toBe(0)
79
+ } finally {
80
+ unmount()
81
+ }
82
+ })
83
+
84
+ it('defaultCollapseAll sets to empty string collapses all items', async () => {
85
+ const { host, model, unmount } = mountCollapse({ defaultCollapseAll: '' })
86
+
87
+ try {
88
+ await nextTick()
89
+ expect(model.value).toBeUndefined()
90
+
91
+ const activeItems = host.querySelectorAll('.u-collapse__item.is-active')
92
+ expect(activeItems.length).toBe(0)
93
+ } finally {
94
+ unmount()
95
+ }
96
+ })
97
+
98
+ it('defaultCollapseAll sets to "true" collapses all items', async () => {
99
+ const { host, model, unmount } = mountCollapse({ defaultCollapseAll: 'true' })
100
+
101
+ try {
102
+ await nextTick()
103
+ expect(model.value).toBeUndefined()
104
+
105
+ const activeItems = host.querySelectorAll('.u-collapse__item.is-active')
106
+ expect(activeItems.length).toBe(0)
107
+ } finally {
108
+ unmount()
109
+ }
110
+ })
111
+
112
+ it('respects initial modelValue even if defaultCollapseAll is false', async () => {
113
+ const { host, model, unmount } = mountCollapse({ modelValue: ['a'] })
114
+
115
+ try {
116
+ await nextTick()
117
+ // 外部传入初始值:即使 defaultCollapseAll 为 false,也仅激活外部指定的项
118
+ expect(model.value).toEqual(['a'])
119
+
120
+ const activeItems = host.querySelectorAll('.u-collapse__item.is-active')
121
+ expect(activeItems.length).toBe(1)
122
+ expect(activeItems[0].querySelector('.u-collapse__title')?.textContent).toBe('Title A')
123
+ } finally {
124
+ unmount()
125
+ }
126
+ })
127
+
128
+ it('accordion mode with defaultCollapseAll=false only expands the first item', async () => {
129
+ const { host, model, unmount } = mountCollapse({ accordion: true })
130
+
131
+ try {
132
+ await nextTick()
133
+ // 手风琴模式且没有初始值,默认只展开第一个
134
+ expect(model.value).toBe('a')
135
+
136
+ const activeItems = host.querySelectorAll('.u-collapse__item.is-active')
137
+ expect(activeItems.length).toBe(1)
138
+ expect(activeItems[0].querySelector('.u-collapse__title')?.textContent).toBe('Title A')
139
+ } finally {
140
+ unmount()
141
+ }
142
+ })
143
+
144
+ it('toggles collapse items on header click', async () => {
145
+ const { host, model, unmount } = mountCollapse({ defaultCollapseAll: true })
146
+
147
+ try {
148
+ await nextTick()
149
+ expect(model.value).toBeUndefined()
150
+
151
+ const headers = host.querySelectorAll('.u-collapse__header')
152
+ expect(headers.length).toBe(2)
153
+
154
+ // 点击第一个 header 展开它
155
+ ;(headers[0] as HTMLElement).click()
156
+ await nextTick()
157
+
158
+ expect(model.value).toEqual(['a'])
159
+ expect(host.querySelectorAll('.u-collapse__item.is-active').length).toBe(1)
160
+
161
+ // 点击第二个 header 展开它
162
+ ;(headers[1] as HTMLElement).click()
163
+ await nextTick()
164
+
165
+ expect(model.value).toEqual(['a', 'b'])
166
+ expect(host.querySelectorAll('.u-collapse__item.is-active').length).toBe(2)
167
+
168
+ // 再次点击第一个 header 收起它
169
+ ;(headers[0] as HTMLElement).click()
170
+ await nextTick()
171
+
172
+ expect(model.value).toEqual(['b'])
173
+ expect(host.querySelectorAll('.u-collapse__item.is-active').length).toBe(1)
174
+ } finally {
175
+ unmount()
176
+ }
177
+ })
178
+ })
@@ -10,17 +10,11 @@
10
10
  @keydown.enter.prevent="handleClick"
11
11
  @keydown.space.prevent="handleClick"
12
12
  >
13
- <span v-if="showLeftIcon" :class="cls.e('icon')">
14
- <slot name="icon" :is-active="isActive">
15
- <UIcon><component :is="iconComponent" /></UIcon>
16
- </slot>
17
- </span>
18
-
19
13
  <span :class="cls.e('title')">
20
14
  <slot name="title">{{ title }}</slot>
21
15
  </span>
22
16
 
23
- <span v-if="showRightIcon" :class="cls.e('icon')">
17
+ <span :class="cls.e('icon')">
24
18
  <slot name="icon" :is-active="isActive">
25
19
  <UIcon><component :is="iconComponent" /></UIcon>
26
20
  </slot>
@@ -55,13 +49,8 @@ const cls = context?.cls ?? bem('collapse')
55
49
 
56
50
  const isActive = computed(() => !!context?.activeValues.value.includes(props.value))
57
51
 
58
- const iconPosition = computed(() => context?.iconPosition.value ?? 'right')
59
-
60
52
  const iconComponent = computed(() => context?.expandIcon.value ?? ArrowRight)
61
53
 
62
- const showLeftIcon = computed(() => !props.hideIcon && iconPosition.value === 'left')
63
- const showRightIcon = computed(() => !props.hideIcon && iconPosition.value === 'right')
64
-
65
54
  const classList = computed(() => [
66
55
  cls.e('item'),
67
56
  bem.is('active', isActive.value),
@@ -84,6 +73,7 @@ const wrapperEl = ref<HTMLElement>()
84
73
  const { expandTransition } = context
85
74
 
86
75
  onMounted(() => {
76
+ context?.register(props.value)
87
77
  if (!wrapperEl.value) return
88
78
  expandTransition.setExpanded(wrapperEl.value, isActive.value)
89
79
  })
@@ -94,6 +84,7 @@ watch(isActive, (active) => {
94
84
  })
95
85
 
96
86
  onBeforeUnmount(() => {
87
+ context?.unregister(props.value)
97
88
  if (!wrapperEl.value) return
98
89
  expandTransition.cancel(wrapperEl.value)
99
90
  })
@@ -7,15 +7,9 @@
7
7
  <script lang="ts" setup>
8
8
  import { useFormFallbackProps, useModel } from '@veltra/compositions'
9
9
  import { bem, ExpandTransition } from '@veltra/utils'
10
- import { computed, provide } from 'vue'
11
-
12
- import type {
13
- CollapseEmits,
14
- CollapseModelValue,
15
- CollapseProps,
16
- CollapseValue,
17
- _CollapseExposed
18
- } from '../../types'
10
+ import { computed, provide, ref } from 'vue'
11
+
12
+ import type { CollapseEmits, CollapseModelValue, CollapseProps, CollapseValue } from '../../types'
19
13
  import { CollapseDIKey } from './di'
20
14
 
21
15
  defineOptions({
@@ -24,8 +18,7 @@ defineOptions({
24
18
 
25
19
  const props = withDefaults(defineProps<CollapseProps>(), {
26
20
  accordion: false,
27
- bordered: true,
28
- iconPosition: 'right'
21
+ defaultCollapseAll: false
29
22
  })
30
23
  const emit = defineEmits<CollapseEmits>()
31
24
 
@@ -37,15 +30,9 @@ const expandTransition = new ExpandTransition({
37
30
 
38
31
  const { size } = useFormFallbackProps([props], { size: 'default' })
39
32
 
40
- const iconPosition = computed(() => props.iconPosition ?? 'right')
41
33
  const expandIcon = computed(() => props.expandIcon)
42
34
 
43
- const classList = computed(() => [
44
- cls.b,
45
- cls.m(size.value),
46
- cls.m(`icon-${iconPosition.value}`),
47
- bem.is('bordered', props.bordered)
48
- ])
35
+ const classList = computed(() => [cls.b, cls.m(size.value)])
49
36
 
50
37
  const modelValue = useModel<CollapseProps, 'modelValue'>({
51
38
  props,
@@ -77,51 +64,54 @@ const toggle = (value: CollapseValue) => {
77
64
  update(values)
78
65
  }
79
66
 
80
- const expand = (value: CollapseValue) => {
81
- if (activeValues.value.includes(value)) return
82
- if (props.accordion) {
83
- update(value)
84
- return
67
+ // 收集子组件已注册的 values
68
+ const registeredValues = ref<CollapseValue[]>([])
69
+
70
+ // 初始捕获 modelValue 是否具有有效初值
71
+ const hasInitialValue =
72
+ modelValue.value !== undefined &&
73
+ modelValue.value !== null &&
74
+ (!Array.isArray(modelValue.value) || modelValue.value.length > 0)
75
+
76
+ const register = (value: CollapseValue) => {
77
+ if (!registeredValues.value.includes(value)) {
78
+ registeredValues.value.push(value)
79
+
80
+ // 若默认展开全部(defaultCollapseAll 为 false)且外部未指定初始值,则自动展开该子项
81
+ if (!props.defaultCollapseAll && !hasInitialValue) {
82
+ if (props.accordion) {
83
+ if (registeredValues.value.length === 1) {
84
+ update(value)
85
+ }
86
+ } else {
87
+ const current = Array.isArray(modelValue.value)
88
+ ? modelValue.value
89
+ : modelValue.value !== undefined && modelValue.value !== null
90
+ ? [modelValue.value]
91
+ : []
92
+ if (!current.includes(value)) {
93
+ update([...current, value])
94
+ }
95
+ }
96
+ }
85
97
  }
86
- update([...activeValues.value, value])
87
98
  }
88
99
 
89
- const collapse = (value: CollapseValue) => {
90
- if (!activeValues.value.includes(value)) return
91
- if (props.accordion) {
92
- update([])
93
- return
100
+ const unregister = (value: CollapseValue) => {
101
+ const idx = registeredValues.value.indexOf(value)
102
+ if (idx > -1) {
103
+ registeredValues.value.splice(idx, 1)
94
104
  }
95
- update(activeValues.value.filter((v) => v !== value))
96
- }
97
-
98
- const expandAll = (values: CollapseValue[]) => {
99
- if (props.accordion) {
100
- update(values[0] ?? [])
101
- return
102
- }
103
- update([...values])
104
- }
105
-
106
- const collapseAll = () => {
107
- update([])
108
105
  }
109
106
 
110
107
  provide(CollapseDIKey, {
111
108
  cls,
112
109
  size,
113
- iconPosition,
114
110
  expandIcon,
115
111
  activeValues,
116
112
  toggle,
117
- expandTransition
118
- })
119
-
120
- defineExpose<_CollapseExposed>({
121
- toggle,
122
- expand,
123
- collapse,
124
- expandAll,
125
- collapseAll
113
+ expandTransition,
114
+ register,
115
+ unregister
126
116
  })
127
117
  </script>
@@ -1,16 +1,17 @@
1
1
  import { type BEM, type ComponentSize, ExpandTransition } from '@veltra/utils'
2
2
  import type { Component, ComputedRef, InjectionKey } from 'vue'
3
3
 
4
- import type { CollapseIconPosition, CollapseValue } from '../../types'
4
+ import type { CollapseValue } from '../../types'
5
5
 
6
6
  export interface CollapseContext {
7
7
  cls: BEM<'collapse'>
8
8
  size: ComputedRef<ComponentSize>
9
- iconPosition: ComputedRef<CollapseIconPosition>
10
9
  expandIcon: ComputedRef<Component | undefined>
11
10
  activeValues: ComputedRef<CollapseValue[]>
12
11
  toggle: (value: CollapseValue) => void
13
12
  expandTransition: ExpandTransition
13
+ register: (value: CollapseValue) => void
14
+ unregister: (value: CollapseValue) => void
14
15
  }
15
16
 
16
17
  export const CollapseDIKey: InjectionKey<CollapseContext> = Symbol('Collapse')
@@ -10,54 +10,48 @@ $ease: cubic-bezier(0.4, 0, 0.2, 1);
10
10
  $transition-fast: 0.18s $ease;
11
11
  $transition-normal: 0.24s $ease;
12
12
 
13
- // 各尺寸的水平 / 垂直内边距:决定 header 和 content 的呼吸感
14
- // 节奏在配置项分组场景下既不过紧、也不过松
15
- $header-padding-x: (
16
- small: 12px,
17
- default: 16px,
18
- large: 20px
19
- );
20
- $header-padding-y: (
21
- small: 10px,
22
- default: 14px,
23
- large: 18px
24
- );
25
-
26
13
  @include m.b($root-name) {
27
14
  width: 100%;
28
15
  font-size: fn.use-var(font-size-main, default);
29
16
  color: fn.use-var(text-color, main);
17
+ background-color: transparent;
30
18
 
31
- // ===== Bordered(默认):整体一张卡片,项之间细线分隔 =====
32
- // 面向"配置项分组"场景:清晰边界 + 克制反馈,不抢内部控件视觉
33
- @include m.is(bordered) {
34
- border: 1px solid fn.use-var(border, color);
35
- border-radius: calc(fn.use-var(radius, large) + 2px);
36
- background-color: fn.use-var(bg-color, top);
37
- box-shadow:
38
- 0 1px 2px rgba(15, 23, 42, 0.04),
39
- 0 1px 3px rgba(15, 23, 42, 0.04);
40
- overflow: hidden;
19
+ // ===== Item =====
20
+ @include m.e(item) {
21
+ border-radius: calc(fn.use-var(radius, default) + 2px);
22
+ background-color: fn.use-var(bg-color, hover);
23
+ transition: background-color $transition-fast;
41
24
 
42
- @include m.e(item) {
43
- &:not(:first-child) {
44
- border-top: 1px solid color-mix(in srgb, #{fn.use-var(border, color)} 60%, transparent);
25
+ & + #{fn.bem($root-name, item)} {
26
+ margin-top: fn.use-var(gap, default);
27
+ }
28
+
29
+ @include m.is-not(disabled) {
30
+ &:hover {
31
+ // hover 状态与展开状态保持一致的微加深背景(混入 6% 的正文文字色)
32
+ background-color: color-mix(
33
+ in srgb,
34
+ #{fn.use-var(text-color, main)} 6%,
35
+ #{fn.use-var(bg-color, hover)}
36
+ );
45
37
  }
46
38
  }
47
- }
48
39
 
49
- // ===== Item 通用 =====
50
- @include m.e(item) {
40
+ @include m.is(active) {
41
+ // 展开/活动状态背景(混入 6% 的正文文字色)
42
+ background-color: color-mix(
43
+ in srgb,
44
+ #{fn.use-var(text-color, main)} 6%,
45
+ #{fn.use-var(bg-color, hover)}
46
+ );
47
+ }
48
+
51
49
  @include m.is(disabled) {
52
50
  opacity: 0.55;
53
51
  }
54
52
  }
55
53
 
56
54
  // ===== Header =====
57
- // 反馈策略:
58
- // - hover:仅背景轻微变化("我可点击"信号)
59
- // - active:仅 font-weight 加重 + 箭头旋转("我已展开"信号由内容显现承担)
60
- // - 颜色不切换到 primary,避免在大量配置块同时展开时产生视觉嘈杂
61
55
  @include m.e(header) {
62
56
  position: relative;
63
57
  display: flex;
@@ -72,12 +66,7 @@ $header-padding-y: (
72
66
  transition:
73
67
  color $transition-fast,
74
68
  background-color $transition-fast;
75
-
76
- @include m.is-not(disabled) {
77
- &:hover {
78
- background-color: fn.use-var(bg-color, hover);
79
- }
80
- }
69
+ border-radius: inherit;
81
70
 
82
71
  &:focus-visible {
83
72
  box-shadow: inset 0 0 0 2px fn.use-var(color, primary);
@@ -86,11 +75,6 @@ $header-padding-y: (
86
75
  @include m.is(disabled) {
87
76
  cursor: not-allowed;
88
77
  color: fn.use-var(text-color, placeholder);
89
-
90
- &:hover {
91
- background-color: transparent;
92
- color: fn.use-var(text-color, placeholder);
93
- }
94
78
  }
95
79
 
96
80
  @include m.is(active) {
@@ -124,12 +108,6 @@ $header-padding-y: (
124
108
  }
125
109
 
126
110
  // ===== Content wrapper =====
127
- // 高度由共享 expand transition hook 在 isActive 切换时驱动:
128
- // 未挂载前 → CSS 兜底 height: 0
129
- // 未激活 → 0px(带 transition)
130
- // 激活中 → 0 → scrollHeight(CSS transition: height)
131
- // 已激活 → auto(动画结束后回归,避免锁死内容动态变化)
132
- // 比 grid-template-rows: 0fr ↔ 1fr 减少每帧 grid 布局重算,嵌套场景显著更流畅。
133
111
  @include m.e(content-wrapper) {
134
112
  overflow: hidden;
135
113
  height: 0;
@@ -142,50 +120,24 @@ $header-padding-y: (
142
120
  }
143
121
 
144
122
  // ===== Sizes =====
145
- // padding-bottom 始终生效;inactive 时由 wrapper height: 0 + overflow: hidden
146
- // 自然裁切,scrollHeight 计算因此始终一致,避免动画首帧抖动。
147
123
  @include m.size using ($size) {
148
124
  font-size: fn.use-var(font-size-main, $size);
149
125
 
150
126
  @include m.bem($root-name, header) {
151
- padding: map.get($header-padding-y, $size) map.get($header-padding-x, $size);
127
+ padding: calc(fn.use-var(gap, $size) - 2px) fn.use-var(gap, $size);
152
128
  font-size: fn.use-var(font-size-title, $size);
153
129
  }
154
130
 
155
131
  @include m.bem($root-name, content) {
156
132
  font-size: fn.use-var(font-size-main, $size);
157
- padding: 0 map.get($header-padding-x, $size) map.get($header-padding-y, $size);
133
+ padding: calc(fn.use-var(gap, $size) - 2px) fn.use-var(gap, $size) fn.use-var(gap, $size);
158
134
  }
159
- }
160
-
161
- // ===== Ghost(无边框):项级胶囊化,反馈完全由 item hover 承担 =====
162
- // 嵌入卡片 / 对话框时使用,不引入额外的容器边界
163
- @include m.is-not(bordered) {
164
- background-color: transparent;
165
135
 
166
136
  #{fn.bem($root-name, item)} {
167
- border-radius: calc(fn.use-var(radius, default) + 2px);
168
- transition: background-color $transition-fast;
137
+ border-radius: calc(fn.use-var(radius, $size) + 2px);
169
138
 
170
139
  & + #{fn.bem($root-name, item)} {
171
- margin-top: 4px;
172
- }
173
-
174
- @include m.is-not(disabled) {
175
- &:hover {
176
- background-color: fn.use-var(bg-color, hover);
177
- }
178
- }
179
- }
180
-
181
- // ghost 下 header 自身不再单独 hover,由 item 整体承载
182
- #{fn.bem($root-name, header)} {
183
- border-radius: inherit;
184
-
185
- @include m.is-not(disabled) {
186
- &:hover {
187
- background-color: transparent;
188
- }
140
+ margin-top: fn.use-var(gap, $size);
189
141
  }
190
142
  }
191
143
  }