@keyfunc/keybricks-ds 2.4.2 → 2.4.6

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 (46) hide show
  1. package/DEPLOY.md +25 -3
  2. package/DESIGN.md +18 -6
  3. package/INTEGRATION.md +16 -6
  4. package/_adherence.oxlintrc.json +7 -0
  5. package/_scripts/build-index.mjs +2 -2
  6. package/assets/icons/icon-index.js +1 -1
  7. package/assets/icons/icon-index.json +1 -1
  8. package/components/_cards/activity.card.html +1 -1
  9. package/components/_cards/button-1-base.card.html +1 -1
  10. package/components/_cards/button-3-sizes-icons.card.html +1 -1
  11. package/components/_cards/icons-0-usage.card.html +1 -1
  12. package/components/_cards/icons-1-core.card.html +2 -2
  13. package/components/_cards/icons-2-feather.card.html +1 -1
  14. package/components/_cards/icons-2b-lucide.card.html +44 -0
  15. package/components/_cards/listrow.card.html +2 -2
  16. package/components/_cards/smart-table.card.html +23 -13
  17. package/components/_cards/table.card.html +10 -10
  18. package/components/_cards/tree.card.html +49 -3
  19. package/components/_shared/src-data.js +1 -1
  20. package/components/_shared/src-icons.js +1 -1
  21. package/components/_shared/src-navigation.js +1 -1
  22. package/components/_vue/index.mjs +2 -0
  23. package/components/buttons/buttons.prompt.md +1 -1
  24. package/components/data/SmartTable.d.ts +3 -2
  25. package/components/data/SmartTable.jsx +11 -6
  26. package/components/data/SmartTable.vue +10 -5
  27. package/components/icons/Icon.jsx +81 -5
  28. package/components/icons/Icon.prompt.md +1 -1
  29. package/components/icons/Lucide.d.ts +22 -0
  30. package/components/icons/Lucide.jsx +18 -0
  31. package/components/icons/Lucide.vue +12 -0
  32. package/components/icons/icon-paths.js +79 -3
  33. package/components/icons/lucide-loader.js +27 -0
  34. package/components/index.d.ts +1 -0
  35. package/components/index.js +1 -0
  36. package/components/navigation/Breadcrumb.jsx +1 -1
  37. package/components/navigation/Sidebar.d.ts +1 -1
  38. package/components/navigation/ThemeToggle.vue +1 -1
  39. package/components/navigation/Tree.d.ts +1 -1
  40. package/components/navigation/Tree.jsx +21 -5
  41. package/components/navigation/Tree.vue +16 -4
  42. package/components/navigation/nav-model.js +3 -3
  43. package/package.json +1 -1
  44. package/readme.md +1 -1
  45. package/readme.zh-CN.md +1 -1
  46. package/tokens/tokens.json +1 -1
@@ -67,7 +67,7 @@ export interface TreeProps {
67
67
  /** Draw connector lines instead of plain indentation */
68
68
  showLine?: boolean;
69
69
  draggable?: boolean;
70
- /** Also allow insert-between: a folder's outer 20% becomes a before/after rule. Off by default — a drag moves a node INTO a folder, and files are never drop targets. */
70
+ /** Also allow insert-between: a folder's outer 20% becomes a before/after rule, and a leaf becomes an insert target over its whole height (top half before, bottom half after) — so leaves can be reordered among themselves. Before / after draw an insert marker (2px line + 6px dot) on the gap between rows, starting at the target's indent. Off by default — a drag moves a node INTO a folder, and files are never drop targets. */
71
71
  reorder?: boolean;
72
72
  /** Highlights matches and auto-expands their ancestors */
73
73
  searchValue?: string;
@@ -4,6 +4,19 @@ import { Checkbox } from "../forms/Checkbox.jsx";
4
4
 
5
5
  const docHues = { orange: "var(--cat-transform)", green: "var(--cat-source)", purple: "var(--cat-merge)", blue: "var(--info-fg)", teal: "var(--cat-output)", gray: "var(--text-muted)" };
6
6
  const INDENT = 18;
7
+ /* Insert marker (reorder before / after): a 2px --action-primary line with a 6px dot at its left end, drawn between rows.
8
+ It starts at the target row's content indent (8 + depth * INDENT — the chevron column, which is also the elbow column
9
+ under showLine) and ends at the row's 8px right padding, so the line says which level the item lands on. Its centre
10
+ sits on the gap between rows (gap 1px, or 0 under showLine). */
11
+ function insertMarker(pos, depth, gap) {
12
+ const centre = -(gap / 2);
13
+ return {
14
+ position: "absolute", left: 8 + depth * INDENT - 3, right: 8, height: 6, pointerEvents: "none", zIndex: 1,
15
+ ...(pos === "before" ? { top: centre - 3 } : { bottom: centre - 3 }),
16
+ };
17
+ }
18
+ const markerDot = { position: "absolute", left: 0, top: 0, width: 6, height: 6, borderRadius: "50%", background: "var(--action-primary)" };
19
+ const markerLine = { position: "absolute", left: 3, right: 0, top: 2, height: 2, borderRadius: 1, background: "var(--action-primary)" };
7
20
 
8
21
  const keyOf = (n) => (n.key != null ? n.key : n.label);
9
22
  const titleOf = (n) => (n.title != null ? n.title : n.label);
@@ -141,14 +154,14 @@ function Row({ item, state, cfg, api }) {
141
154
  onKeyDown={(e) => api.keyDown(e, item)}
142
155
  onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
143
156
  style={{
144
- minHeight: "var(--h-tree-item)", borderRadius: "var(--radius-md)", display: "flex", alignItems: "center", gap: 6,
157
+ position: "relative", minHeight: "var(--h-tree-item)", borderRadius: "var(--radius-md)", display: "flex", alignItems: "center", gap: 6,
145
158
  padding: cfg.showLine ? "0 8px 0 2px" : "5px 8px", paddingLeft: cfg.showLine ? 2 : 8 + depth * INDENT, boxSizing: "border-box",
146
159
  cursor: disabled ? "not-allowed" : "pointer", opacity: disabled ? 0.45 : 1,
147
160
  background: dropAt === "inside" ? "var(--soft-bg)" : selected ? "var(--bg-active)" : hover && !disabled ? "var(--bg-hover)" : "transparent",
148
161
  outline: dropAt === "inside" ? "2px solid var(--action-primary)" : ringed ? "2px solid var(--focus-border)" : "none", outlineOffset: -2,
149
- boxShadow: dropAt === "before" ? "inset 0 2px 0 var(--action-primary)" : dropAt === "after" ? "inset 0 -2px 0 var(--action-primary)" : "none",
150
162
  transition: "var(--t-hover)",
151
163
  }}>
164
+ {dropAt === "before" || dropAt === "after" ? <span aria-hidden="true" data-drop-marker={dropAt} style={insertMarker(dropAt, depth, cfg.showLine ? 0 : 1)}><span style={markerDot} /><span style={markerLine} /></span> : null}
152
165
  {cfg.showLine ? <Guides trail={trail} isLast={isLast} hasKids={branch} /> : null}
153
166
  {chevron}
154
167
  {cfg.checkable && node.checkable !== false ? (
@@ -279,9 +292,11 @@ export function Tree({
279
292
  dragOver: (k, ratio) => {
280
293
  if (k == null) return setDrop(null);
281
294
  const node = indexNodes(data).byKey[k];
282
- /* Only folders are drop targets: a file can't contain anything, so hovering one shows
283
- nothing at all. With reorder, a folder's outer 20% becomes an insert rule instead. */
284
- if (!node || node.disabled || !dragKey || !isBranch(node) || inSubtree(data, dragKey, k)) return setDrop(null);
295
+ if (!node || node.disabled || !dragKey || inSubtree(data, dragKey, k)) return setDrop(null);
296
+ /* Without reorder only folders are drop targets: a file can't contain anything, so hovering
297
+ one shows nothing at all. With reorder, a folder's outer 20% becomes an insert rule, and a
298
+ file is an insert target over its whole height — top half before, bottom half after. */
299
+ if (!isBranch(node)) return reorder ? setDrop({ key: k, pos: ratio < 0.5 ? "before" : "after" }) : setDrop(null);
285
300
  const pos = reorder ? (ratio < 0.2 ? "before" : ratio > 0.8 ? "after" : "inside") : "inside";
286
301
  setDrop({ key: k, pos });
287
302
  },
@@ -290,6 +305,7 @@ export function Tree({
290
305
  const info = drop && drop.key === k ? drop : null;
291
306
  setDrop(null);
292
307
  if (!dragKey || !info || inSubtree(data, dragKey, k)) return setDragKey(null);
308
+ if (info.pos === "inside" && !isBranch(indexNodes(data).byKey[k])) return setDragKey(null);
293
309
  /* Computed outside setData: React re-invokes updaters (StrictMode, concurrent
294
310
  rebasing), which would fire the consumer's onDrop twice for one gesture. */
295
311
  const clone = JSON.parse(JSON.stringify(data));
@@ -7,6 +7,15 @@ import Checkbox from '../forms/Checkbox.vue'
7
7
  // loadData, search highlight, drag to move, keyboard navigation.
8
8
  const docHues = { orange: 'var(--cat-transform)', green: 'var(--cat-source)', purple: 'var(--cat-merge)', blue: 'var(--info-fg)', teal: 'var(--cat-output)', gray: 'var(--text-muted)' }
9
9
  const INDENT = 18
10
+ // Insert marker (reorder before / after): a 2px --action-primary line with a 6px dot at its left end, drawn between rows.
11
+ // It starts at the target row's content indent (8 + depth * INDENT — the chevron column, which is also the elbow column
12
+ // under showLine) and ends at the row's 8px right padding; its centre sits on the gap between rows (1px, or 0 under showLine).
13
+ function insertMarker(pos, depth) {
14
+ const centre = -((props.showLine ? 0 : 1) / 2)
15
+ return { position: 'absolute', left: (8 + depth * INDENT - 3) + 'px', right: '8px', height: '6px', pointerEvents: 'none', zIndex: 1, ...(pos === 'before' ? { top: (centre - 3) + 'px' } : { bottom: (centre - 3) + 'px' }) }
16
+ }
17
+ const markerDot = { position: 'absolute', left: 0, top: 0, width: '6px', height: '6px', borderRadius: '50%', background: 'var(--action-primary)' }
18
+ const markerLine = { position: 'absolute', left: '3px', right: 0, top: '2px', height: '2px', borderRadius: '1px', background: 'var(--action-primary)' }
10
19
  const props = defineProps({
11
20
  items: { type: Array, default: () => [] },
12
21
  treeData: Array, // alias of items (same as Tree.jsx)
@@ -151,16 +160,19 @@ function onDragOver(e, k) {
151
160
  e.preventDefault()
152
161
  if (!dragKey.value) return
153
162
  const n = index(data.value).byKey[k]
154
- // Only folders are drop targets: hovering a file shows nothing, since it can't contain anything.
155
- if (!n || n.disabled || !isBranch(n) || inSubtree(dragKey.value, k)) { drop.value = null; return }
163
+ if (!n || n.disabled || inSubtree(dragKey.value, k)) { drop.value = null; return }
156
164
  const r = e.currentTarget.getBoundingClientRect()
157
165
  const y = (e.clientY - r.top) / r.height
166
+ // Without reorder only folders are drop targets: hovering a file shows nothing, since it can't contain anything.
167
+ // With reorder, a folder's outer 20% becomes an insert rule, and a file is an insert target over its whole height — top half before, bottom half after.
168
+ if (!isBranch(n)) { drop.value = props.reorder ? { key: k, pos: y < 0.5 ? 'before' : 'after' } : null; return }
158
169
  drop.value = { key: k, pos: props.reorder ? (y < 0.2 ? 'before' : y > 0.8 ? 'after' : 'inside') : 'inside' }
159
170
  }
160
171
  function onDrop(k) {
161
172
  const info = drop.value && drop.value.key === k ? drop.value : null
162
173
  drop.value = null
163
174
  if (!dragKey.value || !info || inSubtree(dragKey.value, k)) { dragKey.value = null; return }
175
+ if (info.pos === 'inside' && !isBranch(index(data.value).byKey[k])) { dragKey.value = null; return }
164
176
  const clone = JSON.parse(JSON.stringify(data.value))
165
177
  let moved = null
166
178
  const prune = nodes => nodes.filter(n => {
@@ -218,14 +230,14 @@ const dropAt = k => (drop.value && drop.value.key === k ? drop.value.pos : null)
218
230
  @click="rowClick(row.n)" @keydown="keyDown($event, row)"
219
231
  :class="{ 'is-disabled': row.n.disabled }"
220
232
  :style="{
221
- minHeight: 'var(--h-tree-item)', borderRadius: 'var(--radius-md)', display: 'flex', alignItems: 'center', gap: '6px',
233
+ position: 'relative', minHeight: 'var(--h-tree-item)', borderRadius: 'var(--radius-md)', display: 'flex', alignItems: 'center', gap: '6px',
222
234
  padding: showLine ? '0 8px 0 2px' : '5px 8px', paddingLeft: showLine ? '2px' : (8 + row.depth * INDENT) + 'px', boxSizing: 'border-box',
223
235
  cursor: row.n.disabled ? 'not-allowed' : 'pointer', opacity: row.n.disabled ? 0.45 : 1,
224
236
  background: dropAt(row.key) === 'inside' ? 'var(--soft-bg)' : selected.includes(row.key) ? 'var(--bg-active)' : 'transparent',
225
237
  outline: dropAt(row.key) === 'inside' ? '2px solid var(--action-primary)' : 'none',
226
238
  outlineOffset: '-2px',
227
- boxShadow: dropAt(row.key) === 'before' ? 'inset 0 2px 0 var(--action-primary)' : dropAt(row.key) === 'after' ? 'inset 0 -2px 0 var(--action-primary)' : 'none',
228
239
  transition: 'var(--t-hover)' }">
240
+ <span v-if="dropAt(row.key) === 'before' || dropAt(row.key) === 'after'" aria-hidden="true" :data-drop-marker="dropAt(row.key)" :style="insertMarker(dropAt(row.key), row.depth)"><span :style="markerDot" /><span :style="markerLine" /></span>
229
241
  <template v-if="showLine">
230
242
  <span v-for="(hasNext, i) in row.trail" :key="i"
231
243
  :style="{ width: INDENT + 'px', alignSelf: 'stretch', flexShrink: 0, marginLeft: i === 0 ? '6px' : 0, borderLeft: hasNext ? '1px solid var(--border-subtle)' : '1px solid transparent' }" />
@@ -17,10 +17,10 @@ export const KEYBRICKS_NAV_FOOTER = [
17
17
  { icon: 'book-open', label: 'Knowledge Base' },
18
18
  ]
19
19
  export const KEYBRICKS_MGMT = [
20
- { icon: 'box', label: 'Modeling', children: ['Dimension', 'Model', 'Environment', 'Journal Template', 'Detail Template', 'Relationship'] },
21
- { icon: 'repeat', label: 'Workflow', children: ['Workflow', 'Requests', 'Task Tracker'] },
20
+ { icon: 'model', label: 'Modeling', children: ['Dimension', 'Model', 'Environment', 'Journal Template', 'Detail Template', 'Relationship'] },
21
+ { icon: 'workflow', label: 'Workflow', children: ['Workflow', 'Requests', 'Task Tracker'] },
22
22
  { icon: 'shield', label: 'Security Management', children: ['Users', 'Roles', 'Permissions'] },
23
23
  { icon: 'refresh-cw', label: 'System Migration' },
24
- { icon: 'circle-check', label: 'Validation Rules' },
24
+ { icon: 'rules-validation', label: 'Validation Rules' },
25
25
  { icon: 'settings-2', label: 'Control Center' },
26
26
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keyfunc/keybricks-ds",
3
- "version": "2.4.2",
3
+ "version": "2.4.6",
4
4
  "description": "Keybricks design system — design tokens, three size tiers and 121 UI components in React and Vue 3, source-only.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
package/readme.md CHANGED
@@ -40,7 +40,7 @@ One product: the Keybricks web app (desktop, 1512×~860 design size). Key surfac
40
40
 
41
41
  ## Iconography
42
42
  - The product's icons are the **Lucide** stroke set (16/14px at 1.33px effective stroke, `currentColor`). We ship `components/icons/Icon.jsx`, embedding the exact Lucide path data for every glyph the product uses. Usage: `<Icon name="search" size={16}/>`.
43
- - **Keybricks glyphs**: thirteen icons drawn for Keybricks itself sit in the core set beside the Lucide glyphs — AI (`ai`, `ai-sparkles`), modules (`reconciliation`, `consolidation`, `journal`, `event`, `view-tree`) and actions (`drilldown`, `add-below`, `copy-data`, `task-execution`, `time-future`, `time-restatement`). Search the Icon Gallery before drawing a new one: *hierarchy* turned out to be `layers`, and *change* is `repeat`.
43
+ - **Keybricks glyphs**: thirteen icons drawn for Keybricks itself sit in the core set beside the Lucide glyphs — AI (`ai`, `ai-sparkles`), modules (`reconciliation`, `consolidation`, `journal`, `event`, `view-tree`) and actions (`drilldown`, `add-below`, `copy-data`, `task-execution`, `time-future`, `time-restatement`). **Business glyphs** (75, 2.4.6): dimensions (`dimension-account`, `dimension-entity`, …), modules (`modeling`, `analytics`, `automation`, `announcement`, `expense_control-*`, `profile-*`, …), rules (`rules-*`, a sheet frame) and tasks (`task-*`, a clipboard frame) — named exactly as the product uses them. Search the Icon Gallery before drawing a new one: *hierarchy* turned out to be `layers`, and *change* is `repeat`.
44
44
  - **Drawing rules** (every core glyph, and any glyph added later): 24×24 grid; paths stay inside 2–22, which leaves 1px of padding once the stroke is drawn; stroke 2, round caps and joins, no fill, `currentColor`; integer coordinates; at least 2px of clear space between separate elements; rect corners rx 2, or rx 1 at 8px and smaller. Check a new glyph at 14 and 16px before adding it.
45
45
  - Icon color follows text color (#525252 default chrome, #a1a1a1 muted, category hue inside tinted tiles).
46
46
  - Tinted icon tiles are a signature motif: 40×40 r14 (palette), 48×48 r16 (nodes), 32×32 r8 hairline-bordered (list rows) — 100-tint bg + 600-hue glyph.
package/readme.zh-CN.md CHANGED
@@ -42,7 +42,7 @@ Keybricks 是面向财务团队的企业绩效管理(EPM)平台:文档与
42
42
 
43
43
  ## 图标系统
44
44
  - 产品图标是 **Lucide** 线性图标集(16/14px,实际描边约 1.33px,颜色为 `currentColor`)。我们提供 `components/icons/Icon.jsx`,内嵌产品用到的每个图形的 Lucide 原始路径数据。用法:`<Icon name="search" size={16}/>`。
45
- - **Keybricks 自绘图标**:为 Keybricks 自行绘制的 13 个图标与 Lucide 图标一起放在核心集中——AI 类(`ai`、`ai-sparkles`)、模块类(`reconciliation`、`consolidation`、`journal`、`event`、`view-tree`)和操作类(`drilldown`、`add-below`、`copy-data`、`task-execution`、`time-future`、`time-restatement`)。新画图标之前,先在图标库里搜索:*hierarchy* 其实就是 `layers`,*change* 就是 `repeat`。
45
+ - **Keybricks 自绘图标**:为 Keybricks 自行绘制的 13 个图标与 Lucide 图标一起放在核心集中——AI 类(`ai`、`ai-sparkles`)、模块类(`reconciliation`、`consolidation`、`journal`、`event`、`view-tree`)和操作类(`drilldown`、`add-below`、`copy-data`、`task-execution`、`time-future`、`time-restatement`)。**业务图标**(75 个,2.4.6):维度类(`dimension-account`、`dimension-entity` 等)、模块类(`modeling`、`analytics`、`automation`、`announcement`、`expense_control-*`、`profile-*` 等)、规则类(`rules-*`,草纸框)与任务类(`task-*`,剪贴板框),名称与产品中一致。新画图标之前,先在图标库里搜索:*hierarchy* 其实就是 `layers`,*change* 就是 `repeat`。
46
46
  - **绘制规范**(所有核心图标,以及今后新增的图标):24×24 网格;路径限制在 2–22 范围内,描边后四周留 1px 边距;描边 2,圆头圆角,无填充,`currentColor`;坐标取整数;相互独立的元素之间至少留 2px 空隙;矩形圆角 rx 2,8px 及以下的小矩形用 rx 1。新增图标前,先在 14px 和 16px 下检查。
47
47
  - 图标颜色跟随文字颜色(默认框架色 #525252,弱化态 #a1a1a1,彩色图标块内为对应分类色)。
48
48
  - 彩色图标块是标志性元素:40×40 圆角 14(组件面板)、48×48 圆角 16(节点)、32×32 圆角 8 带细边框(列表行)——100 档浅色底 + 600 档分类色图标。
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keyfunc/keybricks-ds",
3
- "version": "2.4.2",
3
+ "version": "2.4.0",
4
4
  "source": "tokens/*.css",
5
5
  "note": "Generated by _scripts/build-tokens.mjs — CSS is the source of truth.",
6
6
  "tokens": {