Files
Chris Tate ddae61805e inline mode (#107)
* ai sdk

* chat

* move more to lib

* fixes

* refactor

* fixes

* streamdown

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* data-spec

* fixes

* sortable

* github

* tools

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* tables

* fixes

* fixes

* rename to chat

* fixes docs

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fix lint

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes
2026-02-13 17:53:03 -06:00

517 lines
15 KiB
TypeScript

import { z } from "zod";
// =============================================================================
// Standard Component Definitions for React Native
// =============================================================================
/**
* Standard component definitions for React Native catalogs.
*
* These can be used directly or extended with custom components.
* All components are built using React Native core primitives only.
*/
export const standardComponentDefinitions = {
// ==========================================================================
// Layout Components
// ==========================================================================
Container: {
props: z.object({
padding: z.number().nullable(),
paddingHorizontal: z.number().nullable(),
paddingVertical: z.number().nullable(),
margin: z.number().nullable(),
backgroundColor: z.string().nullable(),
borderRadius: z.number().nullable(),
flex: z.number().nullable(),
}),
slots: ["default"],
description:
"Generic container wrapper. Use for grouping elements with padding, margin, and background color.",
example: { padding: 16, backgroundColor: "#FFFFFF" },
},
Row: {
props: z.object({
gap: z.number().nullable(),
alignItems: z
.enum(["flex-start", "center", "flex-end", "stretch", "baseline"])
.nullable(),
justifyContent: z
.enum([
"flex-start",
"center",
"flex-end",
"space-between",
"space-around",
"space-evenly",
])
.nullable(),
flexWrap: z.enum(["wrap", "nowrap"]).nullable(),
padding: z.number().nullable(),
flex: z.number().nullable(),
}),
slots: ["default"],
description:
"Horizontal flex layout. Use for placing elements side by side.",
example: { gap: 12, alignItems: "center" },
},
Column: {
props: z.object({
gap: z.number().nullable(),
alignItems: z
.enum(["flex-start", "center", "flex-end", "stretch", "baseline"])
.nullable(),
justifyContent: z
.enum([
"flex-start",
"center",
"flex-end",
"space-between",
"space-around",
"space-evenly",
])
.nullable(),
padding: z.number().nullable(),
flex: z.number().nullable(),
}),
slots: ["default"],
description:
"Vertical flex layout. Use for stacking elements top to bottom.",
example: { gap: 12, padding: 16 },
},
ScrollContainer: {
props: z.object({
horizontal: z.boolean().nullable(),
showsScrollIndicator: z.boolean().nullable(),
padding: z.number().nullable(),
backgroundColor: z.string().nullable(),
}),
slots: ["default"],
description:
"Scrollable container. Use for content that may overflow the screen.",
},
SafeArea: {
props: z.object({
backgroundColor: z.string().nullable(),
}),
slots: ["default"],
description:
"Safe area container that respects device notches and system bars. Use as the outermost wrapper for screens.",
},
Spacer: {
props: z.object({
size: z.number().nullable(),
flex: z.number().nullable(),
}),
slots: [],
description:
"Empty space between elements. Set size for fixed spacing or flex for flexible spacing.",
},
Pressable: {
props: z.object({}),
events: ["press", "longPress"],
slots: ["default"],
description:
"Touchable wrapper that triggers events on press. Wrap any element to make it tappable. Bind on.press to setState to update state for visibility-driven UIs like tabs.",
},
Divider: {
props: z.object({
direction: z.enum(["horizontal", "vertical"]).nullable(),
color: z.string().nullable(),
thickness: z.number().nullable(),
margin: z.number().nullable(),
}),
slots: [],
description: "Thin line separator between content sections.",
},
// ==========================================================================
// Content Components
// ==========================================================================
Heading: {
props: z.object({
text: z.string(),
level: z.enum(["h1", "h2", "h3", "h4"]).nullable(),
color: z.string().nullable(),
align: z.enum(["left", "center", "right"]).nullable(),
}),
slots: [],
description:
"Heading text at various levels. h1 is largest, h4 is smallest.",
example: { text: "Welcome", level: "h1" },
},
Paragraph: {
props: z.object({
text: z.string(),
color: z.string().nullable(),
align: z.enum(["left", "center", "right"]).nullable(),
numberOfLines: z.number().nullable(),
fontSize: z.number().nullable(),
}),
slots: [],
description:
"Body text paragraph. Use for descriptions and longer content.",
example: { text: "This is a paragraph of body text." },
},
Label: {
props: z.object({
text: z.string(),
color: z.string().nullable(),
bold: z.boolean().nullable(),
size: z.enum(["xs", "sm", "md"]).nullable(),
}),
slots: [],
description:
"Small utility text. Use for captions, form labels, and secondary information.",
example: { text: "Status", size: "sm" },
},
Image: {
props: z.object({
src: z.string(),
alt: z.string().nullable(),
width: z.number().nullable(),
height: z.number().nullable(),
resizeMode: z.enum(["cover", "contain", "stretch", "center"]).nullable(),
borderRadius: z.number().nullable(),
}),
slots: [],
description: "Image display. Provide a source URL and optional dimensions.",
example: {
src: "https://picsum.photos/300/200",
alt: "Photo",
width: 300,
height: 200,
},
},
Avatar: {
props: z.object({
src: z.string().nullable(),
initials: z.string().nullable(),
size: z.enum(["sm", "md", "lg", "xl"]).nullable(),
backgroundColor: z.string().nullable(),
}),
slots: [],
description:
"Circular avatar showing an image or initials. Use for user profiles and contacts.",
example: { initials: "JD", size: "md" },
},
Badge: {
props: z.object({
label: z.string(),
variant: z
.enum(["default", "info", "success", "warning", "error"])
.nullable(),
}),
slots: [],
description:
"Small colored indicator with a label. Use for status, counts, and categories.",
example: { label: "New", variant: "info" },
},
Chip: {
props: z.object({
label: z.string(),
selected: z.boolean().nullable(),
backgroundColor: z.string().nullable(),
}),
events: ["press", "remove"],
slots: [],
description:
"Tag or filter chip. Bind on.remove for removable chips, on.press for selectable chips.",
},
// ==========================================================================
// Input Components
// ==========================================================================
Button: {
props: z.object({
label: z.string(),
variant: z
.enum(["primary", "secondary", "danger", "outline", "ghost"])
.nullable(),
size: z.enum(["sm", "md", "lg"]).nullable(),
disabled: z.boolean().nullable(),
loading: z.boolean().nullable(),
}),
events: ["press"],
slots: [],
description:
"Pressable button with label. Set variant for styling. Bind on.press for the handler to call on press.",
example: { label: "Submit", variant: "primary" },
},
TextInput: {
props: z.object({
placeholder: z.string().nullable(),
value: z.string().nullable(),
secureTextEntry: z.boolean().nullable(),
keyboardType: z
.enum(["default", "email-address", "numeric", "phone-pad", "url"])
.nullable(),
multiline: z.boolean().nullable(),
numberOfLines: z.number().nullable(),
label: z.string().nullable(),
flex: z.number().nullable(),
}),
events: ["submit", "focus", "blur"],
slots: [],
description:
"Text input field. Use $bindState to bind to the state model for two-way binding. The value typed by the user is stored at the bound path.",
example: {
placeholder: "Enter text...",
label: "Name",
},
},
Switch: {
props: z.object({
checked: z.boolean().nullable(),
label: z.string().nullable(),
disabled: z.boolean().nullable(),
}),
events: ["change"],
slots: [],
description: "Toggle switch. Use $bindState to bind to the state model.",
},
Checkbox: {
props: z.object({
checked: z.boolean().nullable(),
label: z.string().nullable(),
disabled: z.boolean().nullable(),
}),
events: ["change"],
slots: [],
description:
"Checkbox for boolean selections. Use $bindState to bind to the state model.",
},
Slider: {
props: z.object({
min: z.number().nullable(),
max: z.number().nullable(),
step: z.number().nullable(),
value: z.number().nullable(),
label: z.string().nullable(),
color: z.string().nullable(),
}),
slots: [],
description:
"Range slider for numeric values. Use $bindState to bind to the state model.",
},
SearchBar: {
props: z.object({
placeholder: z.string().nullable(),
value: z.string().nullable(),
}),
events: ["submit"],
slots: [],
description:
"Search input with icon. Bind on.submit to trigger search on submit.",
},
// ==========================================================================
// Feedback Components
// ==========================================================================
Spinner: {
props: z.object({
size: z.enum(["small", "large"]).nullable(),
color: z.string().nullable(),
}),
slots: [],
description: "Loading spinner indicator. Use while content is loading.",
},
ProgressBar: {
props: z.object({
progress: z.number(),
color: z.string().nullable(),
trackColor: z.string().nullable(),
height: z.number().nullable(),
}),
slots: [],
description: "Horizontal progress bar. Set progress from 0 to 1.",
},
// ==========================================================================
// Composite Components
// ==========================================================================
Card: {
props: z.object({
title: z.string().nullable(),
subtitle: z.string().nullable(),
padding: z.number().nullable(),
backgroundColor: z.string().nullable(),
borderRadius: z.number().nullable(),
elevated: z.boolean().nullable(),
}),
slots: ["default"],
description:
"Elevated card container with optional title. Use for grouping related content.",
example: { title: "Details", padding: 16, elevated: true },
},
ListItem: {
props: z.object({
title: z.string(),
subtitle: z.string().nullable(),
leading: z.string().nullable(),
trailing: z.string().nullable(),
showChevron: z.boolean().nullable(),
}),
events: ["press"],
slots: [],
description:
"List row with title, subtitle, and optional leading/trailing text. Bind on.press for the press handler.",
example: {
title: "Settings",
subtitle: "Manage your preferences",
showChevron: true,
},
},
Modal: {
props: z.object({
visible: z.boolean(),
title: z.string().nullable(),
animationType: z.enum(["slide", "fade", "none"]).nullable(),
}),
slots: ["default"],
description:
"Modal overlay dialog. Use $bindState on visible to bind visibility to the state model.",
},
};
// =============================================================================
// Standard Action Definitions for React Native
// =============================================================================
/**
* Standard action definitions for React Native catalogs.
*
* These use React Native core APIs (Alert, Share, Linking) and
* user-provided functions (navigate).
*/
export const standardActionDefinitions = {
navigate: {
params: z.object({
screen: z.string(),
params: z.record(z.string(), z.unknown()).nullable(),
}),
description:
"Navigate to a screen. Calls the user-provided navigation function.",
},
goBack: {
params: z.object({}),
description: "Navigate back to the previous screen.",
},
showAlert: {
params: z.object({
title: z.string(),
message: z.string().nullable(),
buttons: z
.array(
z.object({
text: z.string(),
style: z.enum(["default", "cancel", "destructive"]).nullable(),
action: z.string().nullable(),
}),
)
.nullable(),
}),
description: "Show a native alert dialog using Alert.alert().",
},
share: {
params: z.object({
message: z.string(),
url: z.string().nullable(),
title: z.string().nullable(),
}),
description: "Open the native share sheet using Share.share().",
},
openURL: {
params: z.object({
url: z.string(),
}),
description: "Open a URL in the default browser using Linking.openURL().",
},
setState: {
params: z.object({
statePath: z.string(),
value: z.unknown(),
}),
description: "Update a value in the state model at the given statePath.",
},
pushState: {
params: z.object({
statePath: z.string(),
value: z.unknown(),
clearStatePath: z.string().optional(),
}),
description:
'Append an item to an array in the state model. The value can contain { $state: "/statePath" } references to read from current state, and "$id" to auto-generate a unique ID. Use clearStatePath to reset another path after pushing (e.g. clear an input field). Example: { statePath: "/todos", value: { id: "$id", title: { $state: "/newTodoText" }, completed: false }, clearStatePath: "/newTodoText" }.',
},
removeState: {
params: z.object({
statePath: z.string(),
index: z.number(),
}),
description:
"Remove an item from an array in the state model at the given index.",
},
refresh: {
params: z.object({
target: z.string().nullable(),
}),
description:
"Trigger a data refresh. Optionally specify a target to refresh.",
},
};
// =============================================================================
// Types
// =============================================================================
/**
* Type for a component definition
*/
export type ComponentDefinition = {
props: z.ZodType;
slots: string[];
events?: string[];
description: string;
};
/**
* Type for an action definition
*/
export type ActionDefinition = {
params: z.ZodType;
description: string;
};