@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
package/DEPLOY.md
CHANGED
|
@@ -55,7 +55,7 @@ deploys the documentation site. Watch it under *Build → Pipelines*.
|
|
|
55
55
|
**5. Cut the first release.**
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
|
-
git tag v2.4.
|
|
58
|
+
git tag v2.4.4 && git push --tags
|
|
59
59
|
```
|
|
60
60
|
|
|
61
61
|
That tag runs `generated:check` → `version:check` → `npm publish`. Confirm under *Deploy →
|
|
@@ -112,7 +112,7 @@ import tokens from '@keyfunc/keybricks-ds/tokens.json' // cross-
|
|
|
112
112
|
```
|
|
113
113
|
|
|
114
114
|
Deep imports are the better default in Vue and in any app that cares about tree-shaking —
|
|
115
|
-
a barrel of
|
|
115
|
+
a barrel of 130 exports asks the bundler to prove each one is unused.
|
|
116
116
|
|
|
117
117
|
## 3. Publishing (maintainers)
|
|
118
118
|
|
|
@@ -130,6 +130,28 @@ git push && git push --tags
|
|
|
130
130
|
`.gitlab-ci.yml` then runs: `generated:check` → `version:check` (tag must equal
|
|
131
131
|
`package.json` version) → `npm publish` → `pages`.
|
|
132
132
|
|
|
133
|
+
**Two sources of change.** The design-system project (where releases are packed) and this
|
|
134
|
+
repo can both move: app developers push components straight to `main` (2.4.0 → 2.4.2 carried
|
|
135
|
+
`RelativeTimePicker` that way). So a release is always two steps, never one:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
# step 1 — look before you overwrite: what does GitLab have that the package was not built from?
|
|
139
|
+
git fetch origin && git log --oneline --stat HEAD..origin/main
|
|
140
|
+
# anything listed → bring those files into the design-system project first (onto the system
|
|
141
|
+
# rules, into the docs site, tests and changelog), bump the version past origin/main's
|
|
142
|
+
# package.json, and pack again.
|
|
143
|
+
|
|
144
|
+
# step 2 — release from a clean checkout of origin/main, so the package is the whole tree
|
|
145
|
+
git fetch origin && git reset --hard origin/main
|
|
146
|
+
# copy the unpacked package over the working tree, then:
|
|
147
|
+
npm run build && npm run check && git add -A && git commit -m "<version>: …" && git push && git tag v<version> && git push --tags
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
A rejected push (`fetch first`) means step 1 was skipped. Do not resolve it with `git pull`: a
|
|
151
|
+
half-merged package.json is worse than starting step 1 over. Teammate commits are welcome, but a
|
|
152
|
+
component only counts as part of the system once it has an example page, a test and a changelog
|
|
153
|
+
line — that is what step 1 is for.
|
|
154
|
+
|
|
133
155
|
**Changelog.** Every release gets an entry at the top of `changelog.js` (version, ISO date, and
|
|
134
156
|
items tagged `new` / `improved` / `fixed` / `breaking` / `removed`, each in English, Chinese and
|
|
135
157
|
Japanese). The documentation site renders it as the *Changelog* section under Overview. While a
|
|
@@ -162,7 +184,7 @@ GitLab injects `CI_JOB_TOKEN` — use that instead and no secret is stored anywh
|
|
|
162
184
|
**Install — straight from git** (no registry, no publish; works today)
|
|
163
185
|
|
|
164
186
|
```json
|
|
165
|
-
"@keyfunc/keybricks-ds": "git+ssh://git@gitlab.epm2demo.cn/Felix/keybricks-ds.git#v2.4.
|
|
187
|
+
"@keyfunc/keybricks-ds": "git+ssh://git@gitlab.epm2demo.cn/Felix/keybricks-ds.git#v2.4.4"
|
|
166
188
|
```
|
|
167
189
|
|
|
168
190
|
Because the package has no build step, a git dependency needs no `prepare` script — npm
|
|
@@ -212,7 +234,7 @@ semantic layer. That is what makes dark mode cost nothing.
|
|
|
212
234
|
## Runtime checks
|
|
213
235
|
|
|
214
236
|
`_tests/` holds browser pages that exercise the real components: `react-runtime.html` (the compiled
|
|
215
|
-
bundle), `table-selection.html` (Table loaded from source: checkbox spacing at every width, code chips, two-line rows, sorting), `vue-runtime.html` (the `.vue` files, loaded with vue3-sfc-loader), `cards-interaction*.html`
|
|
237
|
+
bundle), `table-selection.html` (Table loaded from source: checkbox spacing at every width, code chips, two-line rows, sorting), `smart-table-density.html` (SmartTable from source: default / compact rows, the Row height menu, controlled density), `relative-time-picker.html` (RelativeTimePicker from source: helpers, tiers, dialog, keyboard), `tree-reorder.html` (Tree from source: insert-between on leaves and folder edges, self/subtree rejection), `vue-runtime.html` (the `.vue` files, loaded with vue3-sfc-loader), `cards-interaction*.html`
|
|
216
238
|
(clicks through the documentation demos) and `icon-rules.html` (the Keybricks glyphs against the drawing rules). Serve the repo root (`npx serve .`), open a page and read the
|
|
217
239
|
console — every line starts with PASS or FAIL. Run them after changing a component in either framework.
|
|
218
240
|
`measure-cards.html` is a tool, not a test: it measures each documentation page at its card width so the
|
package/DESIGN.md
CHANGED
|
@@ -319,6 +319,16 @@ Where each tier belongs:
|
|
|
319
319
|
|
|
320
320
|
- **sm** — dense data grids (`SmartTable`, `Spreadsheet` and `DataPreview` set it themselves),
|
|
321
321
|
controls inside pipeline nodes, a toolbar that must fit a 32px row.
|
|
322
|
+
`SmartTable` adds a **row density** on top of the tier: `default` rows are the tier's `--bar-h`
|
|
323
|
+
(36 / 40 / 48) with `--cell-px`; `compact` rows are the tier's `--control-h` (28 / 32 / 40 — the
|
|
324
|
+
same as a Spreadsheet cell) with the Spreadsheet's 8px cell padding — `--cell-px` is already 8 at
|
|
325
|
+
sm, so there only the height changes. Header and footer rows follow; the toolbar does not.
|
|
326
|
+
Both grids freeze their edges the same way: the header row is `position: sticky; top: 0` and the
|
|
327
|
+
row-number column `position: sticky; left: 0` inside the scroll area, the corner cell on both axes,
|
|
328
|
+
stacked corner > header > row numbers > body. Frozen cells keep the `--bg-inset` fill so body
|
|
329
|
+
cells never show through, and the frozen edge is a `--border-subtle` hairline — at every tier and density. Users switch it from the toolbar's *Row height* menu (a single-choice menu); `density`
|
|
330
|
+
controls it, `defaultDensity` seeds it. Pills (20), checkboxes (16) and avatars (20) all centre in
|
|
331
|
+
a 28px row. There is no taller option: long text truncates in every density.
|
|
322
332
|
- **md** — everything else: app toolbars, filter bars, tables, long forms, settings pages.
|
|
323
333
|
- **lg** — dialogs (`Modal` sets it), short forms, wizards, onboarding, a page with one task.
|
|
324
334
|
|
|
@@ -418,10 +428,18 @@ An indicator appears **only where a drop is legal**, and its shape names the ope
|
|
|
418
428
|
|
|
419
429
|
- **Move into a container** — the whole target row fills `--soft-bg` with a 2px
|
|
420
430
|
`--action-primary` outline. Never a half-height box: the target is the row, not an edge.
|
|
421
|
-
- **Insert between** — a 2px `--action-primary`
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
431
|
+
- **Insert between** — an insert marker: a 2px `--action-primary` line with a 6px dot of the
|
|
432
|
+
same colour at its left end (the Finder / VS Code shape). It is its own element, not a shadow
|
|
433
|
+
on the row: it sits centred on the gap between rows, starts at the target row's content indent
|
|
434
|
+
(the chevron column, `8 + depth × 18`, which is also the guide-line elbow under `showLine`) and
|
|
435
|
+
ends at the row's right padding — so the line says which level the item lands on. The row keeps
|
|
436
|
+
its own hover or selected fill underneath. Only where ordering is a real operation (`Tree` opts
|
|
437
|
+
in with `reorder`). On a folder the
|
|
438
|
+
outer 20% of the row's height means before / after and the middle 60% means inside; a leaf
|
|
439
|
+
has no inside, so its whole height splits in half — top half before, bottom half after — and
|
|
440
|
+
draws the same rule. So under `reorder` files can be reordered among themselves.
|
|
441
|
+
- **Nothing** — hovering an illegal target (a file without `reorder`, a disabled row, a node's
|
|
442
|
+
own subtree) shows no indicator at all. An indicator for a drop that would be rejected is a lie.
|
|
425
443
|
- **Connecting two things** (pipeline ports) — the source handle stays lit, a dashed
|
|
426
444
|
`--action-primary` bezier follows the cursor, and the reachable end point lights and swaps
|
|
427
445
|
its glyph for a crosshair. Snapping is by distance, not by hover, so the wire lands even
|
|
@@ -455,7 +473,7 @@ The list that prevents the most rework. These are absolute.
|
|
|
455
473
|
- **Do not show a drop indicator where the drop is impossible.** No half-row boxes on a
|
|
456
474
|
container, no insert rules on a node that can't take children.
|
|
457
475
|
- **Do not build a new primitive when one exists.** Check the component index in
|
|
458
|
-
`INTEGRATION.md` first — there are
|
|
476
|
+
`INTEGRATION.md` first — there are 121.
|
|
459
477
|
- **Do not use emoji as icons.** Use `<Icon name="..." />` (330 glyphs) or `<Unicon>` (1,206).
|
|
460
478
|
- **Do not invent new component variants.** If `Button` lacks the variant you want, that is a
|
|
461
479
|
design-system request, not a local override.
|
package/INTEGRATION.md
CHANGED
|
@@ -31,7 +31,7 @@ Not using a registry yet? A git dependency works with no publishing at all — t
|
|
|
31
31
|
has no build step, so npm can use the checkout as-is:
|
|
32
32
|
|
|
33
33
|
```json
|
|
34
|
-
"@keyfunc/keybricks-ds": "git+ssh://git@gitlab.epm2demo.cn/Felix/keybricks-ds.git#v2.4.
|
|
34
|
+
"@keyfunc/keybricks-ds": "git+ssh://git@gitlab.epm2demo.cn/Felix/keybricks-ds.git#v2.4.4"
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
**Source-only package.** `.jsx` and `.vue` files are published as authored, so your
|
|
@@ -162,7 +162,7 @@ Text, weight and tracking follow the tier through `--control-text` / `--control-
|
|
|
162
162
|
weight becomes 600 and all tracking is 0 — nothing to configure. `DESIGN.md` has the full token
|
|
163
163
|
table and the rules behind it.
|
|
164
164
|
|
|
165
|
-
## Component index (
|
|
165
|
+
## Component index (121 components)
|
|
166
166
|
Import path is `@keyfunc/keybricks-ds/components/<group>/<Name>.jsx` (React) or `.vue` (Vue 3).
|
|
167
167
|
Every component has a sibling `.d.ts` with its full props contract, and each group has a
|
|
168
168
|
`.prompt.md` with usage notes.
|
|
@@ -220,12 +220,13 @@ import Textarea from '@keyfunc/keybricks-ds/components/forms/Textarea.vue'
|
|
|
220
220
|
| `Slider` | Range input with a filled track, drag value bubble and optional min/max labels. |
|
|
221
221
|
| `DatePicker` | Text field plus a month-grid popover. Values are `YYYY-MM-DD` strings; `locale` localises the month title, weekday initials and field text. |
|
|
222
222
|
| `Dropzone` | File drop target with hover, drag-over, uploaded and error states. |
|
|
223
|
+
| `RelativeTimePicker` | "How long ago" filter for logs, monitoring and inboxes: a field-shaped trigger opening a small dialog with an amount input, a unit select (minute … month, optional "Any time" → `null`) and a live "From" preview. Pure helpers in `forms/relative-time.js` (`resolveRelativeTime`, `normalizeRelativeTime` …). `labels` localises every string. |
|
|
223
224
|
|
|
224
225
|
### `data/` — tables and structured values
|
|
225
226
|
| Component | What it's for |
|
|
226
227
|
| --- | --- |
|
|
227
228
|
| `Table` | The standard table (renamed from `DataTable` in 1.0.6; the old name still works as a deprecated alias from the package entry until 3.0, but file imports must point at `data/Table.jsx` / `Table.vue`): sortable headers, per-column render functions, alignment, selection. It is a size region — rows are 32 / 40 / 44px and cells share the tier's text; `size="sm"` (or the 1.x `dense`) for preview grids. Put it in a rounded box with `bordered` (it draws its own frame), or `framed` when it sits flush inside a `Card pad={false}` — never wrap it in your own bordered div, the header rule doubles against the frame. `empty` (Vue: `#empty` slot) renders inside the table when there are no rows. `resizable` adds a short divider between header cells — hover darkens it, drag sets the width; pass `columnWidths` + `onColumnResize` to persist. |
|
|
228
|
-
| `SmartTable` | Notion/Airtable-style table — typed field columns, inline editing, row hover actions. |
|
|
229
|
+
| `SmartTable` | Notion/Airtable-style table — typed field columns, inline editing, row hover actions, default/compact row density, frozen header row and row-number column (like `Spreadsheet`). |
|
|
229
230
|
| `Spreadsheet` | Excel-style grid: formula bar, frozen headers, range selection, `=SUM()` evaluation, sheet tabs. |
|
|
230
231
|
| `FilterBar` | List-report filter bar with variant title, wrapping filter fields, and Adapt Filters / Go. |
|
|
231
232
|
| `ListRow` | One row of a list view — icon, title, meta, trailing actions. |
|
|
@@ -244,7 +245,7 @@ import Textarea from '@keyfunc/keybricks-ds/components/forms/Textarea.vue'
|
|
|
244
245
|
| `PageHeading` | Page title with optional subtitle and right-aligned actions. |
|
|
245
246
|
| `PageToolbar` | Secondary bar under the heading: filters, view toggles, bulk actions. |
|
|
246
247
|
| `Breadcrumb` | Path trail; collapses to an ellipsis dropdown past `maxItems`. |
|
|
247
|
-
| `Tree` | Generic nested tree: cascading checkboxes with indeterminate parents (`checkStrictly` opts out), disabled nodes vs. locked checkboxes, `showLine` guides, async `loadData`, search highlight, drag to move into a folder, full keyboard nav. |
|
|
248
|
+
| `Tree` | Generic nested tree: cascading checkboxes with indeterminate parents (`checkStrictly` opts out), disabled nodes vs. locked checkboxes, `showLine` guides, async `loadData`, search highlight, drag to move into a folder (`reorder` adds insert-between on folder edges and between leaves), full keyboard nav. |
|
|
248
249
|
| `DocTree` | Document-oriented tree with file-type glyph hues. |
|
|
249
250
|
| `ThemeToggle` | Light/dark/auto switch. `applyStoredTheme()` restores the choice on load. |
|
|
250
251
|
|
|
@@ -519,6 +520,23 @@ Pin a minor range and update deliberately:
|
|
|
519
520
|
- **Tokens outside CSS.** `tokens.json` / `tokens.js` / `tokens.d.ts` gained the tier tokens
|
|
520
521
|
(`--control-*`, `--bar-h`, `--group-gap`, …) — 352 light tokens in all (2.0.2 adds `--danger-soft-hover`).
|
|
521
522
|
|
|
523
|
+
### 2.4.4
|
|
524
|
+
|
|
525
|
+
- **`.npmrc`** (added on `main` in `817e86d`) now reads the npmjs.org token from the `NPM_TOKEN` environment variable (`${NPM_TOKEN?}` — empty when unset, so builds never fail) instead of storing it in the repository. Set `NPM_TOKEN` locally if you need it; the GitLab release uses `CI_JOB_TOKEN` and does not.
|
|
526
|
+
- **`Tree` reorder between leaves** — with `reorder`, a leaf row is now an insert target over its whole height (top half → `before`, bottom half → `after`) and shows the same insert marker as a folder edge; folders keep the 20 / 60 / 20 before / inside / after split. `onDrop({ dragKey, dropKey, position, treeData })` is unchanged; drops onto the dragged node or into its own subtree are still ignored, and `inside` on a leaf is rejected. The before / after indicator is now an insert marker — a separate 2px `--action-primary` line with a 6px dot at its left end, centred on the gap between rows, from the target row's indent (`8 + depth × 18`) to its right padding — instead of an inset shadow on the row; the row keeps its hover / selected fill, and only `inside` fills with `--soft-bg` + outline. Rows are now `position: relative`. React and Vue.
|
|
527
|
+
|
|
528
|
+
### 2.4.3
|
|
529
|
+
|
|
530
|
+
- **`SmartTable` frozen edges** — the field header row is `position: sticky; top: 0` and the 40px row-number column `position: sticky; left: 0` inside the scroll area (`height` sets it), the corner cell on both axes; stacked corner > header > row numbers > body, `--bg-inset` fill kept on sticky cells, `--border-subtle` hairline on the frozen edge. Works at every tier and both densities. No API change; give the grid a `height` to get vertical scrolling.
|
|
531
|
+
|
|
532
|
+
### 2.4.2
|
|
533
|
+
|
|
534
|
+
- **New `RelativeTimePicker`** (forms) — the relative-time filter from the product, contributed by the app team and brought onto the system rules: inline token styling (the separate `relative-time.css` is gone), `Field` labels in the dialog, `data-field` hover, `.5` disabled opacity like every other field. Unit labels may be `{ one, other }` and follow `Intl.PluralRules` of `locale` ("1 hour ago" / "2 hours ago"; a CJK label stays one string); the amount field is a plain numeric text field (`inputmode="numeric"`, no native spinners). Props unchanged: `value` / `onChange(value, date)` (Vue `v-model` + `@change`), `allowUnlimited`, `labels`, `locale`, `referenceDate`, `formatDate`, `formatValue`, `size`. Helpers in `forms/relative-time.js`. Also brought in from the 2.4.1 merge: the app team's original files (`relative-time.js`, types, prompt) are kept; only the styling moved inline.
|
|
535
|
+
|
|
536
|
+
### 2.4.1
|
|
537
|
+
|
|
538
|
+
- **`SmartTable` row density.** `density: "default" | "compact"` (+ `defaultDensity`, `onDensityChange`; Vue `v-model:density` / `@density-change`). Default rows stay the tier's `--bar-h`; compact rows are the tier's `--control-h` (28 at sm, the Spreadsheet's cell height) with 8px cell padding, header and footer included. The toolbar gains a *Row height* single-choice menu. `rowHeight` still works and overrides density.
|
|
539
|
+
|
|
522
540
|
### 2.4.0
|
|
523
541
|
|
|
524
542
|
- **App Chrome matches the product.** `Sidebar`: 56px brand head without a rule, `--text-md` brand, a 2px-gap list, `trailing: true` items with a chevron (they open a side panel), Collapse / Expand item (chevron-left / panel-left), pinned footer and an optional `version` line. New `tone` prop — `"recessed"` (grey rail, active item a white tile with a hairline ring; **the new default**), `"flush"` (the old white look), `"contrast"` (dark-scoped rail), `"custom"` + `color` (`SIDEBAR_COLORS` or any hex). `KEYBRICKS_NAV` is the product's nav (Home … Task Execution); new `KEYBRICKS_NAV_FOOTER` (Management, Knowledge Base) is the default footer (was Help / Settings / Dark Mode) and `KEYBRICKS_MGMT` holds the Management panel groups. To keep the old look pass `tone="flush"` and your own `footerItems`.
|
package/_adherence.oxlintrc.json
CHANGED
|
@@ -405,6 +405,10 @@
|
|
|
405
405
|
"selector": "JSXOpeningElement[name.name='Radio'] > JSXAttribute[name.name='size'] > Literal[value!=/^(?:sm|md|lg)$/]",
|
|
406
406
|
"message": "<Radio> size must be one of 'sm' | 'md' | 'lg'."
|
|
407
407
|
},
|
|
408
|
+
{
|
|
409
|
+
"selector": "JSXOpeningElement[name.name='RelativeTimeValue'] > JSXAttribute > JSXIdentifier[name!=/^(?:amount|unit|key|ref|className|style|children)$/]",
|
|
410
|
+
"message": "<RelativeTimeValue> doesn't accept that prop. Declared props: amount, unit."
|
|
411
|
+
},
|
|
408
412
|
{
|
|
409
413
|
"selector": "JSXOpeningElement[name.name='Ribbon3DSeries'] > JSXAttribute > JSXIdentifier[name!=/^(?:name|values|color|key|ref|className|style|children)$/]",
|
|
410
414
|
"message": "<Ribbon3DSeries> doesn't accept that prop. Declared props: name, values, color."
|
|
@@ -785,6 +789,9 @@
|
|
|
785
789
|
"Radio": {
|
|
786
790
|
"replaces": []
|
|
787
791
|
},
|
|
792
|
+
"RelativeTimeValue": {
|
|
793
|
+
"replaces": []
|
|
794
|
+
},
|
|
788
795
|
"Ribbon3DSeries": {
|
|
789
796
|
"replaces": []
|
|
790
797
|
},
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<!-- @dsCard group="Components" viewport="700x720" name="Relative Time Picker" subtitle="Filter by how long ago · md / sm / lg · unlimited · disabled · localised labels" -->
|
|
2
|
+
<!DOCTYPE html>
|
|
3
|
+
<html><head><meta charset="utf-8"><script src="../_shared/frame-theme.js"></script><link rel="stylesheet" href="../../styles.css"><link rel="stylesheet" href="../../tokens/hover.css"><link rel="stylesheet" href="../_shared/card-kit.css">
|
|
4
|
+
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
|
|
5
|
+
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
|
|
6
|
+
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
|
|
7
|
+
<script src="../../_ds_bundle.js"></script>
|
|
8
|
+
<script src="../_shared/card-kit.js"></script>
|
|
9
|
+
<script src="../_shared/view-source.js"></script>
|
|
10
|
+
<style>body{margin:0;background:var(--bg-surface)}</style></head>
|
|
11
|
+
<body><div id="root"></div>
|
|
12
|
+
<script type="text/babel">
|
|
13
|
+
const { RelativeTimePicker, Field, Button, SearchInput } = window.KeybricksDesignSystem_71231a;
|
|
14
|
+
const ZH = { title: "相对时间", amount: "数量", unit: "单位", preview: "起始时间", unlimited: "不限时间", Minute: "分钟前", Hour: "小时前", Day: "天前", Week: "周前", Month: "个月前" };
|
|
15
|
+
function Sheet() {
|
|
16
|
+
const [a, setA] = React.useState({ amount: 1, unit: "Hour" });
|
|
17
|
+
const [b, setB] = React.useState({ amount: 7, unit: "Day" });
|
|
18
|
+
const [c, setC] = React.useState({ amount: 0, unit: "None" });
|
|
19
|
+
const [z, setZ] = React.useState({ amount: 30, unit: "Minute" });
|
|
20
|
+
const [last, setLast] = React.useState(null);
|
|
21
|
+
const on = (set) => (v, d) => { set(v); setLast(d ? d.toLocaleString() : "null (any time)"); };
|
|
22
|
+
return <div className="kb-sheet">
|
|
23
|
+
<KBSec title="Trigger — md · sm · lg" note="A field-shaped trigger at the tier height; click it to open the dialog (amount + unit, live “From” preview). Omit size to inherit the region's tier.">
|
|
24
|
+
<KBRow k="md"><div style={{ width: 220 }}><RelativeTimePicker value={a} onChange={on(setA)} /></div></KBRow>
|
|
25
|
+
<KBRow k="sm"><div data-size="sm" style={{ width: 200 }}><RelativeTimePicker value={b} onChange={on(setB)} /></div></KBRow>
|
|
26
|
+
<KBRow k="lg"><div data-size="lg" style={{ width: 240 }}><RelativeTimePicker value={a} onChange={on(setA)} /></div></KBRow>
|
|
27
|
+
</KBSec>
|
|
28
|
+
<KBSec title="States" note="Unlimited reads “Any time” and resolves to null. allowUnlimited={false} removes that option; 0 still means unlimited for legacy filters.">
|
|
29
|
+
<KBRow k="unlimited"><div style={{ width: 220 }}><RelativeTimePicker value={c} onChange={on(setC)} /></div></KBRow>
|
|
30
|
+
<KBRow k="no unlimited"><div style={{ width: 220 }}><RelativeTimePicker value={b} onChange={on(setB)} allowUnlimited={false} /></div></KBRow>
|
|
31
|
+
<KBRow k="disabled"><div style={{ width: 220 }}><RelativeTimePicker value={a} disabled /></div></KBRow>
|
|
32
|
+
</KBSec>
|
|
33
|
+
<KBSec title="In a filter bar · localised" note="labels overrides every string; the dialog copies the region's tier, language and theme. onChange(value, date) only fires on edits, never on mount.">
|
|
34
|
+
<KBRow k="zh-CN" style={{ display: "block" }}><div lang="zh-CN" style={{ display: "flex", alignItems: "flex-end", gap: "var(--group-gap)" }}>
|
|
35
|
+
<Field label="时间范围" style={{ width: 200 }}><RelativeTimePicker value={z} onChange={on(setZ)} labels={ZH} locale="zh-CN" /></Field>
|
|
36
|
+
<Field label="关键字" style={{ width: 200 }}><SearchInput placeholder="搜索日志" /></Field>
|
|
37
|
+
<Button variant="outline">查询</Button>
|
|
38
|
+
</div></KBRow>
|
|
39
|
+
<div className="note">Last change resolved to: <code>{last || "—"}</code></div>
|
|
40
|
+
</KBSec>
|
|
41
|
+
</div>;
|
|
42
|
+
}
|
|
43
|
+
ReactDOM.createRoot(document.getElementById("root")).render(<Sheet />);
|
|
44
|
+
if (window.KBViewSource) setTimeout(function () { KBViewSource.mount("#root", [{ "name": "RelativeTimePicker.jsx", "path": "../forms/RelativeTimePicker.jsx" }, { "name": "relative-time.js", "path": "../forms/relative-time.js", "vue": "../forms/relative-time.js", "vueName": "relative-time.js" }]); }, 0);
|
|
45
|
+
</script></body></html>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- @dsCard group="Components" viewport="
|
|
1
|
+
<!-- @dsCard group="Components" viewport="700x1040" name="Smart Table (Database Grid)" subtitle="Frozen header and row numbers · default and compact density · typed fields · hue pills · aggregates · + New" -->
|
|
2
2
|
<!DOCTYPE html>
|
|
3
3
|
<html><head><meta charset="utf-8"><script src="../_shared/frame-theme.js"></script><link rel="stylesheet" href="../../styles.css"><link rel="stylesheet" href="../../tokens/hover.css"><link rel="stylesheet" href="../_shared/card-kit.css">
|
|
4
4
|
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
|
|
@@ -19,18 +19,31 @@ const fields=[
|
|
|
19
19
|
{key:"done",label:"Done",type:"checkbox",width:58,aggregate:"count"},
|
|
20
20
|
{key:"effort",label:"Effort",type:"number",width:68,aggregate:"sum"},
|
|
21
21
|
{key:"progress",label:"Progress",type:"progress",width:118,aggregate:"avg"},
|
|
22
|
+
{key:"entity",label:"Entity",type:"select",width:96,options:{"HK":"blue","SG":"teal","CN":"purple","JP":"pink"}},
|
|
23
|
+
{key:"notes",label:"Notes",type:"longText",width:220},
|
|
22
24
|
];
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
{task:"
|
|
26
|
-
{task:"
|
|
27
|
-
{task:"
|
|
28
|
-
{task:"
|
|
29
|
-
{task:"
|
|
25
|
+
const owners=["Felix Zhang","Maria Gomez","Ken Watanabe","Priya Nair"], statuses=["Live","In progress","Blocked","Backlog"], ents=["HK","SG","CN","JP"];
|
|
26
|
+
const base=[
|
|
27
|
+
{task:"Migrate journal import",tags:["Data","Q3"],notes:"Cut over after the August close"},
|
|
28
|
+
{task:"FX rounding rule review",tags:["Finance","Urgent"],notes:"Treasury sign-off pending"},
|
|
29
|
+
{task:"Vendor master dedupe",tags:["Data"],notes:"Fuzzy match on tax ID first"},
|
|
30
|
+
{task:"Consolidation dry run",tags:["Finance"],notes:"Blocked on the IC matching rules"},
|
|
31
|
+
{task:"Archive 2025 workpapers",tags:["Q3"],notes:"Retention policy: 7 years"},
|
|
32
|
+
{task:"Reconciliation alerts",tags:["Data","Urgent"],notes:"Slack channel wired up"},
|
|
33
|
+
{task:"Lease schedule rebuild",tags:["Finance"],notes:"IFRS 16 remeasurement"},
|
|
34
|
+
{task:"Budget template refresh",tags:["Q3"],notes:"Drivers moved to the Assumptions sheet"},
|
|
35
|
+
{task:"Audit evidence export",tags:["Finance","Data"],notes:"PBC list v3"},
|
|
36
|
+
{task:"Cash forecast automation",tags:["Data"],notes:"13-week rolling"},
|
|
37
|
+
{task:"Intercompany netting",tags:["Finance"],notes:"Monthly cycle, day 3"},
|
|
38
|
+
{task:"Chart of accounts cleanup",tags:["Data","Q3"],notes:"Retire 41 unused accounts"},
|
|
30
39
|
];
|
|
40
|
+
const rows=base.map((b,i)=>({...b,status:statuses[i%4],owner:owners[i%4],due:"Aug "+(12+i),done:i%3===0,effort:[8,5,3,13,2,5][i%6],progress:[100,60,45,20,0,100][i%6],entity:ents[i%4]}));
|
|
31
41
|
function Sheet(){return <div>
|
|
32
|
-
<SmartTable fields={fields} rows={rows} title="Launch tracker"/>
|
|
33
|
-
<div className="hint">
|
|
42
|
+
<SmartTable fields={fields} rows={rows} title="Launch tracker" height={300}/>
|
|
43
|
+
<div className="hint" style={{marginBottom:20}}>Frozen edges, like a Spreadsheet: scroll down and the field header stays; scroll right and the row-number column stays; the corner cell holds on both axes. Sticky cells keep the <code>--bg-inset</code> fill and a <code>--border-subtle</code> hairline on the frozen edge. Default density — rows are the tier's bar height (36px at sm). The toolbar's <b>Row height</b> menu switches between Default and Compact.</div>
|
|
44
|
+
<SmartTable fields={fields} rows={rows} title="Launch tracker — compact" defaultDensity="compact" height={240}/>
|
|
45
|
+
<div className="hint" style={{marginBottom:20}}>Compact density — rows are the tier's control height (28px at sm, the same as a Spreadsheet cell) with the Spreadsheet's 8px cell padding (the same as sm's <code>--cell-px</code>, so here only the height changes); header and footer follow, and so do the frozen edges. <code>density="compact"</code> controls it, <code>defaultDensity</code> just seeds it.</div>
|
|
46
|
+
<div className="hint">Typed fields with per-type header icons; select/multi-select render theme-aware hue pills; hover a row number for the expand affordance; double-click text/number/date cells to edit; “+ New” appends a row; the footer aggregates per column (COUNT / SUM / AVG). Without <code>height</code> the grid grows with its rows and only the row-number column freezes.</div>
|
|
34
47
|
</div>;}
|
|
35
48
|
ReactDOM.createRoot(document.getElementById("root")).render(<Sheet/>);
|
|
36
49
|
if (window.KBViewSource) setTimeout(function(){ KBViewSource.mount("#root", [{"name":"SmartTable.jsx","path":"../data/SmartTable.jsx"}]); }, 0);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- @dsCard group="Components" viewport="
|
|
1
|
+
<!-- @dsCard group="Components" viewport="700x2820" name="Tree" subtitle="Cascading checkboxes · disabled nodes · lines · async load · search · drag · reorder · keyboard" -->
|
|
2
2
|
<!DOCTYPE html>
|
|
3
3
|
<html><head><meta charset="utf-8"><script src="../_shared/frame-theme.js"></script><link rel="stylesheet" href="../../styles.css"><link rel="stylesheet" href="../../tokens/hover.css"><link rel="stylesheet" href="../_shared/card-kit.css">
|
|
4
4
|
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
|
|
@@ -94,6 +94,52 @@ function Draggable(){
|
|
|
94
94
|
</div>;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
const REORDER=[
|
|
98
|
+
{key:"recon",title:"Reconciliation",isLeaf:false,children:[
|
|
99
|
+
{key:"bank",title:"Bank statements.xlsx",hue:"green"},
|
|
100
|
+
{key:"ic",title:"Intercompany balances.xlsx",hue:"green"},
|
|
101
|
+
{key:"ar",title:"AR aging.csv",hue:"blue"},
|
|
102
|
+
{key:"ap",title:"AP aging.csv",hue:"blue"}]},
|
|
103
|
+
{key:"adj",title:"Adjustments.docx",hue:"purple"},
|
|
104
|
+
{key:"memo",title:"Close memo.docx",hue:"purple"},
|
|
105
|
+
];
|
|
106
|
+
function Reorderable(){
|
|
107
|
+
const [last,setLast]=React.useState(null);
|
|
108
|
+
return <div>
|
|
109
|
+
<span className="lbl">Reorder — leaf rows are insert targets too</span>
|
|
110
|
+
<Tree items={REORDER} draggable reorder defaultExpanded={["recon"]} onDrop={(i)=>setLast(i.dragKey+" → "+i.position+" "+i.dropKey)}/>
|
|
111
|
+
<div className="hint">{last ? <>Moved <code>{last}</code></> : <>With <code>reorder</code>, drag <em>Bank statements</em> below <em>AR aging</em>: a file’s top half means before, its bottom half after. The insert marker — a 2px line with a dot — sits on the gap between rows and starts at the target’s indent. Folders keep their 20% / 60% / 20% split (before / inside / after).</>}</div>
|
|
112
|
+
</div>;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* Frozen drop states for reference: each tree gets a synthetic dragstart + dragover after mount, so the real component draws its own indicator. */
|
|
116
|
+
const DS_ITEMS=[{key:"recon",title:"Reconciliation",isLeaf:false,children:[{key:"bank",title:"Bank statements.xlsx",hue:"green"},{key:"ic",title:"Intercompany.xlsx",hue:"green"},{key:"ar",title:"AR aging.csv",hue:"blue"}]},{key:"memo",title:"Close memo.docx",hue:"purple"}];
|
|
117
|
+
function FrozenDrop({from,to,ratio,label,note}){
|
|
118
|
+
const ref=React.useRef(null);
|
|
119
|
+
React.useEffect(()=>{
|
|
120
|
+
const row=(t)=>[...ref.current.querySelectorAll("[role=treeitem]")].find(r=>r.textContent.trim().startsWith(t));
|
|
121
|
+
const fire=(el,type,y)=>{const r=el.getBoundingClientRect();const e=new Event(type,{bubbles:true,cancelable:true});Object.defineProperty(e,"dataTransfer",{value:{effectAllowed:"move",dropEffect:"move",setData(){},getData(){return "";}}});Object.defineProperty(e,"clientY",{value:r.top+r.height*y});el.dispatchEvent(e);};
|
|
122
|
+
const t1=setTimeout(()=>{fire(row(from),"dragstart",.5);},80);
|
|
123
|
+
const t2=setTimeout(()=>{fire(row(to),"dragover",ratio);},200);
|
|
124
|
+
return ()=>{clearTimeout(t1);clearTimeout(t2);};
|
|
125
|
+
},[]);
|
|
126
|
+
return <div ref={ref}>
|
|
127
|
+
<span className="lbl">{label}</span>
|
|
128
|
+
<Tree items={DS_ITEMS} draggable reorder defaultExpanded={["recon"]} defaultSelected={["ic"]}/>
|
|
129
|
+
<div className="hint">{note}</div>
|
|
130
|
+
</div>;
|
|
131
|
+
}
|
|
132
|
+
function DropStates(){
|
|
133
|
+
return <div style={{gridColumn:"1/-1"}}>
|
|
134
|
+
<span className="lbl" style={{color:"var(--text-heading)",fontWeight:"var(--weight-medium)"}}>Drop states — dragging <em>Close memo</em> (frozen for reference)</span>
|
|
135
|
+
<div style={{display:"grid",gridTemplateColumns:"repeat(3,minmax(0,1fr))",gap:20}}>
|
|
136
|
+
<FrozenDrop from="Close memo" to="Intercompany" ratio={.25} label="Before — top half of a file" note="2px line with a 6px dot, between Bank statements above and this row, starting at the file's indent. The selected row keeps its fill."/>
|
|
137
|
+
<FrozenDrop from="Close memo" to="Intercompany" ratio={.75} label="After — bottom half of a file" note="The same marker on the gap below. Its left end at the child indent says the item lands inside Reconciliation."/>
|
|
138
|
+
<FrozenDrop from="Close memo" to="Reconciliation" ratio={.5} label="Inside — middle 60% of a folder" note="Only “inside” fills the row: --soft-bg with a 2px outline. A folder’s outer 20% draws the before / after marker at the folder’s own indent."/>
|
|
139
|
+
</div>
|
|
140
|
+
</div>;
|
|
141
|
+
}
|
|
142
|
+
|
|
97
143
|
function AsyncLoad(){
|
|
98
144
|
const [log,setLog]=React.useState(0);
|
|
99
145
|
const load=(node)=>new Promise(res=>setTimeout(()=>{
|
|
@@ -136,13 +182,13 @@ function Sheet(){return <div className="g">
|
|
|
136
182
|
<Basic/><Checkable/>
|
|
137
183
|
<Strict/><DisabledCase/>
|
|
138
184
|
<Lines/><Multi/>
|
|
139
|
-
<Draggable/><AsyncLoad/>
|
|
185
|
+
<Draggable/><Reorderable/><DropStates/><AsyncLoad/>
|
|
140
186
|
<Searchable/><WithActions/>
|
|
141
187
|
<div className="note">
|
|
142
188
|
<strong>Keyboard.</strong> The tree is a single tab stop. <code>↑ ↓</code> move between visible rows, <code>→</code> opens a branch (or steps into it), <code>←</code> closes it (or jumps to the parent), <code>Enter</code> selects, <code>Space</code> toggles the checkbox. Focus draws the global 2px <code>--focus-ring</code>, never a new colour.
|
|
143
189
|
<br/><br/><strong>Checkbox semantics.</strong> A parent is checked only when every <em>enabled</em> descendant is checked, and indeterminate when some are — so <code>disabled</code> and <code>disableCheckbox</code> rows never block a parent from completing. Use <code>checkStrictly</code> when a branch is a real selectable value rather than a roll-up of its children.
|
|
144
190
|
<br/><br/><strong>Selection vs. expansion.</strong> With <code>selectable={"{false}"}</code> a row click only expands — the right default for a checkbox picker. Otherwise a click both selects and expands, matching the file-tree pattern used in the product sidebar.
|
|
145
|
-
<br/><br/><strong>Dragging moves a node into a folder.</strong> Files are not drop targets — hovering one shows nothing rather than an indicator for a move that can’t happen — and neither is a folder inside the dragged node’s own subtree. Pass <code>reorder</code> when order matters: a folder’s outer 20%
|
|
191
|
+
<br/><br/><strong>Dragging moves a node into a folder.</strong> Files are not drop targets — hovering one shows nothing rather than an indicator for a move that can’t happen — and neither is a folder inside the dragged node’s own subtree. Pass <code>reorder</code> when order matters: a folder’s outer 20% and either half of a file then draw an insert marker — a 2px line with a 6px dot on the gap between rows, starting at the target’s indent so the landing level is visible.
|
|
146
192
|
</div>
|
|
147
193
|
</div>;}
|
|
148
194
|
ReactDOM.createRoot(document.getElementById("root")).render(<Sheet/>);
|