Skip to content

Commit 1d4d2c5

Browse files
committed
feat(json-render): add Tabs, Link, Select to the base catalog
Additive catalog growth (catalog v1: 14 -> 17). Adds zod prop schemas + descriptions for Tabs, Link and Select, ported from hub-ui's existing inline json-render component prop shapes, so specs using them validate at both trust boundaries and render wherever a 17-aware registry is installed. Purely additive: existing specs stay valid. Renderer implementations follow in @devframes/json-render-ui.
1 parent 3a7560f commit 1d4d2c5

6 files changed

Lines changed: 138 additions & 4 deletions

File tree

packages/json-render/src/catalog.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,13 @@ const componentDescriptions: Record<keyof typeof basePropSchemas, string> = {
4343
CodeBlock: 'Preformatted code block with a filename and language label.',
4444
Progress: 'Determinate progress bar.',
4545
Tree: 'Recursive object/array viewer with expandable nodes.',
46+
Tabs: 'Tabbed container; each child renders under the positionally-matching tab.',
47+
Link: 'Hyperlink to a safe-scheme URL with an optional icon.',
48+
Select: 'Single-select dropdown bound to a state value, with optional search.',
4649
}
4750

4851
/**
49-
* The Devframes base catalog (catalog v1): the fourteen canonical components
52+
* The Devframes base catalog (catalog v1): the seventeen canonical components
5053
* with their Devframes-authored Zod prop schemas and descriptions, and an
5154
* empty action set (actions are dispatched dynamically via the bridge, they
5255
* are not declared here). Reference frontend libraries implement this set.

packages/json-render/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,12 @@ export {
2323
DividerPropsSchema,
2424
IconPropsSchema,
2525
KeyValueTablePropsSchema,
26+
LinkPropsSchema,
2627
ProgressPropsSchema,
28+
SelectPropsSchema,
2729
StackPropsSchema,
2830
SwitchPropsSchema,
31+
TabsPropsSchema,
2932
TextInputPropsSchema,
3033
TextPropsSchema,
3134
TreePropsSchema,

packages/json-render/src/prop-schemas.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,48 @@ export const TreePropsSchema = z.object({
124124
defaultExpanded: bool.optional(),
125125
})
126126

127+
export const TabsPropsSchema = z.object({
128+
// `children[i]` renders under `tabs[i]` — the two arrays are positional.
129+
tabs: scalar(z.array(z.looseObject({
130+
value: z.string(),
131+
label: z.string(),
132+
icon: z.string().optional(),
133+
badge: z.string().optional(),
134+
badgeVariant: z.enum(['default', 'info', 'success', 'warning', 'danger']).optional(),
135+
}))).optional(),
136+
value: str.optional(),
137+
defaultValue: str.optional(),
138+
orientation: z.enum(['horizontal', 'vertical']).optional(),
139+
})
140+
141+
export const LinkPropsSchema = z.object({
142+
href: str.optional(),
143+
label: str.optional(),
144+
icon: str.optional(),
145+
external: bool.optional(),
146+
})
147+
148+
export const SelectPropsSchema = z.object({
149+
value: str.optional(),
150+
options: scalar(z.array(z.union([
151+
z.string(),
152+
z.looseObject({
153+
value: z.string(),
154+
label: z.string().optional(),
155+
icon: z.string().optional(),
156+
description: z.string().optional(),
157+
}),
158+
]))).optional(),
159+
placeholder: str.optional(),
160+
label: str.optional(),
161+
disabled: bool.optional(),
162+
searchable: bool.optional(),
163+
})
164+
127165
/**
128166
* Map of base-catalog component name → Zod prop schema. The keys are the
129-
* canonical component set (catalog v1).
167+
* canonical component set (catalog v1: the original fourteen plus the
168+
* additive Tabs/Link/Select).
130169
*/
131170
export const basePropSchemas = {
132171
Stack: StackPropsSchema,
@@ -143,6 +182,9 @@ export const basePropSchemas = {
143182
CodeBlock: CodeBlockPropsSchema,
144183
Progress: ProgressPropsSchema,
145184
Tree: TreePropsSchema,
185+
Tabs: TabsPropsSchema,
186+
Link: LinkPropsSchema,
187+
Select: SelectPropsSchema,
146188
} as const satisfies Record<string, z.ZodType>
147189

148190
/** Canonical base-catalog component name. */

packages/json-render/test/catalog.test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { describe, expect, it } from 'vitest'
22
import { baseCatalog, baseComponentNames, basePropSchemas } from '../src/index'
33

44
describe('base catalog', () => {
5-
it('exposes the fourteen catalog-v1 components', () => {
6-
expect(baseComponentNames).toHaveLength(14)
5+
it('exposes the seventeen catalog-v1 components', () => {
6+
expect(baseComponentNames).toHaveLength(17)
77
expect(baseComponentNames).toEqual([
88
'Stack',
99
'Card',
@@ -19,6 +19,9 @@ describe('base catalog', () => {
1919
'CodeBlock',
2020
'Progress',
2121
'Tree',
22+
'Tabs',
23+
'Link',
24+
'Select',
2225
])
2326
})
2427

tests/__snapshots__/tsnapi/@devframes/json-render/index.snapshot.d.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,46 @@ export declare const basePropSchemas: {
202202
data: z.ZodOptional<z.ZodUnknown>;
203203
defaultExpanded: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
204204
}, z.core.$strip>;
205+
readonly Tabs: z.ZodObject<{
206+
tabs: z.ZodOptional<z.ZodUnion<readonly [z.ZodArray<z.ZodObject<{
207+
value: z.ZodString;
208+
label: z.ZodString;
209+
icon: z.ZodOptional<z.ZodString>;
210+
badge: z.ZodOptional<z.ZodString>;
211+
badgeVariant: z.ZodOptional<z.ZodEnum<{
212+
success: "success";
213+
default: "default";
214+
warning: "warning";
215+
danger: "danger";
216+
info: "info";
217+
}>>;
218+
}, z.core.$loose>>, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
219+
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
220+
defaultValue: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
221+
orientation: z.ZodOptional<z.ZodEnum<{
222+
horizontal: "horizontal";
223+
vertical: "vertical";
224+
}>>;
225+
}, z.core.$strip>;
226+
readonly Link: z.ZodObject<{
227+
href: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
228+
label: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
229+
icon: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
230+
external: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
231+
}, z.core.$strip>;
232+
readonly Select: z.ZodObject<{
233+
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
234+
options: z.ZodOptional<z.ZodUnion<readonly [z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
235+
value: z.ZodString;
236+
label: z.ZodOptional<z.ZodString>;
237+
icon: z.ZodOptional<z.ZodString>;
238+
description: z.ZodOptional<z.ZodString>;
239+
}, z.core.$loose>]>>, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
240+
placeholder: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
241+
label: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
242+
disabled: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
243+
searchable: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
244+
}, z.core.$strip>;
205245
};
206246
export declare const baseSchema: import("@json-render/core").Schema<{
207247
spec: import("@json-render/core").SchemaType<"object", {
@@ -268,11 +308,30 @@ export declare const KeyValueTablePropsSchema: z.ZodObject<{
268308
data: z.ZodOptional<z.ZodUnion<readonly [z.ZodRecord<z.ZodString, z.ZodUnknown>, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
269309
loading: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
270310
}, z.core.$strip>;
311+
export declare const LinkPropsSchema: z.ZodObject<{
312+
href: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
313+
label: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
314+
icon: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
315+
external: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
316+
}, z.core.$strip>;
271317
export declare const ProgressPropsSchema: z.ZodObject<{
272318
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
273319
max: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
274320
label: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
275321
}, z.core.$strip>;
322+
export declare const SelectPropsSchema: z.ZodObject<{
323+
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
324+
options: z.ZodOptional<z.ZodUnion<readonly [z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
325+
value: z.ZodString;
326+
label: z.ZodOptional<z.ZodString>;
327+
icon: z.ZodOptional<z.ZodString>;
328+
description: z.ZodOptional<z.ZodString>;
329+
}, z.core.$loose>]>>, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
330+
placeholder: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
331+
label: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
332+
disabled: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
333+
searchable: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
334+
}, z.core.$strip>;
276335
export declare const StackPropsSchema: z.ZodObject<{
277336
direction: z.ZodOptional<z.ZodEnum<{
278337
row: "row";
@@ -301,6 +360,27 @@ export declare const SwitchPropsSchema: z.ZodObject<{
301360
label: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
302361
disabled: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
303362
}, z.core.$strip>;
363+
export declare const TabsPropsSchema: z.ZodObject<{
364+
tabs: z.ZodOptional<z.ZodUnion<readonly [z.ZodArray<z.ZodObject<{
365+
value: z.ZodString;
366+
label: z.ZodString;
367+
icon: z.ZodOptional<z.ZodString>;
368+
badge: z.ZodOptional<z.ZodString>;
369+
badgeVariant: z.ZodOptional<z.ZodEnum<{
370+
success: "success";
371+
default: "default";
372+
warning: "warning";
373+
danger: "danger";
374+
info: "info";
375+
}>>;
376+
}, z.core.$loose>>, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
377+
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
378+
defaultValue: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
379+
orientation: z.ZodOptional<z.ZodEnum<{
380+
horizontal: "horizontal";
381+
vertical: "vertical";
382+
}>>;
383+
}, z.core.$strip>;
304384
export declare const TextInputPropsSchema: z.ZodObject<{
305385
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
306386
placeholder: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodIntersection<z.ZodObject<{}, z.core.$loose>, z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;

tests/__snapshots__/tsnapi/@devframes/json-render/index.snapshot.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@ export var DividerPropsSchema /* const */
1515
export var IconPropsSchema /* const */
1616
export var JSON_RENDER_INDEX_KEY /* const */
1717
export var KeyValueTablePropsSchema /* const */
18+
export var LinkPropsSchema /* const */
1819
export var ProgressPropsSchema /* const */
20+
export var SelectPropsSchema /* const */
1921
export var StackPropsSchema /* const */
2022
export var SwitchPropsSchema /* const */
23+
export var TabsPropsSchema /* const */
2124
export var TextInputPropsSchema /* const */
2225
export var TextPropsSchema /* const */
2326
export var TreePropsSchema /* const */

0 commit comments

Comments
 (0)