one-design-next 0.0.86 → 0.0.87

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.
@@ -20,9 +20,13 @@ function skillMatchesQuery(skill, query) {
20
20
  * 模块级自增 + useState 初始化,保证每个实例 mount 期间 id 稳定。 */
21
21
  var skillSlotIdSeq = 0;
22
22
 
23
+ /** 技能管理入口左侧 icon。备选:layers / blocks / puzzle / settings-2 / book-open */
24
+ var MANAGE_SKILLS_ICON = 'layout-grid';
25
+
23
26
  /**
24
27
  * menu/card 行内描述:仅在被 line-clamp 截断时,hover 0.5s 右侧 Popover 出全文。
25
- * 始终挂载 Popover(用 visible 关掉未截断态),避免条件挂载导致 ref/测量丢帧。
28
+ * menu 1 行、card 最多 2 行。始终挂载 Popover(用 visible 关掉未截断态),
29
+ * 避免条件挂载导致 ref/测量丢帧。
26
30
  */
27
31
  function SkillDescPopover(_ref) {
28
32
  var description = _ref.description,
@@ -141,15 +145,9 @@ export var SkillSlot = /*#__PURE__*/forwardRef(function SkillSlot(_ref2, ref) {
141
145
  }, [visibleGroups, catalogSkills, searchableMenu, searchQuery]);
142
146
  var showGroupTitles = showGroupTitlesProp !== null && showGroupTitlesProp !== void 0 ? showGroupTitlesProp : variant === 'menu';
143
147
  var groupedWithTitles = !!(skillGroups && showGroupTitles);
144
- /* menu 变体:HoverFill 完全只管鼠标交互(命令式状态机,不变);键盘 ↑↓
145
- * 走独立的 CSS [data-odn-skill-slot-item-highlighted] 视觉层。
146
- *
147
- * 两条轨道**视觉上不重叠**:button:hover 时不画键盘高亮 bg(让 HoverFill
148
- * 显示),鼠标离开后键盘高亮才显出。鼠标进入 item 时 onMouseEnter 同步
149
- * activeIndex → highlighted 转移过来,鼠标走后 highlighted 接力。
150
- *
151
- * 颜色用 HoverFill 默认 hoverColor 同值(rgba(33,34,38,0.05)),过渡也用
152
- * 颜色 transition,视觉看起来一致。
148
+ /* menu 变体:HoverFill 只管鼠标。键盘高亮视觉暂时关闭(见 style),
149
+ * 避免 mouseleave 后 ::before 接力造成闪一下。`activeIndex` / ↑↓ / Enter
150
+ * 数据层仍工作,恢复视觉时解开 item-highlighted ::before 即可。
153
151
  *
154
152
  * 键盘导航(仅 menu):
155
153
  * - 受控:业务传 `activeIndex`(如 composer 把 editor 的 ↑↓ 转发进来),
@@ -170,6 +168,11 @@ export var SkillSlot = /*#__PURE__*/forwardRef(function SkillSlot(_ref2, ref) {
170
168
  var isControlled = activeIndex !== undefined && activeIndex !== null;
171
169
  var currentIndex = isControlled ? activeIndex : internalActiveIndex;
172
170
  var itemRefs = useRef([]);
171
+ var listAreaRef = useRef(null);
172
+ var _useState11 = useState(null),
173
+ _useState12 = _slicedToArray(_useState11, 2),
174
+ listAreaMinHeight = _useState12[0],
175
+ setListAreaMinHeight = _useState12[1];
173
176
 
174
177
  /* SkillItem.disabled 为唯一禁用来源(不再与选中态互斥)。 */
175
178
  var isItemDisabled = function isItemDisabled(skill) {
@@ -232,6 +235,20 @@ export var SkillSlot = /*#__PURE__*/forwardRef(function SkillSlot(_ref2, ref) {
232
235
  },
233
236
  // currentIndex / skills 变化时句柄需读到最新值
234
237
  [currentIndex, skills]);
238
+ useLayoutEffect(function () {
239
+ if (!searchableMenu) {
240
+ setListAreaMinHeight(null);
241
+ return;
242
+ }
243
+ if (skills.length === 0) return;
244
+ var el = listAreaRef.current;
245
+ if (!el) return;
246
+ var h = Math.round(el.getBoundingClientRect().height);
247
+ if (h <= 0) return;
248
+ setListAreaMinHeight(function (prev) {
249
+ return prev === h ? prev : h;
250
+ });
251
+ }, [searchableMenu, skills]);
235
252
  var handleMenuKeyDown = function handleMenuKeyDown(e) {
236
253
  if (skills.length === 0) return;
237
254
  switch (e.key) {
@@ -333,23 +350,21 @@ export var SkillSlot = /*#__PURE__*/forwardRef(function SkillSlot(_ref2, ref) {
333
350
  }) : null)));
334
351
  };
335
352
 
336
- /* 渲染底部"技能管理"按钮(仅 menu 变体 + 提供 onManageSkills 时。不参与键盘导航)。 */
337
- var renderManageFooter = function renderManageFooter() {
353
+ /* 「技能管理」按钮:有搜索时进顶栏右侧 hug;无搜索时仍在列表底部。不参与键盘导航。 */
354
+ var renderManageBtn = function renderManageBtn() {
338
355
  if (variant !== 'menu' || !onManageSkills) return null;
339
- var defaultFooter = /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", null, "\u6280\u80FD\u7BA1\u7406"), /*#__PURE__*/React.createElement(Icon, {
340
- name: "right",
341
- size: 12
342
- }));
343
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
344
- "data-odn-skill-slot-divider": true
345
- }), /*#__PURE__*/React.createElement("button", {
356
+ var defaultFooter = /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Icon, {
357
+ name: MANAGE_SKILLS_ICON,
358
+ size: 14
359
+ }), /*#__PURE__*/React.createElement("span", null, "\u6280\u80FD\u7BA1\u7406"));
360
+ return /*#__PURE__*/React.createElement("button", {
346
361
  type: "button",
347
362
  "data-odn-skill-slot-manage-btn": true,
348
363
  onMouseDown: function onMouseDown(e) {
349
364
  return e.preventDefault();
350
365
  },
351
366
  onClick: onManageSkills
352
- }, skillManageFooter !== null && skillManageFooter !== void 0 ? skillManageFooter : defaultFooter));
367
+ }, skillManageFooter !== null && skillManageFooter !== void 0 ? skillManageFooter : defaultFooter);
353
368
  };
354
369
  var renderCardItem = function renderCardItem(skill) {
355
370
  var disabled = !!skill.disabled;
@@ -417,14 +432,14 @@ export var SkillSlot = /*#__PURE__*/forwardRef(function SkillSlot(_ref2, ref) {
417
432
  return /*#__PURE__*/React.createElement(React.Fragment, null, visibleGroups.map(function (group, groupIdx) {
418
433
  return /*#__PURE__*/React.createElement(Fragment, {
419
434
  key: group.groupName || "_group_".concat(groupIdx)
420
- }, groupIdx > 0 ? /*#__PURE__*/React.createElement("div", {
421
- "data-odn-skill-slot-divider": true,
422
- "data-odn-skill-slot-group-divider": true
423
- }) : null, showGroupTitles && group.groupName ? /*#__PURE__*/React.createElement("div", {
435
+ }, showGroupTitles && group.groupName ? /*#__PURE__*/React.createElement("div", {
424
436
  "data-odn-skill-slot-group-header": true
425
437
  }, group.groupName) : null, group.skills.map(function (skill) {
426
438
  return renderMenuItem(skill, flatIndex++);
427
- }));
439
+ }), groupIdx < visibleGroups.length - 1 ? /*#__PURE__*/React.createElement("div", {
440
+ "data-odn-skill-slot-divider": true,
441
+ "data-odn-skill-slot-group-divider": true
442
+ }) : null);
428
443
  }));
429
444
  }
430
445
  return /*#__PURE__*/React.createElement(React.Fragment, null, visibleGroups.map(function (group, groupIdx) {
@@ -453,8 +468,10 @@ export var SkillSlot = /*#__PURE__*/forwardRef(function SkillSlot(_ref2, ref) {
453
468
  /* 有 skillGroups:始终按组遍历(保序);无标题时仍走分组 DOM,仅不渲染 header,
454
469
  * bar/card 用 group-items 承接原有 flex/grid,避免拍平后丢组边界。 */
455
470
  var listBody = visibleGroups ? renderGroups() : renderFlatItems();
456
- var manageFooter = renderManageFooter();
471
+ var manageBtn = renderManageBtn();
457
472
  var searchBar = searchableMenu ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
473
+ "data-odn-skill-slot-toolbar": true
474
+ }, /*#__PURE__*/React.createElement("div", {
458
475
  "data-odn-skill-slot-search": true,
459
476
  onCompositionStart: function onCompositionStart() {
460
477
  composingRef.current = true;
@@ -481,11 +498,20 @@ export var SkillSlot = /*#__PURE__*/forwardRef(function SkillSlot(_ref2, ref) {
481
498
  name: "search",
482
499
  size: 14
483
500
  })
484
- })), /*#__PURE__*/React.createElement("div", {
501
+ })), manageBtn ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
502
+ "data-odn-skill-slot-toolbar-divider": true,
503
+ "aria-hidden": true
504
+ }), manageBtn) : null), /*#__PURE__*/React.createElement("div", {
485
505
  "data-odn-skill-slot-divider": true
486
506
  })) : null;
507
+ var manageFooter = !searchableMenu && manageBtn ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
508
+ "data-odn-skill-slot-divider": true
509
+ }), manageBtn) : null;
487
510
  var emptyState = searchableMenu && skills.length === 0 ? /*#__PURE__*/React.createElement("div", {
488
- "data-odn-skill-slot-empty": true
511
+ "data-odn-skill-slot-empty": true,
512
+ style: listAreaMinHeight ? {
513
+ minHeight: listAreaMinHeight
514
+ } : undefined
489
515
  }, "\u672A\u627E\u5230\u76F8\u5173\u6280\u80FD") : null;
490
516
 
491
517
  /* 共享的属性容器:menu variant 的 aria / keyboard / 交互属性 */
@@ -515,6 +541,7 @@ export var SkillSlot = /*#__PURE__*/forwardRef(function SkillSlot(_ref2, ref) {
515
541
 
516
542
  /* listbox 挂在列表容器上,搜索框 / footer 不进 listbox。 */
517
543
  var menuList = /*#__PURE__*/React.createElement("div", _extends({
544
+ ref: hasScrollWrap ? undefined : listAreaRef,
518
545
  "data-odn-skill-slot-list": true
519
546
  }, listAttrs), skills.length > 0 ? listBody : emptyState);
520
547
  return /*#__PURE__*/React.createElement("div", {
@@ -524,6 +551,7 @@ export var SkillSlot = /*#__PURE__*/forwardRef(function SkillSlot(_ref2, ref) {
524
551
  className: className,
525
552
  style: rootStyle
526
553
  }, searchBar, hasScrollWrap ? /*#__PURE__*/React.createElement("div", {
554
+ ref: listAreaRef,
527
555
  "data-odn-skill-slot-scroll-wrap": true,
528
556
  style: {
529
557
  maxHeight: menuMaxHeight,
@@ -16,7 +16,7 @@
16
16
  }
17
17
 
18
18
  .odn-skill-slot-item-bg {
19
- border-radius: 0;
19
+ border-radius: 4px;
20
20
  }
21
21
 
22
22
  /* ---- menu 滚动包装层 ----
@@ -50,11 +50,28 @@
50
50
  outline: none;
51
51
  }
52
52
 
53
- /* 顶栏滤镜行:与底部「技能管理」同构(无描边、热区不拉通栏)。
53
+ /* 顶栏:搜索 + 可选「技能管理」(hug 靠右)。
54
54
  * 左右 4px 与菜单上下 padding 对齐;选择器压过 Input 默认描边 / 聚焦蓝框。 */
55
- [data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-skill-slot-search] {
55
+ [data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-skill-slot-toolbar] {
56
+ display: flex;
57
+ align-items: center;
56
58
  flex-shrink: 0;
59
+ gap: 4px;
57
60
  margin: 0 4px;
61
+ min-width: 0;
62
+ }
63
+
64
+ [data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-skill-slot-toolbar-divider] {
65
+ flex: 0 0 auto;
66
+ align-self: center;
67
+ width: 1px;
68
+ height: 12px;
69
+ background: var(--odn-color-black-4);
70
+ }
71
+
72
+ [data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-skill-slot-search] {
73
+ flex: 1;
74
+ min-width: 0;
58
75
  }
59
76
 
60
77
  [data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-skill-slot-search] [data-odn-input] {
@@ -63,17 +80,19 @@
63
80
  min-width: 0;
64
81
  box-sizing: border-box;
65
82
  align-items: center;
83
+ /* 与技能项 icon↔名称 同为 8px;关掉 Input small 默认的 4px icon margin,否则会叠成 12px */
66
84
  gap: 8px;
67
- padding: 8px 12px;
85
+ --odn-input-icon-margin-inline-small: 0;
86
+ height: 32px;
87
+ padding: 6px 12px;
68
88
  font-size: 13px;
69
- line-height: 22px;
89
+ line-height: 20px;
70
90
  color: var(--odn-color-black-12);
71
91
  border: none;
72
92
  border-radius: 4px;
73
93
  background: transparent;
74
94
  box-shadow: none;
75
95
  outline: none;
76
- transition: background-color 0.15s;
77
96
  }
78
97
  [data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-skill-slot-search] [data-odn-input][data-odn-input-focused=true] {
79
98
  border: none;
@@ -81,12 +100,18 @@
81
100
  outline: none;
82
101
  background: transparent;
83
102
  }
84
- [data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-skill-slot-search] [data-odn-input]:hover {
85
- background: rgba(33, 34, 38, 0.05);
103
+
104
+ [data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-skill-slot-search] [data-odn-input-el]::placeholder {
105
+ color: var(--odn-color-black-8);
106
+ opacity: 1;
86
107
  }
87
108
 
88
109
  [data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-skill-slot-search] [data-odn-input-left-element] {
89
110
  color: var(--odn-color-black-9);
111
+ gap: 0;
112
+ }
113
+ [data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-skill-slot-search] [data-odn-input-left-element] > svg {
114
+ margin-inline-end: 0;
90
115
  }
91
116
 
92
117
  [data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-search-mark] {
@@ -104,6 +129,10 @@
104
129
  }
105
130
 
106
131
  [data-odn-skill-slot-empty] {
132
+ display: flex;
133
+ align-items: center;
134
+ justify-content: center;
135
+ box-sizing: border-box;
107
136
  padding: 20px 12px;
108
137
  font-size: 13px;
109
138
  line-height: 20px;
@@ -113,6 +142,7 @@
113
142
 
114
143
  [data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-skill-slot-item-wrap] {
115
144
  display: block;
145
+ margin: 0 4px;
116
146
  }
117
147
 
118
148
  [data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-skill-slot-item] {
@@ -133,26 +163,20 @@
133
163
  margin-top: 4px;
134
164
  }
135
165
  [data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-skill-slot-item] {
136
- /* 键盘高亮:独立视觉层,颜色与 HoverFill 默认 hoverColor 完全一致,过渡用
137
- * 颜色 transition 模拟 HoverFill 的渐入感。
138
- *
139
- * 关键互斥规则:button:hover 时不画键盘高亮,让 HoverFill(外层包裹)显示。
140
- * 这样鼠标真 hover 当前项时,HoverFill 的视觉是唯一来源;鼠标离开后键盘
141
- * 高亮 bg 才出现接力。两条轨道始终只有一个出现,不重叠。
166
+ /* 键盘高亮视觉暂时关闭:鼠标离开后不要用 ::before 接力,避免和 HoverFill 退场叠出闪一下。
167
+ * 数据层 `data-odn-skill-slot-item-highlighted` / composer 转发 ↑↓ 仍保留,恢复时解开下面即可。
142
168
  *
143
- * ::before 而不是直接 background:HoverFill 自己的 bg div 是绝对定位在 button
144
- * 之外的,但布局上 button 和 HoverFill 占同位置;button 给个 background 不会
145
- * 跟 HoverFill 视觉打架,但 ::before 写法以后想加扩散动画更灵活。 */
146
- }
147
- [data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-skill-slot-item][data-odn-skill-slot-item-highlighted]:not(:hover)::before {
148
- content: "";
149
- position: absolute;
150
- inset: 0;
151
- background: rgba(33, 34, 38, 0.05);
152
- border-radius: 0;
153
- transition: background-color 0.18s ease;
154
- pointer-events: none;
155
- z-index: -1;
169
+ * &[data-odn-skill-slot-item-highlighted]:not(:hover)::before {
170
+ * content: '';
171
+ * position: absolute;
172
+ * inset: 0;
173
+ * background: rgba(33, 34, 38, 0.05);
174
+ * border-radius: 4px;
175
+ * transition: background-color 0.18s ease;
176
+ * pointer-events: none;
177
+ * z-index: -1;
178
+ * }
179
+ */
156
180
  }
157
181
 
158
182
  [data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-skill-slot-item-label] {
@@ -162,29 +186,32 @@
162
186
 
163
187
  [data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-skill-slot-item-desc] {
164
188
  font-size: 12px;
165
- line-height: 20px;
189
+ line-height: 18px;
166
190
  color: var(--odn-color-black-9);
167
- /* 描述固定最多 2 行,不可配置 */
191
+ /* 描述固定 1 行;超长由 SkillDescPopover 在截断时 hover 出全文 */
168
192
  display: -webkit-box;
169
193
  -webkit-box-orient: vertical;
170
- -webkit-line-clamp: 2;
194
+ -webkit-line-clamp: 1;
171
195
  overflow: hidden;
172
196
  word-break: break-word;
173
197
  }
174
198
 
175
- /* ---- menu 分组标题 ---- */
199
+ /* ---- menu 分组标题 ----
200
+ * 分类名不做 uppercase。组间分割线画在组末、左右内缩,不跟标题叠尾线。 */
176
201
  [data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-skill-slot-group-header] {
177
- padding: 6px 12px 2px;
178
- font-size: 11px;
179
- font-weight: 600;
202
+ padding: 0 16px 4px;
203
+ font-size: 12px;
204
+ font-weight: 500;
180
205
  color: var(--odn-color-black-8);
181
- text-transform: uppercase;
182
- letter-spacing: 0.5px;
183
206
  line-height: 20px;
184
207
  user-select: none;
185
208
  pointer-events: none;
186
209
  }
187
210
 
211
+ [data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-skill-slot-list] > [data-odn-skill-slot-group-header]:first-child {
212
+ padding-top: 4px;
213
+ }
214
+
188
215
  /* 无组标题时:group 壳不参与布局,子项仍挂在根 flex/grid 上(与拍平一致) */
189
216
  [data-odn-skill-slot]:not([data-odn-skill-slot-grouped]) [data-odn-skill-slot-group],
190
217
  [data-odn-skill-slot]:not([data-odn-skill-slot-grouped]) [data-odn-skill-slot-group-items] {
@@ -224,26 +251,34 @@
224
251
  pointer-events: none;
225
252
  }
226
253
 
227
- /* ---- menu 底部分隔线 ---- */
254
+ /* ---- menu 分隔线 ----
255
+ * 通栏:搜索栏 / 技能管理,标「内容 ↔ 系统操作」。
256
+ * 组间:仅无标题时出现,左右内缩对齐文案(macOS 菜单)。 */
228
257
  [data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-skill-slot-divider] {
229
258
  margin: 4px 0;
230
259
  height: 1px;
231
260
  background: var(--odn-color-black-5);
232
261
  }
233
262
 
234
- /* 组间分割线:略收紧上下留白,避免与组标题叠出过大空隙 */
235
263
  [data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-skill-slot-group-divider] {
236
- margin: 4px 0 2px;
264
+ box-sizing: content-box;
265
+ margin: 0 16px;
266
+ padding: 8px 0;
267
+ background-clip: content-box;
237
268
  }
238
269
 
239
- /* ---- menu 底部管理按钮 ---- */
270
+ /* ---- menu 管理按钮:hug 内容宽;在顶栏靠右,无搜索时仍在列表底 ---- */
240
271
  [data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-skill-slot-manage-btn] {
241
- display: flex;
272
+ display: inline-flex;
242
273
  align-items: center;
243
- justify-content: space-between;
274
+ justify-content: flex-start;
275
+ flex: 0 0 auto;
276
+ width: max-content;
244
277
  box-sizing: border-box;
278
+ gap: 4px;
245
279
  margin: 0 4px;
246
- padding: 8px 12px;
280
+ height: 32px;
281
+ padding: 0 12px;
247
282
  border: none;
248
283
  border-radius: 4px;
249
284
  background: transparent;
@@ -251,6 +286,7 @@
251
286
  color: var(--odn-color-black-9);
252
287
  cursor: pointer;
253
288
  font-family: inherit;
289
+ white-space: nowrap;
254
290
  transition: background-color 0.15s;
255
291
  }
256
292
  [data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-skill-slot-manage-btn]:hover {
@@ -260,6 +296,10 @@
260
296
  background: rgba(33, 34, 38, 0.08);
261
297
  }
262
298
 
299
+ [data-odn-skill-slot][data-odn-skill-slot-variant=menu] [data-odn-skill-slot-toolbar] [data-odn-skill-slot-manage-btn] {
300
+ margin: 0;
301
+ }
302
+
263
303
  /* ---- variant: bar ---- */
264
304
  [data-odn-skill-slot][data-odn-skill-slot-variant=bar] {
265
305
  display: flex;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "one-design-next",
3
- "version": "0.0.86",
3
+ "version": "0.0.87",
4
4
  "description": "One Design Next from TAD@tencent.com",
5
5
  "license": "MIT",
6
6
  "type": "module",