@keyfunc/keybricks-ds 2.4.2 → 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 +25 -3
- package/DESIGN.md +17 -5
- package/INTEGRATION.md +12 -3
- package/components/_cards/smart-table.card.html +23 -13
- package/components/_cards/tree.card.html +49 -3
- package/components/_shared/src-data.js +1 -1
- package/components/_shared/src-navigation.js +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/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 +1 -1
- 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 →
|
|
@@ -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), `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), `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
|
@@ -322,7 +322,11 @@ Where each tier belongs:
|
|
|
322
322
|
`SmartTable` adds a **row density** on top of the tier: `default` rows are the tier's `--bar-h`
|
|
323
323
|
(36 / 40 / 48) with `--cell-px`; `compact` rows are the tier's `--control-h` (28 / 32 / 40 — the
|
|
324
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.
|
|
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`
|
|
326
330
|
controls it, `defaultDensity` seeds it. Pills (20), checkboxes (16) and avatars (20) all centre in
|
|
327
331
|
a 28px row. There is no taller option: long text truncates in every density.
|
|
328
332
|
- **md** — everything else: app toolbars, filter bars, tables, long forms, settings pages.
|
|
@@ -424,10 +428,18 @@ An indicator appears **only where a drop is legal**, and its shape names the ope
|
|
|
424
428
|
|
|
425
429
|
- **Move into a container** — the whole target row fills `--soft-bg` with a 2px
|
|
426
430
|
`--action-primary` outline. Never a half-height box: the target is the row, not an edge.
|
|
427
|
-
- **Insert between** — a 2px `--action-primary`
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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.
|
|
431
443
|
- **Connecting two things** (pipeline ports) — the source handle stays lit, a dashed
|
|
432
444
|
`--action-primary` bezier follows the cursor, and the reachable end point lights and swaps
|
|
433
445
|
its glyph for a crosshair. Snapping is by distance, not by hover, so the wire lands even
|
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
|
|
@@ -226,7 +226,7 @@ import Textarea from '@keyfunc/keybricks-ds/components/forms/Textarea.vue'
|
|
|
226
226
|
| Component | What it's for |
|
|
227
227
|
| --- | --- |
|
|
228
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. |
|
|
229
|
-
| `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`). |
|
|
230
230
|
| `Spreadsheet` | Excel-style grid: formula bar, frozen headers, range selection, `=SUM()` evaluation, sheet tabs. |
|
|
231
231
|
| `FilterBar` | List-report filter bar with variant title, wrapping filter fields, and Adapt Filters / Go. |
|
|
232
232
|
| `ListRow` | One row of a list view — icon, title, meta, trailing actions. |
|
|
@@ -245,7 +245,7 @@ import Textarea from '@keyfunc/keybricks-ds/components/forms/Textarea.vue'
|
|
|
245
245
|
| `PageHeading` | Page title with optional subtitle and right-aligned actions. |
|
|
246
246
|
| `PageToolbar` | Secondary bar under the heading: filters, view toggles, bulk actions. |
|
|
247
247
|
| `Breadcrumb` | Path trail; collapses to an ellipsis dropdown past `maxItems`. |
|
|
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, 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. |
|
|
249
249
|
| `DocTree` | Document-oriented tree with file-type glyph hues. |
|
|
250
250
|
| `ThemeToggle` | Light/dark/auto switch. `applyStoredTheme()` restores the choice on load. |
|
|
251
251
|
|
|
@@ -520,6 +520,15 @@ Pin a minor range and update deliberately:
|
|
|
520
520
|
- **Tokens outside CSS.** `tokens.json` / `tokens.js` / `tokens.d.ts` gained the tier tokens
|
|
521
521
|
(`--control-*`, `--bar-h`, `--group-gap`, …) — 352 light tokens in all (2.0.2 adds `--danger-soft-hover`).
|
|
522
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
|
+
|
|
523
532
|
### 2.4.2
|
|
524
533
|
|
|
525
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.
|
|
@@ -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,21 +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" style={{marginBottom:20}}>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>
|
|
34
|
-
<SmartTable fields={fields} rows={rows
|
|
35
|
-
<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. <code>density="compact"</code> controls it, <code>defaultDensity</code> just seeds it.</div>
|
|
36
|
-
<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).
|
|
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>
|
|
37
47
|
</div>;}
|
|
38
48
|
ReactDOM.createRoot(document.getElementById("root")).render(<Sheet/>);
|
|
39
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/>);
|