Skip to main content

tauri_plugin_system_components/
models.rs

1use serde::{Deserialize, Serialize};
2
3/// A single tab in the native bottom tab bar.
4#[derive(Debug, Clone, Serialize, Deserialize)]
5#[serde(rename_all = "camelCase")]
6pub struct TabItem {
7    /// Stable identifier reported back in `tabSelected` events.
8    pub id: String,
9    /// User-visible label under the icon.
10    pub title: String,
11    /// SF Symbol name (e.g. "house.fill"). Must exist on the device's OS
12    /// version; unknown names render a label-only item.
13    #[serde(default, skip_serializing_if = "Option::is_none")]
14    pub sf_symbol: Option<String>,
15    /// Bitmap icon as base64 (raw or `data:` URL) — e.g. a user avatar.
16    /// Takes precedence over `sf_symbol`.
17    #[serde(default, skip_serializing_if = "Option::is_none")]
18    pub image: Option<String>,
19    /// Clip the bitmap `image` to a circle (avatar style).
20    #[serde(default, skip_serializing_if = "Option::is_none")]
21    pub circular: Option<bool>,
22    /// Optional badge text (e.g. "3"). `None` shows no badge.
23    #[serde(default, skip_serializing_if = "Option::is_none")]
24    pub badge: Option<String>,
25}
26
27/// A standalone account button floated beside the bar (Apple Music
28/// search-button style). `image` (base64 / data URL) wins over `sf_symbol`.
29#[derive(Debug, Clone, Serialize, Deserialize)]
30#[serde(rename_all = "camelCase")]
31pub struct AccessoryItem {
32    /// Stable id reported back in `tabSelected` events when tapped.
33    pub id: String,
34    #[serde(default, skip_serializing_if = "Option::is_none")]
35    pub sf_symbol: Option<String>,
36    #[serde(default, skip_serializing_if = "Option::is_none")]
37    pub image: Option<String>,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
41#[serde(rename_all = "camelCase")]
42pub struct ConfigureTabBarOptions {
43    pub items: Vec<TabItem>,
44    /// Tab to select initially; defaults to the first item.
45    #[serde(default, skip_serializing_if = "Option::is_none")]
46    pub selected_id: Option<String>,
47    /// Hex accent color `#RRGGBB[AA]` — selected-item color on iOS, glass
48    /// capsule tint + selected segment color on macOS.
49    #[serde(default, skip_serializing_if = "Option::is_none")]
50    pub tint: Option<String>,
51    /// Optional circular account button beside the bar (iOS). Ignored on macOS,
52    /// where the account button is an overlay component instead.
53    #[serde(default, skip_serializing_if = "Option::is_none")]
54    pub accessory: Option<AccessoryItem>,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
58#[serde(rename_all = "camelCase")]
59pub struct SelectTabOptions {
60    pub id: String,
61}
62
63/// An option for a `select` sheet row.
64#[derive(Debug, Clone, Serialize, Deserialize)]
65#[serde(rename_all = "camelCase")]
66pub struct SheetOption {
67    pub value: String,
68    pub label: String,
69}
70
71/// One row in a native sheet (iOS), rendered natively. Tappable rows report
72/// the `id` via `sheetRow`; form rows report via `sheetField` / `sheetSubmit`.
73#[derive(Debug, Clone, Serialize, Deserialize)]
74#[serde(rename_all = "camelCase")]
75pub struct SheetRow {
76    pub id: String,
77    /// `header | action | text | textfield | toggle | datetime | select | submit`.
78    /// Defaults to `action` (or `header` when `header == true`).
79    #[serde(default, skip_serializing_if = "Option::is_none")]
80    pub kind: Option<String>,
81    pub label: String,
82    #[serde(default, skip_serializing_if = "Option::is_none")]
83    pub detail: Option<String>,
84    /// Bitmap (base64 / data URL) — wins over `sf_symbol`.
85    #[serde(default, skip_serializing_if = "Option::is_none")]
86    pub image: Option<String>,
87    #[serde(default, skip_serializing_if = "Option::is_none")]
88    pub sf_symbol: Option<String>,
89    #[serde(default, skip_serializing_if = "Option::is_none")]
90    pub badge: Option<String>,
91    #[serde(default, skip_serializing_if = "Option::is_none")]
92    pub destructive: Option<bool>,
93    /// Back-compat: same as `kind: "header"`.
94    #[serde(default, skip_serializing_if = "Option::is_none")]
95    pub header: Option<bool>,
96    /// Initial value: `textfield` text / `datetime` ISO-8601 / `select` value.
97    #[serde(default, skip_serializing_if = "Option::is_none")]
98    pub value: Option<String>,
99    /// Initial `toggle` state.
100    #[serde(default, skip_serializing_if = "Option::is_none")]
101    pub on: Option<bool>,
102    #[serde(default, skip_serializing_if = "Option::is_none")]
103    pub placeholder: Option<String>,
104    /// `select` options.
105    #[serde(default, skip_serializing_if = "Option::is_none")]
106    pub options: Option<Vec<SheetOption>>,
107}
108
109/// Present a native Liquid Glass bottom sheet (iOS) with natively-rendered rows.
110#[derive(Debug, Clone, Serialize, Deserialize)]
111#[serde(rename_all = "camelCase")]
112pub struct PresentSheetOptions {
113    /// Stable id reported in `sheetRow` / `sheetDismissed` events.
114    pub id: String,
115    /// Hex accent `#RRGGBB[AA]` for badges / selection.
116    #[serde(default, skip_serializing_if = "Option::is_none")]
117    pub tint: Option<String>,
118    pub rows: Vec<SheetRow>,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
122#[serde(rename_all = "camelCase")]
123pub struct DismissSheetOptions {
124    pub id: String,
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
128#[serde(rename_all = "camelCase")]
129pub struct SetBadgeOptions {
130    pub id: String,
131    /// `None` clears the badge.
132    #[serde(default, skip_serializing_if = "Option::is_none")]
133    pub value: Option<String>,
134}
135
136/// Space the web content should reserve so the floating bar doesn't cover it.
137#[derive(Debug, Clone, Serialize, Deserialize)]
138#[serde(rename_all = "camelCase")]
139pub struct TabBarInsets {
140    /// Bar height + bottom safe area, in CSS points.
141    pub bottom: f64,
142}
143
144#[derive(Debug, Clone, Default, Serialize, Deserialize)]
145#[serde(rename_all = "camelCase")]
146pub struct WindowGlassOptions {
147    #[serde(default, skip_serializing_if = "Option::is_none")]
148    pub corner_radius: Option<f64>,
149    /// Hex color, `#RRGGBB` or `#RRGGBBAA`.
150    #[serde(default, skip_serializing_if = "Option::is_none")]
151    pub tint_color: Option<String>,
152}
153
154/// Payload of the `system-components://tab-selected` event emitted on macOS
155/// (iOS delivers the same shape through the plugin event channel).
156#[derive(Debug, Clone, Serialize, Deserialize)]
157#[serde(rename_all = "camelCase")]
158pub struct TabSelectedPayload {
159    pub id: String,
160}
161
162/// Kind of native overlay component.
163#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
164#[serde(rename_all = "camelCase")]
165pub enum ComponentKind {
166    /// UISwitch / NSSwitch — emits `change` with `on`.
167    Switch,
168    /// UIButton (glass configuration on iOS 26) / NSButton — emits `click`.
169    Button,
170    /// UISlider / NSSlider — emits `change` with `value`.
171    Slider,
172    /// UIProgressView / NSProgressIndicator — display only.
173    Progress,
174    /// UIImageView / NSImageView — display only (avatars, thumbnails).
175    Image,
176    /// A bare glass panel (UIGlassEffect / NSGlassEffectView, blur
177    /// fallback) — pair with `below: true` + `absolute` placement to back
178    /// DOM elements with real glass (see `attachGlassCard` in the JS API).
179    Glass,
180    /// A layout container (UIStackView / NSStackView) that arranges its
181    /// `children` along `props.axis` — the building block consumers compose
182    /// their own nav (bar or sidebar) from. Imposes no specific layout.
183    Container,
184    /// The system tab bar (UITabBar / NSSegmentedControl) as a composable
185    /// component: `props.items` are the tabs; selection arrives as a
186    /// `select` component event whose `detail` is the chosen tab id.
187    TabBar,
188}
189
190/// Where a component is pinned, relative to the window/safe area.
191#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
192#[serde(rename_all = "camelCase")]
193pub enum ComponentAnchor {
194    TopLeading,
195    #[default]
196    TopTrailing,
197    BottomLeading,
198    BottomTrailing,
199    Center,
200    /// Centered against one edge of the safe area — for docking a nav
201    /// container as a bottom bar (`Bottom`) or a side rail (`Leading` /
202    /// `Trailing`). `props.inset` sets the gap from that edge.
203    Bottom,
204    Top,
205    Leading,
206    Trailing,
207    /// Position by `props.x`/`props.y` (CSS points from the top-left) —
208    /// for views synced to DOM element rects.
209    Absolute,
210    /// Cover the whole window, resizing with it (e.g. background images).
211    Fill,
212}
213
214/// Per-kind properties. All optional; irrelevant fields are ignored.
215#[derive(Debug, Clone, Default, Serialize, Deserialize)]
216#[serde(rename_all = "camelCase", default)]
217pub struct ComponentProps {
218    /// Text: button title, switch/slider accessibility label.
219    pub label: Option<String>,
220    /// Switch state.
221    pub on: Option<bool>,
222    /// Slider/progress value (progress is 0..1).
223    pub value: Option<f64>,
224    pub min: Option<f64>,
225    pub max: Option<f64>,
226    /// SF Symbol for buttons.
227    pub sf_symbol: Option<String>,
228    /// Bitmap as base64 (raw or `data:` URL) — image components, button icons.
229    pub image: Option<String>,
230    /// Clip the bitmap to a circle (avatar style).
231    pub circular: Option<bool>,
232    /// Wrap the control in a floating glass capsule.
233    pub glass: Option<bool>,
234    /// Prominent (tinted) button style.
235    pub prominent: Option<bool>,
236    /// Hex tint color `#RRGGBB[AA]`.
237    pub tint: Option<String>,
238    /// Explicit size in points (defaults to the control's natural size).
239    pub width: Option<f64>,
240    pub height: Option<f64>,
241    /// Top-left position in CSS points, for `absolute` placement. Also
242    /// accepted by `update_component` to move/resize (DOM scroll sync).
243    pub x: Option<f64>,
244    pub y: Option<f64>,
245    /// Corner radius for `glass` panels.
246    pub corner_radius: Option<f64>,
247
248    // ── `container` layout ───────────────────────────────────────────────
249    /// `horizontal` (a bar) or `vertical` (a sidebar). Default `horizontal`.
250    pub axis: Option<String>,
251    /// Cross-axis alignment of children: `center` (default) | `leading` |
252    /// `trailing` | `fill`.
253    pub align: Option<String>,
254    /// Gap between children, in points.
255    pub spacing: Option<f64>,
256    /// Gap from the anchored edge of the safe area, in points.
257    pub inset: Option<f64>,
258
259    // ── `tabBar` ─────────────────────────────────────────────────────────
260    /// The tabs, when `kind == tabBar`.
261    pub items: Option<Vec<TabItem>>,
262    /// Initially-selected tab id (defaults to the first).
263    pub selected_id: Option<String>,
264}
265
266#[derive(Debug, Clone, Serialize, Deserialize)]
267#[serde(rename_all = "camelCase")]
268pub struct CreateComponentOptions {
269    /// Stable identifier, reported back in component events.
270    pub id: String,
271    pub kind: ComponentKind,
272    #[serde(default)]
273    pub props: ComponentProps,
274    #[serde(default)]
275    pub anchor: ComponentAnchor,
276    /// Offset from the anchor, in points (dx grows inward/right, dy inward/down).
277    #[serde(default)]
278    pub dx: f64,
279    #[serde(default)]
280    pub dy: f64,
281    /// Insert the view *below* the webview instead of above it. The webview
282    /// is made transparent so unpainted DOM regions reveal the view — this
283    /// is how glass panels sit behind DOM content (text stays sharp on
284    /// top while the glass refracts what's behind the page).
285    #[serde(default)]
286    pub below: bool,
287    /// Child components, when `kind == container` — arranged along
288    /// `props.axis`. Each child is a full component spec (so containers may
289    /// nest, and a `tabBar` or `button` can live inside).
290    #[serde(default, skip_serializing_if = "Option::is_none")]
291    pub children: Option<Vec<CreateComponentOptions>>,
292}
293
294#[derive(Debug, Clone, Serialize, Deserialize)]
295#[serde(rename_all = "camelCase")]
296pub struct UpdateComponentOptions {
297    pub id: String,
298    pub props: ComponentProps,
299}
300
301/// Batched form of [`UpdateComponentOptions`] — one IPC round trip and one
302/// (animation-disabled) native transaction for all geometry updates of a
303/// frame. Used by the DOM scroll-sync path.
304#[derive(Debug, Clone, Serialize, Deserialize)]
305#[serde(rename_all = "camelCase")]
306pub struct UpdateComponentsOptions {
307    pub components: Vec<UpdateComponentOptions>,
308}
309
310#[derive(Debug, Clone, Serialize, Deserialize)]
311#[serde(rename_all = "camelCase")]
312pub struct RemoveComponentOptions {
313    pub id: String,
314}
315
316/// Payload of the `system-components://component-event` event on macOS (iOS
317/// delivers the same shape through the plugin event channel).
318#[derive(Debug, Clone, Serialize, Deserialize)]
319#[serde(rename_all = "camelCase")]
320pub struct ComponentEventPayload {
321    pub id: String,
322    /// `click` (button), `change` (switch/slider), or `select` (tab bar).
323    pub event: String,
324    #[serde(default, skip_serializing_if = "Option::is_none")]
325    pub on: Option<bool>,
326    #[serde(default, skip_serializing_if = "Option::is_none")]
327    pub value: Option<f64>,
328    /// String payload — the chosen tab id for a `tabBar` `select` event.
329    #[serde(default, skip_serializing_if = "Option::is_none")]
330    pub detail: Option<String>,
331}
332
333#[derive(Debug, Clone, Serialize, Deserialize)]
334#[serde(rename_all = "camelCase")]
335pub struct GlassSupport {
336    /// `true` when the real `NSGlassEffectView` (macOS 26+) is available.
337    pub supported: bool,
338    /// `true` when `set_window_glass` would use the `NSVisualEffectView`
339    /// blur fallback instead of real glass.
340    pub fallback: bool,
341}