@batono/ui 0.0.3 → 0.0.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/README.md +237 -11
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @batono/ui
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> UI primitives for the Batono server-driven interaction protocol.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@batono/ui)
|
|
6
6
|
[](https://www.typescriptlang.org/)
|
|
@@ -8,54 +8,280 @@
|
|
|
8
8
|
[](https://nodejs.org/)
|
|
9
9
|
[](https://codecov.io/gh/batono/ui)
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
`@batono/ui` provides the UI building blocks for the Batono protocol — layout containers, content definitions, and
|
|
12
|
+
action primitives. It builds on top of `@batono/core` and is designed to be used together with it.
|
|
12
13
|
|
|
13
14
|
---
|
|
14
15
|
|
|
15
16
|
## Features
|
|
16
17
|
|
|
17
|
-
- ✅
|
|
18
|
+
- ✅ Peer dependency on `@batono/core` only
|
|
19
|
+
- ✅ Fully composable layout system (`rows`, `row`, `inline`)
|
|
20
|
+
- ✅ Rich content definitions (`header`, `section`, `field`, `meta`, `note`, `stat`)
|
|
21
|
+
- ✅ Fluent builder API with method chaining
|
|
22
|
+
- ✅ Typed result interfaces for frontend renderers
|
|
23
|
+
- ✅ String shorthand for field values — auto-wrapped in `Text`
|
|
18
24
|
|
|
19
25
|
---
|
|
20
26
|
|
|
21
27
|
## Installation
|
|
22
28
|
|
|
23
29
|
```bash
|
|
24
|
-
npm install @batono/ui
|
|
25
|
-
|
|
30
|
+
npm install @batono/ui @batono/core
|
|
31
|
+
```
|
|
26
32
|
|
|
27
33
|
---
|
|
28
34
|
|
|
29
35
|
## Basic Usage
|
|
30
36
|
|
|
37
|
+
```ts
|
|
38
|
+
import {bt as btCore} from '@batono/core'
|
|
39
|
+
import {bt} from '@batono/ui'
|
|
40
|
+
|
|
41
|
+
const openProfile = btCore.defineAction(
|
|
42
|
+
bt.request('GET', '/customers/42')
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
const graph = btCore.graph(
|
|
46
|
+
bt.rows(
|
|
47
|
+
bt.row(
|
|
48
|
+
bt.header('Batman Forever', {
|
|
49
|
+
avatar: 'BF',
|
|
50
|
+
subtitle: bt.inline(
|
|
51
|
+
bt.link('Bruno Labbadia', openProfile),
|
|
52
|
+
bt.text(' · '),
|
|
53
|
+
bt.text('K2602206207')
|
|
54
|
+
)
|
|
55
|
+
})
|
|
56
|
+
),
|
|
57
|
+
bt.row(
|
|
58
|
+
bt.section('Personal Data', bt.rows(
|
|
59
|
+
bt.row(
|
|
60
|
+
bt.field('First Name', 'Batman'),
|
|
61
|
+
bt.field('Last Name', 'Forever')
|
|
62
|
+
),
|
|
63
|
+
bt.row(
|
|
64
|
+
bt.field('Email', null),
|
|
65
|
+
bt.field('Phone', null)
|
|
66
|
+
)
|
|
67
|
+
))
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
res.json(graph)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Layout
|
|
78
|
+
|
|
79
|
+
### `rows` / `row`
|
|
80
|
+
|
|
81
|
+
The primary layout system. `rows` is a vertical stack of `row` containers. Each `row` distributes its children
|
|
82
|
+
horizontally with equal width.
|
|
31
83
|
|
|
32
84
|
```ts
|
|
33
|
-
|
|
85
|
+
bt.rows(
|
|
86
|
+
bt.row(bt.field('First Name', 'Batman'), bt.field('Last Name', 'Forever')),
|
|
87
|
+
bt.row(bt.field('Email', null))
|
|
88
|
+
)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### `inline`
|
|
34
92
|
|
|
35
|
-
|
|
93
|
+
A horizontal container without grid spacing — for composing inline content like subtitles.
|
|
36
94
|
|
|
95
|
+
```ts
|
|
96
|
+
bt.inline(
|
|
97
|
+
bt.link('Parent', openParent),
|
|
98
|
+
bt.text(' · '),
|
|
99
|
+
bt.text('K2602206207')
|
|
100
|
+
)
|
|
37
101
|
```
|
|
38
102
|
|
|
39
103
|
---
|
|
40
104
|
|
|
41
|
-
|
|
105
|
+
## Definitions
|
|
106
|
+
|
|
107
|
+
### `header`
|
|
108
|
+
|
|
109
|
+
Page-level header with avatar, subtitle and actions.
|
|
42
110
|
|
|
43
111
|
```ts
|
|
44
|
-
|
|
112
|
+
bt.header('Batman Forever', {
|
|
113
|
+
avatar: 'BF',
|
|
114
|
+
subtitle: bt.inline(bt.text('Student')),
|
|
115
|
+
actions: bt.actionButtons(
|
|
116
|
+
bt.action('+ Book Unit', bookUnit, {variant: 'primary'})
|
|
117
|
+
)
|
|
118
|
+
})
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### `section`
|
|
45
122
|
|
|
46
|
-
|
|
123
|
+
A card container with a title, optional icon, actions and a rows layout.
|
|
47
124
|
|
|
125
|
+
```ts
|
|
126
|
+
bt.section('Personal Data', bt.rows(
|
|
127
|
+
bt.row(bt.field('First Name', 'Batman'))
|
|
128
|
+
))
|
|
129
|
+
.withIcon('🧑')
|
|
130
|
+
.withVariant('accent')
|
|
131
|
+
.withActions(bt.actionButtons(
|
|
132
|
+
bt.action('Edit', editAction)
|
|
133
|
+
))
|
|
48
134
|
```
|
|
49
135
|
|
|
136
|
+
### `field`
|
|
137
|
+
|
|
138
|
+
A labeled value. Accepts a string shorthand — automatically wrapped in `Text`.
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
bt.field('First Name', 'Batman')
|
|
142
|
+
bt.field('Newsletter', 'Yes', 'bool-true')
|
|
143
|
+
bt.field('Email', null) // renders as empty
|
|
144
|
+
bt.field('ID', bt.inline(bt.text('NJkR7'), bt.text(' (obfuscated)')))
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### `meta`
|
|
148
|
+
|
|
149
|
+
A compact chip or badge for metadata display.
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
bt.meta('89544').withLabel('ID').withIcon('🆔')
|
|
153
|
+
bt.meta('New Customer').withVariant('badge-green')
|
|
154
|
+
bt.meta('Student').withVariant('badge-accent')
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### `stat`
|
|
158
|
+
|
|
159
|
+
A large metric display for key numbers.
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
bt.stat('Available Units', '99.0')
|
|
163
|
+
bt.stat('Available (HH:mm)', '74:15')
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### `note`
|
|
167
|
+
|
|
168
|
+
A timestamped note with author and optional actions.
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
bt.note(
|
|
172
|
+
'Student mentioned difficulties in analysis.',
|
|
173
|
+
'Maria K.',
|
|
174
|
+
'2026-02-12T11:15:00',
|
|
175
|
+
bt.actionButtons(
|
|
176
|
+
bt.action('✕', deleteNote, {variant: 'ghost'})
|
|
177
|
+
)
|
|
178
|
+
)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### `text` / `link`
|
|
182
|
+
|
|
183
|
+
Inline content primitives.
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
bt.text('Some text')
|
|
187
|
+
bt.link('Open Profile', openProfileAction)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Actions
|
|
193
|
+
|
|
194
|
+
### `request`
|
|
195
|
+
|
|
196
|
+
An HTTP action definition.
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
bt.request('POST', '/bookings')
|
|
200
|
+
bt.request('DELETE', '/users/42').withPayload({id: 42})
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### `modal`
|
|
204
|
+
|
|
205
|
+
A modal dialog action.
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
bt.modal('Are you sure?')
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### `actionButtons`
|
|
212
|
+
|
|
213
|
+
A container for `action` buttons, used in `header` and `section`.
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
bt.actionButtons(
|
|
217
|
+
bt.action('+ Book Unit', bookUnit, {variant: 'primary'}),
|
|
218
|
+
bt.action('+ Note', addNote, {variant: 'secondary'})
|
|
219
|
+
)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Variants
|
|
225
|
+
|
|
226
|
+
`RenderVariant` is used on `field`, `meta`, `section` and `action` to signal visual intent to the renderer.
|
|
227
|
+
|
|
228
|
+
| Variant | Usage |
|
|
229
|
+
|----------------|-----------------------------|
|
|
230
|
+
| `primary` | Primary action button |
|
|
231
|
+
| `secondary` | Secondary action button |
|
|
232
|
+
| `ghost` | Subtle/destructive action |
|
|
233
|
+
| `mono` | Monospace text (IDs, codes) |
|
|
234
|
+
| `bool-true` | Positive boolean value |
|
|
235
|
+
| `bool-false` | Negative boolean value |
|
|
236
|
+
| `badge-green` | Success/positive badge |
|
|
237
|
+
| `badge-accent` | Highlighted badge |
|
|
238
|
+
| `accent` | Highlighted section |
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Frontend Types
|
|
243
|
+
|
|
244
|
+
`@batono/ui` exports typed result interfaces for frontend renderers — no dependency on build-time classes needed.
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
import type {
|
|
248
|
+
HeaderResult,
|
|
249
|
+
SectionResult,
|
|
250
|
+
FieldResult,
|
|
251
|
+
RowsResult,
|
|
252
|
+
Defined
|
|
253
|
+
} from '@batono/ui'
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
All result types extend `Defined` from `@batono/core`:
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
interface Defined {
|
|
260
|
+
$schema: string
|
|
261
|
+
$graph: string
|
|
262
|
+
type: string
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Utilities
|
|
269
|
+
|
|
50
270
|
```ts
|
|
271
|
+
import {NBSP} from '@batono/ui'
|
|
51
272
|
|
|
273
|
+
bt.text(`${NBSP}·${NBSP}`) // non-breaking space separator
|
|
52
274
|
```
|
|
53
275
|
|
|
54
276
|
---
|
|
55
277
|
|
|
56
278
|
## Design Goals
|
|
57
279
|
|
|
58
|
-
|
|
280
|
+
- **Semantic over generic** — `header`, `section`, `field` express intent, not just structure
|
|
281
|
+
- **Fluent API** — method chaining for optional properties keeps constructors clean
|
|
282
|
+
- **Frontend-agnostic** — result interfaces work with any renderer (Vue, React, vanilla)
|
|
283
|
+
- **Composable** — every definition is an `IBuildable`, freely nestable
|
|
284
|
+
- **Renderer-friendly** — `type` discriminator on every node enables exhaustive switch rendering
|
|
59
285
|
|
|
60
286
|
---
|
|
61
287
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@batono/ui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Pascal Pfeifer <pascal@pfeifer.zone>",
|
|
@@ -38,16 +38,16 @@
|
|
|
38
38
|
"prepublishOnly": "npm test",
|
|
39
39
|
"build": "npm run clean && tsc",
|
|
40
40
|
"clean": "rm -rf dist",
|
|
41
|
-
"test": "npm run build && node --test
|
|
42
|
-
"test:coverage": "npm run build && node --test
|
|
43
|
-
"test:coverage:c8": "npm run build && npx c8 node --test
|
|
44
|
-
"test:coverage:lcov": "npm run build && node --enable-source-maps --test
|
|
41
|
+
"test": "npm run build && node --test",
|
|
42
|
+
"test:coverage": "npm run build && node --test --experimental-test-coverage --test-coverage-exclude='test/**'",
|
|
43
|
+
"test:coverage:c8": "npm run build && npx c8 node --test",
|
|
44
|
+
"test:coverage:lcov": "npm run build && node --enable-source-maps --test --experimental-test-coverage --test-coverage-exclude='test/**' --test-reporter=lcov --test-reporter-destination=lcov.info"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@types/node": "^20.17.9",
|
|
48
48
|
"typescript": "^5.9.3"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@batono/core": "^0.0.
|
|
51
|
+
"@batono/core": "^0.0.4"
|
|
52
52
|
}
|
|
53
53
|
}
|