@keyfunc/keybricks-ds 2.4.0 → 2.4.4

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.
@@ -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' }" />
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@keyfunc/keybricks-ds",
3
- "version": "2.4.0",
4
- "description": "Keybricks design system — design tokens, three size tiers and 120 UI components in React and Vue 3, source-only.",
3
+ "version": "2.4.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",
7
7
  "sideEffects": [
package/readme.md CHANGED
@@ -69,7 +69,7 @@ One product: the Keybricks web app (desktop, 1512×~860 design size). Key surfac
69
69
 
70
70
  **For developers integrating this system**
71
71
  - `DESIGN.md` — the rules file for AI coding agents: absolute token values, interaction states, an explicit "do not" list, and voice. Point Claude Code / Cursor / Copilot at this before they generate UI.
72
- - `INTEGRATION.md` — the integration guide: three steps to first paint, the full 120-component index with import paths, token reference, dark mode, interaction states, versioning.
72
+ - `INTEGRATION.md` — the integration guide: three steps to first paint, the full 121-component index with import paths, token reference, dark mode, interaction states, versioning.
73
73
  - `DEPLOY.md` — running the system on GitLab: what ships in the package and what stays on Pages, the entry points, the release flow, and the bundler config each consumer needs.
74
74
  - `index.html` · `gallery.html` — the documentation site: the browsable system and the filterable thumbnail grid, both deployed to GitLab Pages by CI.
75
75
  - `package.json` · `.gitlab-ci.yml` · `_scripts/` — makes the system an installable package: a version tag publishes to the GitLab Package Registry, the default branch redeploys the docs site, and `npm run check` fails the pipeline if the generated tokens or barrels drift from source.
@@ -83,7 +83,7 @@ One product: the Keybricks web app (desktop, 1512×~860 design size). Key surfac
83
83
  ## Components
84
84
  - `components/icons/` — Icon (+ ICON_PATHS glyph data + feather-paths.js, the full Feather fallback), DataSourceLogo (+ DATA_SOURCE_LOGOS), Unicon (Unicons CDN webfont).
85
85
  - `components/buttons/` — Button, IconButton, ActionChip.
86
- - `components/forms/` — Field, Input, Textarea, Select, SearchInput, Checkbox, NumberStepper, Dropzone, Switch, Radio, RadioGroup, Combobox, MultiSelect, DatePicker, Slider.
86
+ - `components/forms/` — Field, Input, Textarea, Select, SearchInput, Checkbox, NumberStepper, Dropzone, Switch, Radio, RadioGroup, Combobox, MultiSelect, DatePicker, Slider, RelativeTimePicker.
87
87
  - `components/tabs/` — Tabs, PanelTabs, PillTabs, ViewToggle, SegmentedControl.
88
88
  - `components/display/` — Card, StatCard, Badge, StatusBadge, IconTile, ProgressBar, Donut, AreaChart, MiniBars, Avatar, AvatarGroup, Tag, Divider, Kbd.
89
89
  - `components/feedback/` — Stepper, ContextMenu, ActivityItem, DotActivity, LogViewer, Tooltip, Modal, Toast, ToastStack, Alert, EmptyState, Skeleton, Spinner, Menu, Accordion, Banner, Drawer, Timeline.
@@ -110,6 +110,7 @@ These extend the original inventory — each is a standard need this product cle
110
110
  - `FilterBar` — Fiori-style list-report filter bar (variant name + expand/collapse + Adapt Filters (n) + Go).
111
111
  - `Combobox` — type-to-filter single-select dropdown; `MultiSelect` — multi-select dropdown (tags + "+n" overflow + clear all).
112
112
  - `DatePicker` — calendar popover date picker; `Slider` — slider (drag bubble + min/max labels).
113
+ - `RelativeTimePicker` — "how long ago" filter (amount + unit dialog, "Any time" → null) for logs and monitoring.
113
114
  - `Drawer` — right-hand slide-out detail panel; `Banner` — page-level notification (distinct from in-card Alert and floating Toast); `Timeline` — vertical activity timeline; `Tree` — general nested tree (cascading checks, async loading, search highlight, drag into folder, keyboard navigation).
114
115
  - `Tag`, `Divider`, `Kbd` — filter tags, separators and shortcut hints that were hand-built over and over in the UI.
115
116
  - `Menu` (dropdown anchored under its trigger, distinct from `ContextMenu`, which opens at the click point), `Accordion` (general collapsible section, distinct from `ListRow`, which is specific to workflow documents).
package/readme.zh-CN.md CHANGED
@@ -71,7 +71,7 @@ Keybricks 是面向财务团队的企业绩效管理(EPM)平台:文档与
71
71
 
72
72
  **开发团队接入入口**
73
73
  - `DESIGN.md` —— 给 AI 编程代理读的规则文件:绝对数值、交互状态、明确的"禁止"清单、文案语气。让 Claude Code / Cursor / Copilot 在生成界面前先读它。
74
- - `INTEGRATION.md` —— 接入指南:三步跑通首屏、全部 120 个组件的清单与引入路径、token 速查、深色模式、交互状态、版本策略。
74
+ - `INTEGRATION.md` —— 接入指南:三步跑通首屏、全部 121 个组件的清单与引入路径、token 速查、深色模式、交互状态、版本策略。
75
75
  - `DEPLOY.md` —— 在 GitLab 上运行本系统:包里发布什么、什么留在 Pages 上、入口点、发布流程、各消费方需要的构建配置。
76
76
  - `index.html` · `gallery.html` —— 文档站:可浏览的系统主页与可筛选的缩略图网格,均由 CI 部署到 GitLab Pages。
77
77
  - `package.json` · `.gitlab-ci.yml` · `_scripts/` —— 让本系统成为可安装的包:打版本 tag 即发布到 GitLab Package Registry,默认分支更新即重新部署文档站,生成的 token 或入口文件与源码不一致时 `npm run check` 会让流水线失败。
@@ -85,7 +85,7 @@ Keybricks 是面向财务团队的企业绩效管理(EPM)平台:文档与
85
85
  ## 组件清单
86
86
  - `components/icons/` —— Icon(+ ICON_PATHS 图形数据 + feather-paths.js 全量 Feather 回退)、Unicon(Unicons CDN 字体)。
87
87
  - `components/buttons/` —— Button、IconButton、ActionChip。
88
- - `components/forms/` —— Field、Input、Textarea、Select、SearchInput、Checkbox、NumberStepper、Dropzone、Switch、Radio、RadioGroup、Combobox、MultiSelect、DatePicker、Slider。
88
+ - `components/forms/` —— Field、Input、Textarea、Select、SearchInput、Checkbox、NumberStepper、Dropzone、Switch、Radio、RadioGroup、Combobox、MultiSelect、DatePicker、Slider、RelativeTimePicker。
89
89
  - `components/tabs/` —— Tabs、PanelTabs、PillTabs、ViewToggle、SegmentedControl。
90
90
  - `components/display/` —— Card、StatCard、Badge、StatusBadge、IconTile、ProgressBar、Donut、AreaChart、MiniBars、Avatar、AvatarGroup、Tag、Divider、Kbd。
91
91
  - `components/feedback/` —— Stepper、ContextMenu、ActivityItem、DotActivity、LogViewer、Tooltip、Modal、Toast、ToastStack、Alert、EmptyState、Skeleton、Spinner、Menu、Accordion、Banner、Drawer、Timeline。
@@ -112,6 +112,7 @@ Keybricks 是面向财务团队的企业绩效管理(EPM)平台:文档与
112
112
  - `FilterBar` —— Fiori 风格的列表报表筛选栏(变体名 + 展开 / 收起 + Adapt Filters (n) + Go)。
113
113
  - `Combobox` —— 输入即筛选的单选下拉;`MultiSelect` —— 多选下拉(标签 + "+n" 溢出 + 一键清空)。
114
114
  - `DatePicker` —— 日历弹层日期选择;`Slider` —— 滑杆(拖动气泡 + 最值标签)。
115
+ - `RelativeTimePicker` —— "多久以前"筛选器(数量 + 单位弹层,"不限时间"返回 null),用于日志与监控页。
115
116
  - `Drawer` —— 右侧滑出的详情面板;`Banner` —— 页面级通知条(区别于卡片内的 Alert 与浮动的 Toast);`Timeline` —— 垂直活动时间线;`Tree` —— 通用嵌套树(级联勾选、异步加载、搜索高亮、拖入文件夹、键盘导航)。
116
117
  - `Tag`、`Divider`、`Kbd` —— 筛选标签、分隔线、快捷键提示等在界面中被反复手写的小元素。
117
118
  - `Menu`(挂在触发元素下方的下拉菜单,区别于在点击位置弹出的 `ContextMenu`)、`Accordion`(通用可折叠区块,区别于专用于工作流文档的 `ListRow`)。
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keyfunc/keybricks-ds",
3
- "version": "2.4.0",
3
+ "version": "2.4.4",
4
4
  "source": "tokens/*.css",
5
5
  "note": "Generated by _scripts/build-tokens.mjs — CSS is the source of truth.",
6
6
  "tokens": {