From 1d4d2c52011069db5d65c7d162bea8451fc5d1d6 Mon Sep 17 00:00:00 2001 From: "Anthony Fu (via agent)" Date: Mon, 10 Aug 2026 04:02:03 +0000 Subject: [PATCH] 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. --- packages/json-render/src/catalog.ts | 5 +- packages/json-render/src/index.ts | 3 + packages/json-render/src/prop-schemas.ts | 44 +++++++++- packages/json-render/test/catalog.test.ts | 7 +- .../json-render/index.snapshot.d.ts | 80 +++++++++++++++++++ .../@devframes/json-render/index.snapshot.js | 3 + 6 files changed, 138 insertions(+), 4 deletions(-) diff --git a/packages/json-render/src/catalog.ts b/packages/json-render/src/catalog.ts index 55990ee1..d8f280c9 100644 --- a/packages/json-render/src/catalog.ts +++ b/packages/json-render/src/catalog.ts @@ -43,10 +43,13 @@ const componentDescriptions: Record = { CodeBlock: 'Preformatted code block with a filename and language label.', Progress: 'Determinate progress bar.', Tree: 'Recursive object/array viewer with expandable nodes.', + Tabs: 'Tabbed container; each child renders under the positionally-matching tab.', + Link: 'Hyperlink to a safe-scheme URL with an optional icon.', + Select: 'Single-select dropdown bound to a state value, with optional search.', } /** - * The Devframes base catalog (catalog v1): the fourteen canonical components + * The Devframes base catalog (catalog v1): the seventeen canonical components * with their Devframes-authored Zod prop schemas and descriptions, and an * empty action set (actions are dispatched dynamically via the bridge, they * are not declared here). Reference frontend libraries implement this set. diff --git a/packages/json-render/src/index.ts b/packages/json-render/src/index.ts index 862d95bf..14472337 100644 --- a/packages/json-render/src/index.ts +++ b/packages/json-render/src/index.ts @@ -23,9 +23,12 @@ export { DividerPropsSchema, IconPropsSchema, KeyValueTablePropsSchema, + LinkPropsSchema, ProgressPropsSchema, + SelectPropsSchema, StackPropsSchema, SwitchPropsSchema, + TabsPropsSchema, TextInputPropsSchema, TextPropsSchema, TreePropsSchema, diff --git a/packages/json-render/src/prop-schemas.ts b/packages/json-render/src/prop-schemas.ts index 5ec99b60..8fd4a26f 100644 --- a/packages/json-render/src/prop-schemas.ts +++ b/packages/json-render/src/prop-schemas.ts @@ -124,9 +124,48 @@ export const TreePropsSchema = z.object({ defaultExpanded: bool.optional(), }) +export const TabsPropsSchema = z.object({ + // `children[i]` renders under `tabs[i]` — the two arrays are positional. + tabs: scalar(z.array(z.looseObject({ + value: z.string(), + label: z.string(), + icon: z.string().optional(), + badge: z.string().optional(), + badgeVariant: z.enum(['default', 'info', 'success', 'warning', 'danger']).optional(), + }))).optional(), + value: str.optional(), + defaultValue: str.optional(), + orientation: z.enum(['horizontal', 'vertical']).optional(), +}) + +export const LinkPropsSchema = z.object({ + href: str.optional(), + label: str.optional(), + icon: str.optional(), + external: bool.optional(), +}) + +export const SelectPropsSchema = z.object({ + value: str.optional(), + options: scalar(z.array(z.union([ + z.string(), + z.looseObject({ + value: z.string(), + label: z.string().optional(), + icon: z.string().optional(), + description: z.string().optional(), + }), + ]))).optional(), + placeholder: str.optional(), + label: str.optional(), + disabled: bool.optional(), + searchable: bool.optional(), +}) + /** * Map of base-catalog component name → Zod prop schema. The keys are the - * canonical component set (catalog v1). + * canonical component set (catalog v1: the original fourteen plus the + * additive Tabs/Link/Select). */ export const basePropSchemas = { Stack: StackPropsSchema, @@ -143,6 +182,9 @@ export const basePropSchemas = { CodeBlock: CodeBlockPropsSchema, Progress: ProgressPropsSchema, Tree: TreePropsSchema, + Tabs: TabsPropsSchema, + Link: LinkPropsSchema, + Select: SelectPropsSchema, } as const satisfies Record /** Canonical base-catalog component name. */ diff --git a/packages/json-render/test/catalog.test.ts b/packages/json-render/test/catalog.test.ts index 6329df75..b487d5a0 100644 --- a/packages/json-render/test/catalog.test.ts +++ b/packages/json-render/test/catalog.test.ts @@ -2,8 +2,8 @@ import { describe, expect, it } from 'vitest' import { baseCatalog, baseComponentNames, basePropSchemas } from '../src/index' describe('base catalog', () => { - it('exposes the fourteen catalog-v1 components', () => { - expect(baseComponentNames).toHaveLength(14) + it('exposes the seventeen catalog-v1 components', () => { + expect(baseComponentNames).toHaveLength(17) expect(baseComponentNames).toEqual([ 'Stack', 'Card', @@ -19,6 +19,9 @@ describe('base catalog', () => { 'CodeBlock', 'Progress', 'Tree', + 'Tabs', + 'Link', + 'Select', ]) }) diff --git a/tests/__snapshots__/tsnapi/@devframes/json-render/index.snapshot.d.ts b/tests/__snapshots__/tsnapi/@devframes/json-render/index.snapshot.d.ts index ecc70f51..ec8e12bc 100644 --- a/tests/__snapshots__/tsnapi/@devframes/json-render/index.snapshot.d.ts +++ b/tests/__snapshots__/tsnapi/@devframes/json-render/index.snapshot.d.ts @@ -202,6 +202,46 @@ export declare const basePropSchemas: { data: z.ZodOptional; defaultExpanded: z.ZodOptional, z.ZodRecord>]>>; }, z.core.$strip>; + readonly Tabs: z.ZodObject<{ + tabs: z.ZodOptional; + badge: z.ZodOptional; + badgeVariant: z.ZodOptional>; + }, z.core.$loose>>, z.ZodIntersection, z.ZodRecord>]>>; + value: z.ZodOptional, z.ZodRecord>]>>; + defaultValue: z.ZodOptional, z.ZodRecord>]>>; + orientation: z.ZodOptional>; + }, z.core.$strip>; + readonly Link: z.ZodObject<{ + href: z.ZodOptional, z.ZodRecord>]>>; + label: z.ZodOptional, z.ZodRecord>]>>; + icon: z.ZodOptional, z.ZodRecord>]>>; + external: z.ZodOptional, z.ZodRecord>]>>; + }, z.core.$strip>; + readonly Select: z.ZodObject<{ + value: z.ZodOptional, z.ZodRecord>]>>; + options: z.ZodOptional; + icon: z.ZodOptional; + description: z.ZodOptional; + }, z.core.$loose>]>>, z.ZodIntersection, z.ZodRecord>]>>; + placeholder: z.ZodOptional, z.ZodRecord>]>>; + label: z.ZodOptional, z.ZodRecord>]>>; + disabled: z.ZodOptional, z.ZodRecord>]>>; + searchable: z.ZodOptional, z.ZodRecord>]>>; + }, z.core.$strip>; }; export declare const baseSchema: import("@json-render/core").Schema<{ spec: import("@json-render/core").SchemaType<"object", { @@ -268,11 +308,30 @@ export declare const KeyValueTablePropsSchema: z.ZodObject<{ data: z.ZodOptional, z.ZodIntersection, z.ZodRecord>]>>; loading: z.ZodOptional, z.ZodRecord>]>>; }, z.core.$strip>; +export declare const LinkPropsSchema: z.ZodObject<{ + href: z.ZodOptional, z.ZodRecord>]>>; + label: z.ZodOptional, z.ZodRecord>]>>; + icon: z.ZodOptional, z.ZodRecord>]>>; + external: z.ZodOptional, z.ZodRecord>]>>; +}, z.core.$strip>; export declare const ProgressPropsSchema: z.ZodObject<{ value: z.ZodOptional, z.ZodRecord>]>>; max: z.ZodOptional, z.ZodRecord>]>>; label: z.ZodOptional, z.ZodRecord>]>>; }, z.core.$strip>; +export declare const SelectPropsSchema: z.ZodObject<{ + value: z.ZodOptional, z.ZodRecord>]>>; + options: z.ZodOptional; + icon: z.ZodOptional; + description: z.ZodOptional; + }, z.core.$loose>]>>, z.ZodIntersection, z.ZodRecord>]>>; + placeholder: z.ZodOptional, z.ZodRecord>]>>; + label: z.ZodOptional, z.ZodRecord>]>>; + disabled: z.ZodOptional, z.ZodRecord>]>>; + searchable: z.ZodOptional, z.ZodRecord>]>>; +}, z.core.$strip>; export declare const StackPropsSchema: z.ZodObject<{ direction: z.ZodOptional, z.ZodRecord>]>>; disabled: z.ZodOptional, z.ZodRecord>]>>; }, z.core.$strip>; +export declare const TabsPropsSchema: z.ZodObject<{ + tabs: z.ZodOptional; + badge: z.ZodOptional; + badgeVariant: z.ZodOptional>; + }, z.core.$loose>>, z.ZodIntersection, z.ZodRecord>]>>; + value: z.ZodOptional, z.ZodRecord>]>>; + defaultValue: z.ZodOptional, z.ZodRecord>]>>; + orientation: z.ZodOptional>; +}, z.core.$strip>; export declare const TextInputPropsSchema: z.ZodObject<{ value: z.ZodOptional, z.ZodRecord>]>>; placeholder: z.ZodOptional, z.ZodRecord>]>>; diff --git a/tests/__snapshots__/tsnapi/@devframes/json-render/index.snapshot.js b/tests/__snapshots__/tsnapi/@devframes/json-render/index.snapshot.js index 5a6ae454..e4943934 100644 --- a/tests/__snapshots__/tsnapi/@devframes/json-render/index.snapshot.js +++ b/tests/__snapshots__/tsnapi/@devframes/json-render/index.snapshot.js @@ -15,9 +15,12 @@ export var DividerPropsSchema /* const */ export var IconPropsSchema /* const */ export var JSON_RENDER_INDEX_KEY /* const */ export var KeyValueTablePropsSchema /* const */ +export var LinkPropsSchema /* const */ export var ProgressPropsSchema /* const */ +export var SelectPropsSchema /* const */ export var StackPropsSchema /* const */ export var SwitchPropsSchema /* const */ +export var TabsPropsSchema /* const */ export var TextInputPropsSchema /* const */ export var TextPropsSchema /* const */ export var TreePropsSchema /* const */