feat(client): add json-render catalog and registry

Co-authored-by: Ona <no-reply@ona.com>
This commit is contained in:
2026-03-13 23:52:18 +00:00
parent c78ad25f0d
commit e7d0a38e93
5 changed files with 122 additions and 1 deletions

View File

@@ -18,6 +18,7 @@
"@expo-google-fonts/inter": "^0.4.2", "@expo-google-fonts/inter": "^0.4.2",
"@expo-google-fonts/source-serif-4": "^0.4.1", "@expo-google-fonts/source-serif-4": "^0.4.1",
"@expo/vector-icons": "^15.0.3", "@expo/vector-icons": "^15.0.3",
"@json-render/react-native": "^0.13.0",
"@react-navigation/bottom-tabs": "^7.4.0", "@react-navigation/bottom-tabs": "^7.4.0",
"@react-navigation/elements": "^2.6.3", "@react-navigation/elements": "^2.6.3",
"@react-navigation/native": "^7.1.8", "@react-navigation/native": "^7.1.8",
@@ -45,7 +46,8 @@
"react-native-svg": "15.12.1", "react-native-svg": "15.12.1",
"react-native-web": "~0.21.0", "react-native-web": "~0.21.0",
"react-native-worklets": "0.5.1", "react-native-worklets": "0.5.1",
"twrnc": "^4.16.0" "twrnc": "^4.16.0",
"zod": "^4.3.6"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "~19.1.0", "@types/react": "~19.1.0",

View File

@@ -0,0 +1,68 @@
import { defineCatalog } from "@json-render/core"
import { schema } from "@json-render/react-native/schema"
import { z } from "zod"
export const catalog = defineCatalog(schema, {
components: {
View: {
props: z.object({
style: z.string().nullable(),
}),
slots: ["default"],
description:
"Generic layout container. The style prop accepts a twrnc class string (e.g. 'flex-row gap-2 p-4 items-center').",
example: { style: "flex-row gap-2 p-4" },
},
Button: {
props: z.object({
label: z.string(),
leadingIcon: z.string().nullable(),
trailingIcon: z.string().nullable(),
}),
events: ["press"],
slots: [],
description:
"Pressable button with a label and optional Feather icons. Icon values are Feather icon names (e.g. 'plus', 'arrow-right'). Bind on.press to trigger an action.",
example: { label: "Add item", leadingIcon: "plus", trailingIcon: null },
},
FeedCard: {
props: z.object({
style: z.string().nullable(),
}),
slots: ["default"],
description: "Bordered card container for feed content. The style prop accepts a twrnc class string.",
example: { style: "p-4 gap-2" },
},
SansSerifText: {
props: z.object({
text: z.string(),
style: z.string().nullable(),
}),
slots: [],
description:
"Sans-serif text (Inter font). The style prop accepts a twrnc class string for size, weight, color, etc.",
example: { text: "Hello world", style: "text-base font-medium" },
},
SerifText: {
props: z.object({
text: z.string(),
style: z.string().nullable(),
}),
slots: [],
description:
"Serif text (Source Serif 4 font). The style prop accepts a twrnc class string for size, color, etc.",
example: { text: "Heading", style: "text-xl" },
},
MonospaceText: {
props: z.object({
text: z.string(),
style: z.string().nullable(),
}),
slots: [],
description:
"Monospace text (Menlo font). The style prop accepts a twrnc class string for size, color, etc.",
example: { text: "const x = 42", style: "text-sm" },
},
},
actions: {},
})

View File

@@ -0,0 +1,2 @@
export { catalog } from "./catalog"
export { registry } from "./registry"

View File

@@ -0,0 +1,43 @@
import Feather from "@expo/vector-icons/Feather"
import { defineRegistry } from "@json-render/react-native"
import { View } from "react-native"
import tw from "twrnc"
import { Button } from "@/components/ui/button"
import { FeedCard } from "@/components/ui/feed-card"
import { MonospaceText } from "@/components/ui/monospace-text"
import { SansSerifText } from "@/components/ui/sans-serif-text"
import { SerifText } from "@/components/ui/serif-text"
import { catalog } from "./catalog"
function featherIcon(name: string | null | undefined) {
if (!name) return undefined
return <Feather name={name as React.ComponentProps<typeof Feather>["name"]} size={18} color="#e7e5e4" />
}
export const { registry } = defineRegistry(catalog, {
components: {
View: ({ props, children }) => <View style={props.style ? tw`${props.style}` : undefined}>{children}</View>,
Button: ({ props, emit }) => (
<Button
label={props.label}
leadingIcon={featherIcon(props.leadingIcon)}
trailingIcon={featherIcon(props.trailingIcon)}
onPress={() => emit("press")}
/>
),
FeedCard: ({ props, children }) => (
<FeedCard style={props.style ? tw`${props.style}` : undefined}>{children}</FeedCard>
),
SansSerifText: ({ props }) => (
<SansSerifText style={props.style ? tw`${props.style}` : undefined}>{props.text}</SansSerifText>
),
SerifText: ({ props }) => (
<SerifText style={props.style ? tw`${props.style}` : undefined}>{props.text}</SerifText>
),
MonospaceText: ({ props }) => (
<MonospaceText style={props.style ? tw`${props.style}` : undefined}>{props.text}</MonospaceText>
),
},
})

View File

@@ -42,6 +42,7 @@
"@expo-google-fonts/inter": "^0.4.2", "@expo-google-fonts/inter": "^0.4.2",
"@expo-google-fonts/source-serif-4": "^0.4.1", "@expo-google-fonts/source-serif-4": "^0.4.1",
"@expo/vector-icons": "^15.0.3", "@expo/vector-icons": "^15.0.3",
"@json-render/react-native": "^0.13.0",
"@react-navigation/bottom-tabs": "^7.4.0", "@react-navigation/bottom-tabs": "^7.4.0",
"@react-navigation/elements": "^2.6.3", "@react-navigation/elements": "^2.6.3",
"@react-navigation/native": "^7.1.8", "@react-navigation/native": "^7.1.8",
@@ -70,6 +71,7 @@
"react-native-web": "~0.21.0", "react-native-web": "~0.21.0",
"react-native-worklets": "0.5.1", "react-native-worklets": "0.5.1",
"twrnc": "^4.16.0", "twrnc": "^4.16.0",
"zod": "^4.3.6",
}, },
"devDependencies": { "devDependencies": {
"@types/react": "~19.1.0", "@types/react": "~19.1.0",
@@ -657,6 +659,8 @@
"@json-render/core": ["@json-render/core@0.12.1", "", { "dependencies": { "zod": "^4.3.6" } }, "sha512-1tV/481GPHmIRd6lXfWcTaIslQusmDg5lzcSBzWLkSXjF9sjjyOQL090in7uHT4tOMWkdmlEJOW5H9C72PsUEQ=="], "@json-render/core": ["@json-render/core@0.12.1", "", { "dependencies": { "zod": "^4.3.6" } }, "sha512-1tV/481GPHmIRd6lXfWcTaIslQusmDg5lzcSBzWLkSXjF9sjjyOQL090in7uHT4tOMWkdmlEJOW5H9C72PsUEQ=="],
"@json-render/react-native": ["@json-render/react-native@0.13.0", "", { "dependencies": { "@json-render/core": "0.13.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-native": ">=0.71.0", "zod": "^4.0.0" } }, "sha512-uUrK28xPb7LuyYsi9cTnvrnXnVBG0OwU5Up35aaXwcWMLrfqxJ7oWfF97HlDvZIckQtm0VEngAXhHMW97qBEkg=="],
"@mjackson/node-fetch-server": ["@mjackson/node-fetch-server@0.2.0", "", {}, "sha512-EMlH1e30yzmTpGLQjlFmaDAjyOeZhng1/XCd7DExR8PNAnG/G1tyruZxEoUe11ClnwGhGrtsdnyyUx1frSzjng=="], "@mjackson/node-fetch-server": ["@mjackson/node-fetch-server@0.2.0", "", {}, "sha512-EMlH1e30yzmTpGLQjlFmaDAjyOeZhng1/XCd7DExR8PNAnG/G1tyruZxEoUe11ClnwGhGrtsdnyyUx1frSzjng=="],
"@mongodb-js/saslprep": ["@mongodb-js/saslprep@1.4.6", "", { "dependencies": { "sparse-bitfield": "^3.0.3" } }, "sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g=="], "@mongodb-js/saslprep": ["@mongodb-js/saslprep@1.4.6", "", { "dependencies": { "sparse-bitfield": "^3.0.3" } }, "sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g=="],
@@ -3457,6 +3461,8 @@
"@jest/transform/write-file-atomic": ["write-file-atomic@4.0.2", "", { "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" } }, "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg=="], "@jest/transform/write-file-atomic": ["write-file-atomic@4.0.2", "", { "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" } }, "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg=="],
"@json-render/react-native/@json-render/core": ["@json-render/core@0.13.0", "", { "dependencies": { "zod": "^4.3.6" } }, "sha512-CXmCsc8BHDRq45ScVd+qgvjTbwZHPVpVD05WnTqgDxqfY3LGXu5vxaQRSwYoEodg/DGcZq/4HSj4ipVvrzy3qQ=="],
"@mrleebo/prisma-ast/lilconfig": ["lilconfig@2.1.0", "", {}, "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ=="], "@mrleebo/prisma-ast/lilconfig": ["lilconfig@2.1.0", "", {}, "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ=="],
"@oclif/core/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], "@oclif/core/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],