@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.
- package/DEPLOY.md +26 -4
- package/DESIGN.md +23 -5
- package/INTEGRATION.md +22 -4
- package/_adherence.oxlintrc.json +7 -0
- package/components/_cards/relative-time-picker.card.html +45 -0
- package/components/_cards/smart-table.card.html +23 -10
- package/components/_cards/tree.card.html +49 -3
- package/components/_shared/src-data.js +1 -1
- package/components/_shared/src-forms.js +1 -1
- package/components/_shared/src-navigation.js +1 -1
- package/components/_vue/index.mjs +1 -0
- package/components/data/SmartTable.d.ts +10 -4
- package/components/data/SmartTable.jsx +22 -9
- package/components/data/SmartTable.vue +22 -9
- package/components/forms/RelativeTimePicker.d.ts +39 -0
- package/components/forms/RelativeTimePicker.jsx +89 -0
- package/components/forms/RelativeTimePicker.prompt.md +72 -0
- package/components/forms/RelativeTimePicker.vue +81 -0
- package/components/forms/forms.prompt.md +3 -0
- package/components/forms/relative-time.d.ts +12 -0
- package/components/forms/relative-time.js +90 -0
- package/components/index.d.ts +1 -0
- package/components/index.js +1 -0
- package/components/navigation/Sidebar.d.ts +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/package.json +2 -2
- package/readme.md +3 -2
- package/readme.zh-CN.md +3 -2
- package/tokens/tokens.json +1 -1
|
@@ -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' }" />
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keyfunc/keybricks-ds",
|
|
3
|
-
"version": "2.4.
|
|
4
|
-
"description": "Keybricks design system — design tokens, three size tiers and
|
|
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
|
|
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` —— 接入指南:三步跑通首屏、全部
|
|
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`)。
|