Skip to main content

ferro_json_ui/
lib.rs

1//! # Ferro JSON-UI
2//!
3//! Stable JSON-based server-driven UI schema types for the Ferro framework.
4//!
5//! This crate defines the typed foundation for JSON-UI: a declarative
6//! component system where the server sends JSON descriptions that are
7//! rendered to HTML. Components, actions, and visibility rules are all
8//! defined as Rust types with serde serialization and JSON Schema generation.
9//!
10//! ## Schema Structure
11//!
12//! A JSON-UI view consists of:
13//! - **Components** - UI elements (Card, Table, Form, Button, etc.)
14//! - **Actions** - Handler references with confirmations and outcomes
15//! - **Visibility** - Conditional rendering based on data conditions
16//! - **View** - Top-level container with layout and title
17//!
18//! ## Example
19//!
20//! ```rust
21//! use ferro_json_ui::{JsonUiView, ComponentNode, Component, CardProps};
22//!
23//! let view = JsonUiView::new()
24//!     .title("Users")
25//!     .component(ComponentNode {
26//!         key: "header".to_string(),
27//!         component: Component::Card(CardProps {
28//!             title: "User Management".to_string(),
29//!             description: None,
30//!             children: vec![],
31//!             max_width: None,
32//!             footer: vec![],
33//!         }),
34//!         action: None,
35//!         visibility: None,
36//!     });
37//!
38//! let json = view.to_json().unwrap();
39//! assert!(json.contains("\"$schema\":\"ferro-json-ui/v1\""));
40//! ```
41
42pub mod action;
43pub mod assets;
44pub mod component;
45pub mod config;
46pub mod data;
47pub mod layout;
48pub mod plugin;
49pub mod plugins;
50pub mod render;
51pub mod resolve;
52pub mod view;
53pub mod visibility;
54
55pub(crate) mod runtime;
56
57pub use action::{Action, ActionOutcome, ConfirmDialog, DialogVariant, HttpMethod, NotifyVariant};
58pub use assets::FERRO_BASE_CSS;
59pub use component::{
60    ActionCardProps, ActionCardVariant, AlertProps, AlertVariant, AvatarProps, BadgeProps,
61    BadgeVariant, BreadcrumbItem, BreadcrumbProps, ButtonGroupProps, ButtonProps, ButtonType,
62    ButtonVariant, CardProps, CheckboxProps, ChecklistItem, ChecklistProps, CollapsibleProps,
63    Column, ColumnFormat, Component, ComponentNode, DataTableProps, DescriptionItem,
64    DescriptionListProps, DetailField, DetailFormProps, DropdownMenuAction, DropdownMenuProps,
65    EditMode, EmptyStateProps, FormMaxWidth, FormProps, FormSectionProps, GapSize, GridProps,
66    HeaderProps, IconPosition, ImageProps, InputProps, InputType, KanbanBoardProps,
67    KanbanColumnProps, KeyValueEditorProps, ModalProps, NotificationDropdownProps,
68    NotificationItem, Orientation, PageHeaderProps, PaginationProps, PluginProps, ProductTileProps,
69    ProgressProps, SelectOption, SelectProps, SeparatorProps, SidebarGroup, SidebarNavItem,
70    SidebarProps, Size, SkeletonProps, SortDirection, StatCardProps, SwitchProps, Tab, TableProps,
71    TabsProps, TextElement, TextProps, ToastProps, ToastVariant,
72};
73pub use config::JsonUiConfig;
74// resolve_path and resolve_path_string are pub(crate) — internal render pipeline helpers
75pub use layout::{
76    register_layout, render_layout, DashboardLayout, DashboardLayoutConfig, Layout, LayoutContext,
77    LayoutRegistry, NavItem, SidebarSection,
78};
79// AppLayout, AuthLayout, DefaultLayout are pub in layout.rs but not user-facing — users select
80// layouts by name string ("dashboard", "app", "auth"), not by struct.
81// navigation, sidebar, footer, global_registry are framework-internal.
82pub use plugin::{
83    collect_plugin_assets, global_plugin_registry, register_plugin, registered_plugin_types,
84    with_plugin, Asset, CollectedAssets, JsonUiPlugin, PluginRegistry,
85};
86pub use plugins::{register_built_in_plugins, MapPlugin};
87pub use render::{render_to_html, render_to_html_with_plugins, RenderResult};
88// collect_plugin_types is pub(crate) — internal render pipeline helper
89pub use resolve::{resolve_actions, resolve_actions_strict, resolve_errors, resolve_errors_all};
90pub use view::{JsonUiView, SCHEMA_VERSION};
91pub use visibility::{Visibility, VisibilityCondition, VisibilityOperator};
92
93#[cfg(feature = "projections")]
94pub mod projection;
95
96#[cfg(feature = "projections")]
97pub use projection::{JsonUiRenderer, RenderMode, VisualContext};
98
99/// Concise reference of all JSON-UI components for AI generation prompts.
100///
101/// Used by ferro-cli (AI view generation) and ferro-mcp (json_ui_generate tool)
102/// as shared context for LLM-based code generation.
103pub const COMPONENT_CATALOG: &str = r#"## Component Catalog
104
105### Text
106Props: content (String), element (h1|h2|h3|span|div|section|p)
107
108### Button
109Props: label (String), variant (default|secondary|destructive|outline|ghost|link), size (xs|sm|default|lg), disabled (Option<bool>), icon (Option<String>), icon_position (Option<left|right>)
110
111### Card
112Props: title (String), description (Option<String>), children (Vec<ComponentNode>), footer (Vec<ComponentNode>)
113
114### Table
115Props: columns (Vec<Column {key, label, format?}>), data_path (String), row_actions (Option<Vec<Action>>), empty_message (Option<String>), sortable (Option<bool>), sort_column (Option<String>), sort_direction (Option<asc|desc>)
116
117### Form
118Props: action (Action), fields (Vec<ComponentNode>), method (Option<GET|POST|PUT|PATCH|DELETE>)
119
120### DetailForm
121Props: mode (EditMode: view|edit), action (Action), fields (Vec<DetailField {label, value, input}>), edit_url (String), cancel_url (String), edit_label (Option<String>, default "Modifica"), save_label (Option<String>, default "Salva"), cancel_label (Option<String>, default "Annulla"), method (Option<HttpMethod>)
122Split-mode detail page with inline edit: View mode renders a <dl> with a "Modifica" link; Edit mode wraps the same <dl> in a <form> with "Salva"/"Annulla" actions. Mode is URL-driven via ?mode=edit (server-side only; no JS). Authoring rule (Option A): when DetailField.input is an Input/Select/Textarea/Checkbox/Switch component, the caller MUST set its label to "" — the <dt> provides the visible label. DetailForm does not mutate caller-supplied props; it emits aria-label on each input derived from the field's label so screen readers retain context.
123
124### Input
125Props: field (String), label (String), input_type (text|email|password|number|textarea|hidden|date|time|url|tel|search), placeholder (Option<String>), required (Option<bool>), disabled (Option<bool>), error (Option<String>), description (Option<String>), default_value (Option<String>), data_path (Option<String>), step (Option<String>)
126
127### Select
128Props: field (String), label (String), options (Vec<SelectOption {value, label}>), placeholder (Option<String>), required (Option<bool>), disabled (Option<bool>), error (Option<String>), description (Option<String>), default_value (Option<String>), data_path (Option<String>)
129
130### Alert
131Props: message (String), variant (info|success|warning|error), title (Option<String>)
132
133### Badge
134Props: label (String), variant (default|secondary|destructive|outline)
135
136### Modal
137Props: title (String), description (Option<String>), children (Vec<ComponentNode>), footer (Vec<ComponentNode>), trigger_label (Option<String>)
138
139### Checkbox
140Props: field (String), label (String), description (Option<String>), checked (Option<bool>), data_path (Option<String>), required (Option<bool>), disabled (Option<bool>), error (Option<String>)
141
142### Switch
143Props: field (String), label (String), description (Option<String>), checked (Option<bool>), data_path (Option<String>), required (Option<bool>), disabled (Option<bool>), error (Option<String>)
144
145### KeyValueEditor
146Props: field (String), label (Option<String>), suggested_keys (Vec<String>), allow_custom_keys (bool, default true), data_path (Option<String> — must resolve to a JSON object), error (Option<String>)
147Serializes to hidden `<input name="{field}" type="hidden" value="{...json...}">`. When `allow_custom_keys` is true, the key input is a text field with a `<datalist>` from `suggested_keys`; when false, the key input is a `<select>` restricted to `suggested_keys`. Runtime syncs the hidden field on every add/delete/input event.
148
149### Separator
150Props: orientation (Option<horizontal|vertical>)
151
152### DescriptionList
153Props: items (Vec<DescriptionItem {label, value, format?}>), columns (Option<u8>)
154
155### Tabs
156Props: default_tab (String), tabs (Vec<Tab {value, label, children}>)
157
158### Breadcrumb
159Props: items (Vec<BreadcrumbItem {label, url?}>)
160
161### Pagination
162Props: current_page (u32), per_page (u32), total (u32), base_url (Option<String>)
163
164### Progress
165Props: value (u8 0-100), max (Option<u8>), label (Option<String>)
166
167### Avatar
168Props: src (Option<String>), alt (String), fallback (Option<String>), size (Option<xs|sm|default|lg>)
169
170### Image
171Props: src (Option<String>) OR svg (Option<String>) — exactly one required; alt (String, required for accessibility); aspect_ratio (Option<String>, e.g. "16/9"); placeholder_label (Option<String>, URL variant only)
172Bounded visual asset rendered into a box. URL variant (src): the attribute is HTML-escaped; placeholder_label drives the skeleton fallback. SVG variant (svg): the string is emitted verbatim, intended for server-constructed SVG (charts, sparklines, icons) — not user input. alt is required on both variants (compile-enforced accessibility).
173
174### Skeleton
175Props: width (Option<String>), height (Option<String>), rounded (Option<bool>)
176
177## Plugin Components
178
179Plugin components use the same JSON syntax as built-in components. Their JS/CSS assets are loaded automatically.
180
181### Map
182Props: center (Option<[f64; 2]>), zoom (u8 0-18, default 13), height (String, default "400px"), fit_bounds (Option<bool>), markers (Vec<{lat, lng, popup?, color?, popup_html?, href?}>), tile_url (Option<String>), attribution (Option<String>), max_zoom (Option<u8>)
183Example JSON: {"type": "Map", "fit_bounds": true, "markers": [{"lat": 51.5, "lng": -0.09, "popup": "Hello"}]}
184Note: Leaflet CSS/JS loaded via CDN automatically. Works inside Tabs/Modals (IntersectionObserver handles resize).
185
186## Action
187Props: handler (String "controller.method" format), method (GET|POST|PUT|PATCH|DELETE), confirm (Option<ConfirmDialog {title, message?, variant: default|danger}>), on_success (Option<ActionOutcome>), on_error (Option<ActionOutcome>)
188Builders: Action::new("handler") (POST), Action::get("handler"), Action::delete("handler"), .confirm("title"), .confirm_danger("title")
189
190## ComponentNode
191Wraps every component: key (String), component (Component variant), action (Option<Action>), visibility (Option<Visibility>)
192
193## JsonUiView Builder
194JsonUiView::new().title("Title").layout("app").data(json).component(node).components(vec_of_nodes)
195"#;