@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.
- package/DEPLOY.md +25 -3
- package/DESIGN.md +18 -6
- package/INTEGRATION.md +16 -6
- package/_adherence.oxlintrc.json +7 -0
- package/_scripts/build-index.mjs +2 -2
- package/assets/icons/icon-index.js +1 -1
- package/assets/icons/icon-index.json +1 -1
- package/components/_cards/activity.card.html +1 -1
- package/components/_cards/button-1-base.card.html +1 -1
- package/components/_cards/button-3-sizes-icons.card.html +1 -1
- package/components/_cards/icons-0-usage.card.html +1 -1
- package/components/_cards/icons-1-core.card.html +2 -2
- package/components/_cards/icons-2-feather.card.html +1 -1
- package/components/_cards/icons-2b-lucide.card.html +44 -0
- package/components/_cards/listrow.card.html +2 -2
- package/components/_cards/smart-table.card.html +23 -13
- package/components/_cards/table.card.html +10 -10
- package/components/_cards/tree.card.html +49 -3
- package/components/_shared/src-data.js +1 -1
- package/components/_shared/src-icons.js +1 -1
- package/components/_shared/src-navigation.js +1 -1
- package/components/_vue/index.mjs +2 -0
- package/components/buttons/buttons.prompt.md +1 -1
- package/components/data/SmartTable.d.ts +3 -2
- package/components/data/SmartTable.jsx +11 -6
- package/components/data/SmartTable.vue +10 -5
- package/components/icons/Icon.jsx +81 -5
- package/components/icons/Icon.prompt.md +1 -1
- package/components/icons/Lucide.d.ts +22 -0
- package/components/icons/Lucide.jsx +18 -0
- package/components/icons/Lucide.vue +12 -0
- package/components/icons/icon-paths.js +79 -3
- package/components/icons/lucide-loader.js +27 -0
- package/components/index.d.ts +1 -0
- package/components/index.js +1 -0
- package/components/navigation/Breadcrumb.jsx +1 -1
- package/components/navigation/Sidebar.d.ts +1 -1
- package/components/navigation/ThemeToggle.vue +1 -1
- package/components/navigation/Tree.d.ts +1 -1
- package/components/navigation/Tree.jsx +21 -5
- package/components/navigation/Tree.vue +16 -4
- package/components/navigation/nav-model.js +3 -3
- package/package.json +1 -1
- package/readme.md +1 -1
- package/readme.zh-CN.md +1 -1
- 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
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
|
|
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: '
|
|
21
|
-
{ icon: '
|
|
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: '
|
|
24
|
+
{ icon: 'rules-validation', label: 'Validation Rules' },
|
|
25
25
|
{ icon: 'settings-2', label: 'Control Center' },
|
|
26
26
|
]
|
package/package.json
CHANGED
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
|
|
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 档分类色图标。
|