@x12i/fieldkit 1.0.0
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/LICENSE +21 -0
- package/README.md +153 -0
- package/dist/index.cjs +662 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +186 -0
- package/dist/index.d.ts +186 -0
- package/dist/index.js +609 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 x12i
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# @x12i/fieldkit
|
|
2
|
+
|
|
3
|
+
Metadata-driven React components for rendering structured records — markdown-derived
|
|
4
|
+
report fields, pipeline output, whatever — as polished UI instead of a plain key/value
|
|
5
|
+
dump. You own a small **rules map** (`field name -> how to render it`); fieldkit owns
|
|
6
|
+
the **toolbox** of renderers those rules point at, plus a **shape-based fallback** for
|
|
7
|
+
any field your map doesn't cover yet.
|
|
8
|
+
|
|
9
|
+
Nothing in the package is domain-specific. Enum colors, which fields count as
|
|
10
|
+
"content" vs. "diagnostics", and what counts as an attention-worthy state are all
|
|
11
|
+
supplied by you — fieldkit just guarantees every field ends up rendered as *something*,
|
|
12
|
+
never silently dropped.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @x12i/fieldkit
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`react` and `react-dom` (>=18) are peer dependencies.
|
|
21
|
+
|
|
22
|
+
## Quick start
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { RecordView } from '@x12i/fieldkit';
|
|
26
|
+
|
|
27
|
+
const record = {
|
|
28
|
+
status: 'REQUIRE_INFORMATION',
|
|
29
|
+
summary: 'This endpoint asset has no display name or usable address…',
|
|
30
|
+
highlights: ['Asset record is active…', 'Traffic observation coverage is complete…'],
|
|
31
|
+
riskSignals: ['Identity is unresolved…'],
|
|
32
|
+
openQuestions: ['What host, device, or endpoint corresponds to this source ID?'],
|
|
33
|
+
groundingStatus: 'INSUFFICIENT_EVIDENCE',
|
|
34
|
+
quality: 'EMPTY_MODEL',
|
|
35
|
+
stepId: 'triage-endpoint-asset',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const fieldRules = {
|
|
39
|
+
summary: { label: 'Summary', as: 'lead' },
|
|
40
|
+
highlights: { label: 'Highlights', as: 'bullets' },
|
|
41
|
+
riskSignals: { label: 'Risk Signals', as: 'signals' },
|
|
42
|
+
openQuestions: { label: 'Open Questions', as: 'qa' },
|
|
43
|
+
groundingStatus: { label: 'Grounding', as: 'pill' },
|
|
44
|
+
quality: { label: 'Quality', as: 'pill' },
|
|
45
|
+
stepId: { label: 'Step ID', as: 'mono' },
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
<RecordView
|
|
49
|
+
record={record}
|
|
50
|
+
fieldRules={fieldRules}
|
|
51
|
+
contentKeys={['summary', 'highlights', 'riskSignals', 'openQuestions']}
|
|
52
|
+
diagnosticKeys={['groundingStatus', 'quality', 'stepId']}
|
|
53
|
+
toneMap={{ REQUIRE_INFORMATION: 'amber', INSUFFICIENT_EVIDENCE: 'rose', EMPTY_MODEL: 'rose' }}
|
|
54
|
+
/>;
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
See `examples/basic` in this repo for a full working page (header, banner rule, theming) —
|
|
58
|
+
`npm run example` from the repo root.
|
|
59
|
+
|
|
60
|
+
## The toolbox (`FieldKind`)
|
|
61
|
+
|
|
62
|
+
| kind | for |
|
|
63
|
+
|-----------|----------------------------------------------|
|
|
64
|
+
| `lead` | one prose paragraph, the primary read |
|
|
65
|
+
| `bullets` | array of short affirmative statements |
|
|
66
|
+
| `signals` | array of caution/warning statements (auto-classified warn vs. info) |
|
|
67
|
+
| `qa` | array of open questions |
|
|
68
|
+
| `pill` | a single enum-like value (status, quality…) |
|
|
69
|
+
| `mono` | a short technical string (id, hash, code) |
|
|
70
|
+
| `text` | plain secondary prose |
|
|
71
|
+
| `flag` | a boolean |
|
|
72
|
+
| `stat` | a number |
|
|
73
|
+
| `tags` | array of short tokens (not full sentences) |
|
|
74
|
+
| `empty` | null / undefined / `''` — shown, not hidden |
|
|
75
|
+
| `json` | anything else — pretty-printed as a fallback |
|
|
76
|
+
|
|
77
|
+
Every kind is handled in exactly one place (`FieldRenderer`'s switch), in both a
|
|
78
|
+
`content` (full-weight) and `compact` (diagnostics/leftover) visual variant. The
|
|
79
|
+
switch is exhaustive over the `FieldKind` union, so adding a new kind without wiring
|
|
80
|
+
it up in `FieldRenderer` is a **compile error**, not a silently blank field.
|
|
81
|
+
|
|
82
|
+
## Your rules map
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
export const fieldRules: FieldRules = {
|
|
86
|
+
summary: { label: 'Summary', as: 'lead' },
|
|
87
|
+
// ...
|
|
88
|
+
};
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Promoting a field out of the leftover bucket is a one-line addition here plus adding
|
|
92
|
+
its key to `contentKeys` or `diagnosticKeys`.
|
|
93
|
+
|
|
94
|
+
## Leftover handling
|
|
95
|
+
|
|
96
|
+
Any key in `record` that isn't in `fieldRules` (or `identityKeys`) is resolved by
|
|
97
|
+
`inferRule(key, value)`, which looks only at the **value's shape**:
|
|
98
|
+
|
|
99
|
+
- `null` / `undefined` / `''` / `[]` → `empty`
|
|
100
|
+
- `boolean` → `flag`
|
|
101
|
+
- `number` → `stat`
|
|
102
|
+
- `'ALL_CAPS_WITH_UNDERSCORES'` → `pill`
|
|
103
|
+
- long prose / multi-sentence string → `lead`
|
|
104
|
+
- short string with no spaces → `mono`
|
|
105
|
+
- other string → `text`
|
|
106
|
+
- array of questions (`?`-terminated) → `qa`
|
|
107
|
+
- array of sentences → `bullets`
|
|
108
|
+
- array of short tokens → `tags`
|
|
109
|
+
- anything else (nested objects, arrays of objects) → `json`
|
|
110
|
+
|
|
111
|
+
These render inside RecordView's diagnostics panel under "Additional Fields", clearly
|
|
112
|
+
marked as unmapped, so a new field your pipeline starts emitting never disappears —
|
|
113
|
+
it just isn't styled by hand yet.
|
|
114
|
+
|
|
115
|
+
## Theming
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
import { FieldKitProvider } from '@x12i/fieldkit';
|
|
119
|
+
|
|
120
|
+
<FieldKitProvider
|
|
121
|
+
theme={{
|
|
122
|
+
colors: { accent: '#5B9BD5', background: '#0B0F14' },
|
|
123
|
+
fonts: { heading: 'Poppins, sans-serif', body: 'Inter, sans-serif' },
|
|
124
|
+
}}
|
|
125
|
+
>
|
|
126
|
+
<RecordView {...} />
|
|
127
|
+
</FieldKitProvider>;
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Anything you don't override keeps `defaultTheme`. `RecordView` also works with no
|
|
131
|
+
provider at all — it falls back to `defaultTheme` via context.
|
|
132
|
+
|
|
133
|
+
Enum colors are a separate axis from the visual theme: pass `toneMap` (your enum
|
|
134
|
+
value → tone name, e.g. `amber`/`rose`/`green`/`blue`/`violet`/`teal`) as a prop on
|
|
135
|
+
`RecordView`. Anything not in your map still gets a deterministic color via
|
|
136
|
+
`hashTone()` instead of falling back to gray every time.
|
|
137
|
+
|
|
138
|
+
## Composable primitives
|
|
139
|
+
|
|
140
|
+
`RecordView` is a default assembly of the pieces below — reach for them directly if
|
|
141
|
+
you want a different layout:
|
|
142
|
+
|
|
143
|
+
- `resolveField(key, value, rule, options)` — turn one field into a `ResolvedField`.
|
|
144
|
+
- `inferRule(key, value)` — the leftover shape-inference function.
|
|
145
|
+
- `FieldRenderer` — renders one `ResolvedField`.
|
|
146
|
+
- `labelFromKey`, `defaultClassifySignal`, `hashTone`, `toneHex`, `TONE_PALETTE`.
|
|
147
|
+
|
|
148
|
+
## Status
|
|
149
|
+
|
|
150
|
+
`1.0.0` — first cut, ported from a design-canvas prototype. Known gaps to add next:
|
|
151
|
+
a `group` kind for nested objects (currently falls into the `json` catch-all), and a
|
|
152
|
+
richer default `classifySignal` (currently a 3-keyword heuristic — pass your own via
|
|
153
|
+
the `classifySignal` prop until this improves).
|