Files
dx-admin-template/src/views/components.rs
2025-11-13 10:55:37 +08:00

1248 lines
70 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use crate::components::ui::{
Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertVariant, AspectRatio,
Avatar, Badge, BadgeVariant, Breadcrumb, Button, ButtonSize, ButtonVariant, Calendar, Card,
CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, Collapsible,
CollapsibleContent, CollapsibleTrigger, Combobox, ComboboxOption, CommandItem, CommandPalette,
ContextItem, ContextMenu, Crumb, DateRange, DateRangePicker, Dialog, DropdownMenu,
DropdownMenuItem, FileDropZone, FileMetadata, FormField, FormMessage, FormMessageVariant,
HoverCard, Input, Label, Menubar, MenubarItem, MenubarMenu, NavigationItem, NavigationMenu,
Pagination, Popover, Progress, RadioGroup, RadioGroupItem, ScrollArea, Select, SelectOption,
Separator, SeparatorOrientation, Sheet, SheetSide, Sidebar, SidebarContent, SidebarFooter,
SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInset,
SidebarLayout, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarSeparator,
SidebarTrigger, Skeleton, Slider, StepItem, Steps, Switch, Table, TableBody, TableCaption,
TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList,
TabsTrigger, Textarea, Toast, ToastViewport, Toggle, ToggleGroup, ToggleGroupItem,
ToggleGroupMode, ToggleGroupOrientation, Tooltip,
};
use crate::time::NaiveDate;
use dioxus::html::events::FormEvent;
use dioxus::prelude::*;
#[component]
pub fn Components() -> Element {
rsx! {
div {
class: "component-page",
div {
class: "page-heading",
h1 { "Component library" }
p { "Browse every primitive wired into this starter so new screens stay consistent." }
}
UiShowcase {}
}
}
}
#[component]
fn UiShowcase() -> Element {
let accepted_terms = use_signal(|| false);
let email_notifications = use_signal(|| true);
let slider_value = use_signal(|| 42.0f32);
let contact_method = use_signal(|| "email".to_string());
let newsletter_opt_in = use_signal(|| true);
let dark_mode = use_signal(|| false);
let theme_choice = use_signal(|| Some("system".to_string()));
let menu_selection = use_signal(|| "Select a menu action".to_string());
let menubar_selection = use_signal(|| "Choose a menu item".to_string());
let pagination_current = use_signal(|| 3usize);
let steps_current = use_signal(|| 2usize);
let command_selection = use_signal(|| "Nothing selected yet".to_string());
let context_selection = use_signal(|| "Right click the area to choose an action".to_string());
let dialog_open = use_signal(|| false);
let sheet_open = use_signal(|| false);
let toast_open = use_signal(|| false);
let sidebar_collapsed = use_signal(|| false);
let sidebar_active = use_signal(|| "analytics".to_string());
let profile_name = use_signal(|| "".to_string());
let name_error =
use_signal(|| Some("Use at least 3 characters to stay descriptive.".to_string()));
let combobox_selection = use_signal(|| Some("analytics".to_string()));
let toggle_active = use_signal(|| true);
let calendar_selection =
use_signal(|| Some(NaiveDate::from_ymd_opt(2024, 6, 11).expect("valid date")));
let collapsible_open = use_signal(|| false);
let toggle_group_values = use_signal(|| vec!["daily".to_string()]);
let date_range_value = use_signal(|| {
Some(DateRange::new(
NaiveDate::from_ymd_opt(2024, 6, 1).expect("valid start"),
NaiveDate::from_ymd_opt(2024, 6, 7).expect("valid end"),
))
});
let dropzone_files = use_signal(|| Vec::<FileMetadata>::new());
let slider_value_signal = slider_value.clone();
let slider_value_setter = slider_value.clone();
let contact_method_signal = contact_method.clone();
let theme_choice_signal = theme_choice.clone();
let accepted_terms_setter = accepted_terms.clone();
let email_notifications_setter = email_notifications.clone();
let contact_method_setter = contact_method.clone();
let newsletter_opt_in_setter = newsletter_opt_in.clone();
let dark_mode_setter = dark_mode.clone();
let theme_choice_setter = theme_choice.clone();
let menu_selection_setter = menu_selection.clone();
let menubar_selection_setter = menubar_selection.clone();
let pagination_setter = pagination_current.clone();
let steps_setter = steps_current.clone();
let command_selection_setter = command_selection.clone();
let context_selection_setter = context_selection.clone();
let dialog_signal = dialog_open.clone();
let sheet_signal = sheet_open.clone();
let toast_signal = toast_open.clone();
let sidebar_collapsed_setter = sidebar_collapsed.clone();
let sidebar_active_setter = sidebar_active.clone();
let profile_name_signal = profile_name.clone();
let profile_name_setter = profile_name.clone();
let name_error_signal = name_error.clone();
let name_error_setter = name_error.clone();
let combobox_selection_signal = combobox_selection.clone();
let combobox_selection_setter = combobox_selection.clone();
let toggle_active_signal = toggle_active.clone();
let toggle_active_setter = toggle_active.clone();
let calendar_selection_signal = calendar_selection.clone();
let calendar_selection_setter = calendar_selection.clone();
let collapsible_signal = collapsible_open.clone();
let collapsible_setter = collapsible_open.clone();
let toggle_group_signal = toggle_group_values.clone();
let toggle_group_setter = toggle_group_values.clone();
let date_range_signal = date_range_value.clone();
let date_range_setter = date_range_value.clone();
let dropzone_files_signal = dropzone_files.clone();
let dropzone_files_setter = dropzone_files.clone();
let intensity_text = move || format!("Accent intensity: {:.0}%", slider_value_signal());
let contact_text = move || format!("Preferred contact: {}", contact_method_signal());
let profile_preview = move || {
let value = profile_name_signal();
if value.trim().is_empty() {
"Name is currently empty".to_string()
} else {
format!("Display name preview: {}", value.trim())
}
};
let combobox_summary = move || {
if let Some(value) = combobox_selection_signal() {
format!("Project owner: {value}")
} else {
"Assign a project owner to sync permissions.".to_string()
}
};
let calendar_summary = move || {
if let Some(date) = calendar_selection_signal() {
format!("Next milestone: {}", date.format("%b %d, %Y"))
} else {
"Pick a date to keep the timeline on track.".to_string()
}
};
let toggle_summary = move || {
if toggle_active_signal() {
"Emails are enabled for this workflow.".to_string()
} else {
"Emails are paused until you re-enable them.".to_string()
}
};
let toggle_group_summary = move || {
let values = toggle_group_signal();
if values.is_empty() {
"No frequencies selected.".to_string()
} else {
format!("Cadence: {}", values.join(", "))
}
};
let range_preview = move || match date_range_signal() {
Some(range) if range.start != range.end => format!(
"Range: {}{}",
range.start.format("%b %d"),
range.end.format("%b %d %Y")
),
Some(range) => format!("Single day: {}", range.start.format("%b %d, %Y")),
None => "Pick a date window to compare analytics.".to_string(),
};
let dropzone_summary = move || {
let files = dropzone_files_signal();
if files.is_empty() {
"No assets queued.".to_string()
} else {
format!("{} file(s) staged.", files.len())
}
};
let select_options = vec![
SelectOption::new("System", "system"),
SelectOption::new("Light", "light"),
SelectOption::new("Dark", "dark"),
];
let calendar_month = NaiveDate::from_ymd_opt(2024, 6, 1).expect("valid date");
let combobox_options = vec![
ComboboxOption::new("Analytics", "analytics")
.with_description("Dashboards, funnels, and trend alerts"),
ComboboxOption::new("Growth", "growth")
.with_description("Lifecycle campaigns and experiments"),
ComboboxOption::new("Infrastructure", "infrastructure")
.with_description("Runtime, deploys, and observability"),
ComboboxOption::new("Support", "support")
.with_description("Queues, macros, and response goals"),
];
let menu_items = vec![
DropdownMenuItem::new("Profile", "profile").with_shortcut("⌘P"),
DropdownMenuItem::new("Billing", "billing").with_shortcut("⌘B"),
DropdownMenuItem::new("Team", "team"),
DropdownMenuItem::new("Sign out", "logout").destructive(),
];
let breadcrumb_items = vec![
Crumb::new("Dashboard", Some("#")),
Crumb::new("Settings", Some("#settings")),
Crumb::new("Team", None::<String>),
];
let navigation_items = vec![
NavigationItem::new(
"Overview",
"#overview",
Some("Project snapshots and quick metrics"),
),
NavigationItem::new(
"Playground",
"#playground",
Some("Prototype new ideas and components"),
),
NavigationItem::new(
"Documentation",
"https://dioxuslabs.com/learn",
Some("Dive into the latest Dioxus 0.7 docs"),
),
];
let table_rows = vec![
("DW-9021", "Realtime dashboard", "Shipping", "2 minutes ago"),
(
"DB-1740",
"AI campaign assistant",
"Review",
"14 minutes ago",
),
("MR-1183", "Metrics service", "Building", "38 minutes ago"),
("PK-9422", "Payments ledger", "Queued", "58 minutes ago"),
("XD-7710", "Access gateway", "Paused", "2 hours ago"),
];
let activity_items = vec![
("09:05", "Jesse", "merged \"navigation cleanups\" into main"),
("10:18", "Mia", "scheduled the weekly metrics export"),
("11:42", "Arjun", "paused the experiment \"Pricing v2\""),
("12:03", "Ivy", "restarted the realtime analytics workers"),
("12:44", "Kai", "commented on the onboarding funnel deck"),
("13:27", "Lena", "acknowledged alert \"Queue depth\""),
];
let menubar_menus = vec![
MenubarMenu::new(
"File",
vec![
MenubarItem::new("New Tab", "new_tab").shortcut("⌘T"),
MenubarItem::new("Open Workspace", "open_workspace"),
MenubarItem::new("Save", "save").shortcut("⌘S"),
],
),
MenubarMenu::new(
"Edit",
vec![
MenubarItem::new("Undo", "undo").shortcut("⌘Z"),
MenubarItem::new("Redo", "redo").shortcut("⇧⌘Z"),
MenubarItem::new("Delete", "delete").destructive(),
],
),
];
let command_items = vec![
CommandItem::new("Create project", "create_project")
.shortcut("⌘N")
.group("Actions"),
CommandItem::new("Invite teammate", "invite").group("Actions"),
CommandItem::new("Open documentation", "docs").group("Resources"),
CommandItem::new("Keyboard shortcuts", "shortcuts").group("Resources"),
];
let context_items = vec![
ContextItem::new("Rename", "rename"),
ContextItem::new("Duplicate", "duplicate"),
ContextItem::new("Archive", "archive"),
ContextItem::new("Delete", "delete").destructive(),
];
let steps_items = vec![
StepItem::new("Plan", Some("Outline requirements")),
StepItem::new("Build", Some("Implement features")),
StepItem::new("Review", Some("QA and ship")),
];
let total_pages = 8usize;
let pagination_summary =
move || format!("Showing page {} of {total_pages}", pagination_current());
let steps_total = steps_items.len();
let steps_summary = move || format!("Stage {} of {steps_total}", steps_current());
let theme_display = {
let current = theme_choice();
current
.as_ref()
.and_then(|value| {
select_options
.iter()
.find(|option| option.value == *value)
.map(|option| option.label.clone())
})
.unwrap_or_else(|| "System".to_string())
};
let theme_summary = format!("Active theme: {theme_display}");
let collapsed_state = sidebar_collapsed();
let current_sidebar_value = sidebar_active();
let is_analytics_active = current_sidebar_value.as_str() == "analytics";
let is_crm_active = current_sidebar_value.as_str() == "crm";
let is_billing_active = current_sidebar_value.as_str() == "billing";
let is_settings_active = current_sidebar_value.as_str() == "settings";
let (sidebar_title, sidebar_body) = match current_sidebar_value.as_str() {
"analytics" => (
"Analytics overview".to_string(),
"Monitor KPI trends, conversion funnels, and health metrics in real time.".to_string(),
),
"crm" => (
"Customer relationship management".to_string(),
"Surface leads, segment accounts, and coordinate follow-ups in one place.".to_string(),
),
"billing" => (
"Billing & usage".to_string(),
"Review invoices, adjust subscription tiers, and reconcile metered usage.".to_string(),
),
"settings" => (
"Workspace settings".to_string(),
"Manage authentication, API tokens, and notification preferences.".to_string(),
),
_ => (
"Select a section".to_string(),
"Pick a destination from the sidebar to preview the content area.".to_string(),
),
};
let scroll_container_style = "width: 100%; overflow-x: auto; padding-bottom: 2.5rem;";
let showcase_grid_style = "display: grid; grid-template-columns: repeat(2, minmax(520px, 1fr)); gap: 2.5rem; align-items: start; min-width: 1100px;";
let full_width_style = "grid-column: 1 / -1; min-width: 520px;";
let single_column_style = "min-width: 520px;";
rsx! {
section {
class: if dark_mode() { "ui-shell shadcn dark" } else { "ui-shell shadcn" },
"data-theme": if dark_mode() { "dark" } else { "light" },
div {
class: "ui-showcase-scroll",
style: scroll_container_style,
div {
class: "ui-showcase-grid",
style: showcase_grid_style,
div {
style: full_width_style,
Card {
CardHeader {
CardTitle { "Profile form" }
CardDescription { "Inputs, sliders, helpers, and actions inside a card layout." }
}
CardContent {
div { class: "ui-stack",
Label { html_for: "profile-name", "Name" }
Input { id: "profile-name", placeholder: "Ada Lovelace" }
}
div { class: "ui-stack",
Label { html_for: "profile-about", "About" }
Textarea {
id: "profile-about",
placeholder: "Tell us something fun...",
rows: 4,
}
SpanHelper { "Textarea adopts shadcn spacing and typography out of the box." }
}
Separator { style: "margin: 1rem 0;" }
div { class: "ui-stack",
Label { html_for: "accent-slider", "Accent strength" }
Slider {
value: slider_value(),
min: 0.0,
max: 100.0,
step: 1.0,
on_value_change: {
let mut signal = slider_value_setter.clone();
move |val| signal.set(val)
},
}
Progress { value: slider_value(), max: 100.0 }
SpanHelper { "{intensity_text()}" }
}
div { class: "ui-stack",
Label { html_for: "theme-select", "Theme preference" }
Select {
id: Some("theme-select".to_string()),
placeholder: "Select a theme",
options: select_options.clone(),
selected: theme_choice_signal(),
on_change: move |value| {
let mut signal = theme_choice_setter.clone();
signal.set(Some(value));
},
}
SpanHelper { "{theme_summary}" }
}
div { class: "ui-bleed",
div { class: "ui-cluster",
Checkbox {
id: Some("accept-terms".to_string()),
checked: accepted_terms(),
on_checked_change: move |state| accepted_terms_setter.clone().set(state),
}
Label { html_for: "accept-terms", "Agree to terms" }
}
div { class: "ui-cluster",
Label { html_for: "profile-emails", "Email notifications" }
Switch {
id: Some("profile-emails".to_string()),
checked: email_notifications(),
on_checked_change: move |state| email_notifications_setter.clone().set(state),
}
}
}
}
CardFooter {
div { class: "ui-cluster",
Button { variant: ButtonVariant::Outline, size: ButtonSize::Sm, "Cancel" }
Button { disabled: !accepted_terms(), "Save changes" }
}
}
}
}
div {
style: single_column_style,
Card {
CardHeader {
CardTitle { "Form helpers" }
CardDescription { "FormField, Combobox, and Toggle wire up validation with shadcn styling." }
}
CardContent {
div { class: "ui-stack",
FormField {
id: Some("helper-name".to_string()),
label: Some("Project name".to_string()),
helper_text: Some(profile_preview()),
error: Some(name_error_signal),
Input {
id: "helper-name",
placeholder: "Launch analytics workspace",
value: profile_name_signal(),
on_input: {
let mut value_signal = profile_name_setter.clone();
let mut error_signal = name_error_setter.clone();
move |event: FormEvent| {
let value = event.value();
let trimmed_len = value.trim().len();
value_signal.set(value.clone());
if trimmed_len >= 3 {
error_signal.set(None);
} else {
error_signal.set(Some("Use at least 3 characters to stay descriptive.".to_string()));
}
}
},
}
}
FormField {
id: Some("helper-brief".to_string()),
label: Some("Summary".to_string()),
description: Some("Share quick context for the owners reviewing this request.".to_string()),
helper_text: Some("You can mention teammates with @ and use Markdown formatting.".to_string()),
Textarea {
id: "helper-brief",
placeholder: "Outline the goal, stakeholders, and success signal...",
rows: 4,
}
}
FormField {
id: Some("owner-combobox".to_string()),
label: Some("Assign owner".to_string()),
description: Some("Search across teams to hand off this initiative.".to_string()),
helper_text: Some(combobox_summary()),
Combobox {
id: Some("owner-combobox".to_string()),
placeholder: "Search by team...",
options: combobox_options.clone(),
selected: combobox_selection_signal(),
on_select: {
let mut setter = combobox_selection_setter.clone();
move |value| setter.set(Some(value))
},
}
}
div { class: "ui-stack",
Toggle {
pressed: toggle_active_signal(),
on_pressed_change: {
let mut setter = toggle_active_setter.clone();
move |state| setter.set(state)
},
"Email alerts"
}
FormMessage {
variant: FormMessageVariant::Helper,
class: Some("ui-field-helper".to_string()),
"{toggle_summary()}"
}
}
}
}
}
}
div {
style: single_column_style,
Card {
CardHeader {
CardTitle { "Layout & uploads" }
CardDescription { "Aspect ratios and drag-and-drop staging." }
}
CardContent {
div { class: "ui-stack",
AspectRatio {
ratio: 16.0 / 9.0,
div {
style: "width: 100%; height: 100%; background: radial-gradient(circle at 20% 20%, hsl(var(--primary) / 0.3), transparent); border-radius: calc(var(--radius) - 2px); display: flex; align-items: center; justify-content: center; font-size: 0.85rem; color: hsl(var(--muted-foreground));",
"Video or hero media stays perfectly scaled."
}
}
FileDropZone {
multiple: true,
on_files: {
let mut setter = dropzone_files_setter.clone();
move |files| setter.set(files)
},
content: Some(rsx! {
div {
class: "ui-stack",
span { class: "ui-dropzone-title", "Drop brand assets" }
span { class: "ui-field-helper", "Supports PNG, SVG, and MP4 up to 200 MB." }
}
}),
}
FormMessage {
variant: FormMessageVariant::Helper,
class: Some("ui-field-helper".to_string()),
"{dropzone_summary()}"
}
if !dropzone_files_signal().is_empty() {
{
let files = dropzone_files_signal();
rsx! {
ul {
style: "font-size: 0.8rem; display: flex; flex-direction: column; gap: 0.35rem;",
for file in files {
{
let label = format!("{} ({:.1} KB)", file.name, file.size as f64 / 1024.0);
rsx! { li { "{label}" } }
}
}
}
}
}
}
}
}
}
}
div {
style: single_column_style,
Card {
CardHeader {
CardTitle { "Schedules & ranges" }
CardDescription { "Collapsible filters, toggle groups, and dual-month range picking." }
}
CardContent {
div { class: "ui-stack",
Collapsible {
open: collapsible_signal.clone(),
on_open_change: {
let mut setter = collapsible_setter.clone();
move |state| setter.set(state)
},
CollapsibleTrigger { "Advanced filters" }
CollapsibleContent {
SpanHelper { "Keep optional controls tucked away until needed." }
SpanHelper { "Current state: " }
SpanHelper { if collapsible_signal() { "Expanded" } else { "Collapsed" } }
}
}
div { class: "ui-stack",
span { class: "ui-field-helper", "Delivery cadence" }
ToggleGroup {
values: toggle_group_signal.clone(),
mode: ToggleGroupMode::Multiple,
orientation: ToggleGroupOrientation::Horizontal,
on_value_change: {
let mut setter = toggle_group_setter.clone();
move |values| setter.set(values)
},
ToggleGroupItem { value: "daily".to_string(), "Daily" }
ToggleGroupItem { value: "weekly".to_string(), "Weekly" }
ToggleGroupItem { value: "monthly".to_string(), "Monthly" }
}
FormMessage {
variant: FormMessageVariant::Helper,
class: Some("ui-field-helper".to_string()),
"{toggle_group_summary()}"
}
}
DateRangePicker {
value: date_range_signal.clone(),
on_change: {
let mut setter = date_range_setter.clone();
move |range| setter.set(range)
},
initial_month: Some(NaiveDate::from_ymd_opt(2024, 6, 1).expect("valid month")),
}
FormMessage {
variant: FormMessageVariant::Helper,
class: Some("ui-field-helper".to_string()),
"{range_preview()}"
}
}
}
}
}
div {
style: single_column_style,
Card {
CardHeader {
CardTitle { "Buttons & badges" }
CardDescription { "Variant + size matrix copied directly from shadcn/ui." }
}
CardContent {
div { class: "ui-stack",
SpanHelper { "Buttons variants" }
div { class: "ui-cluster",
Button { "Primary" }
Button { variant: ButtonVariant::Secondary, "Secondary" }
Button { variant: ButtonVariant::Destructive, "Destructive" }
Button { variant: ButtonVariant::Outline, "Outline" }
Button { variant: ButtonVariant::Ghost, "Ghost" }
Button { variant: ButtonVariant::Link, "Learn more" }
}
}
div { class: "ui-stack",
SpanHelper { "Buttons sizes" }
div { class: "ui-cluster",
Button { size: ButtonSize::Sm, "Small" }
Button { "Default" }
Button { size: ButtonSize::Lg, "Large" }
Button { size: ButtonSize::Icon, "" }
}
}
Separator { style: "margin: 1rem 0;" }
div { class: "ui-stack",
SpanHelper { "Badges" }
div { class: "ui-cluster",
Badge { "Default" }
Badge { variant: BadgeVariant::Secondary, "Secondary" }
Badge { variant: BadgeVariant::Destructive, "Destructive" }
Separator { orientation: SeparatorOrientation::Vertical, style: "height: 1.5rem;" }
Badge { variant: BadgeVariant::Outline, "Outline" }
}
}
}
}
}
div {
style: single_column_style,
Card {
CardHeader {
CardTitle { "Select & dropdowns" }
CardDescription { "Select, dropdown menu, tooltip and dynamic feedback." }
}
CardContent {
div { class: "ui-stack",
Label { html_for: "quick-theme", "Quick theme" }
Select {
id: Some("quick-theme".to_string()),
placeholder: "Choose theme",
options: select_options.clone(),
selected: theme_choice_signal(),
on_change: move |value| {
let mut signal = theme_choice_setter.clone();
signal.set(Some(value));
},
}
}
div { class: "ui-stack",
SpanHelper { "Dropdown menu" }
DropdownMenu {
label: "Open menu",
items: menu_items.clone(),
on_select: move |value| {
let mut signal = menu_selection_setter.clone();
signal.set(format!("Selected action: {value}"));
},
}
SpanHelper { "{menu_selection()}" }
}
div { class: "ui-stack",
SpanHelper { "Tooltip" }
Tooltip {
label: "Invite collaborators",
Button {
variant: ButtonVariant::Ghost,
size: ButtonSize::Sm,
"Hover me"
}
}
}
}
}
}
div {
style: full_width_style,
Card {
CardHeader {
CardTitle { "Data timelines" }
CardDescription { "Tables, scroll areas, calendars, and skeleton loaders keep admin dashboards responsive." }
}
CardContent {
div { class: "ui-stack",
SpanHelper { "Deploys" }
ScrollArea {
max_height: Some("220px".to_string()),
Table {
TableCaption { "Latest updates from the delivery pipeline." }
TableHeader {
TableRow {
TableHead { "ID" }
TableHead { "Project" }
TableHead { "Status" }
TableHead { "Updated" }
}
}
TableBody {
for (id, name, status, updated) in table_rows.iter().copied() {
TableRow {
TableCell { "{id}" }
TableCell { "{name}" }
TableCell { "{status}" }
TableCell { "{updated}" }
}
}
}
TableFooter {
TableRow {
TableCell { "Total" }
TableCell { "{table_rows.len()} pipelines" }
TableCell { class: Some("ui-field-helper".to_string()), "Automated checks" }
TableCell { class: Some("ui-field-helper".to_string()), "Past hour" }
}
}
}
}
FormMessage {
variant: FormMessageVariant::Helper,
class: Some("ui-field-helper".to_string()),
"Keep automation quick by streaming the hottest rows into view."
}
Separator { style: "margin: 1rem 0;" }
SpanHelper { "Activity feed" }
ScrollArea {
max_height: Some("140px".to_string()),
ul {
style: "display: flex; flex-direction: column; gap: 0.6rem; font-size: 0.85rem;",
for (time, author, action) in activity_items.iter().copied() {
li {
style: "display: flex; gap: 0.5rem; align-items: baseline;",
span { style: "font-weight: 600; font-variant-numeric: tabular-nums;", "{time}" }
span { style: "font-weight: 600;", "{author}" }
span { style: "color: hsl(var(--muted-foreground));", "{action}" }
}
}
}
}
Separator { style: "margin: 1rem 0;" }
SpanHelper { "{calendar_summary()}" }
Calendar {
initial_month: calendar_month,
selected: calendar_selection_signal(),
on_select: {
let mut setter = calendar_selection_setter.clone();
move |day| setter.set(Some(day))
},
}
div { class: "ui-cluster",
Skeleton { width: Some("160px".to_string()), height: Some("1rem".to_string()) }
Skeleton { width: Some("120px".to_string()), height: Some("1rem".to_string()) }
Skeleton { width: Some("200px".to_string()), height: Some("1rem".to_string()) }
}
}
}
}
}
div {
style: full_width_style,
Card {
CardHeader {
CardTitle { "Navigation patterns" }
CardDescription { "Breadcrumbs, menus, pagination, and progress steps." }
}
CardContent {
div { class: "ui-stack",
SpanHelper { "Breadcrumb" }
Breadcrumb { items: breadcrumb_items.clone(), separator: ">".to_string() }
}
div { class: "ui-stack",
SpanHelper { "Navigation menu" }
NavigationMenu { items: navigation_items.clone() }
}
div { class: "ui-stack",
SpanHelper { "Menubar" }
Menubar {
menus: menubar_menus.clone(),
on_select: move |value| {
let mut signal = menubar_selection_setter.clone();
signal.set(format!("Menubar selected: {value}"));
},
}
SpanHelper { "{menubar_selection()}" }
}
div { class: "ui-stack",
SpanHelper { "Pagination" }
Pagination {
total_pages: total_pages,
current_page: pagination_current(),
on_page_change: move |page| {
let mut signal = pagination_setter.clone();
signal.set(page);
},
}
SpanHelper { "{pagination_summary()}" }
}
div { class: "ui-stack",
SpanHelper { "Steps" }
Steps {
steps: steps_items.clone(),
current: steps_current(),
}
div { class: "ui-cluster",
Button {
variant: ButtonVariant::Outline,
size: ButtonSize::Sm,
on_click: move |_| {
let mut signal = steps_setter.clone();
let prev = signal().saturating_sub(1).max(1);
signal.set(prev);
},
"Previous"
}
Button {
size: ButtonSize::Sm,
on_click: move |_| {
let mut signal = steps_setter.clone();
let next = (signal() + 1).min(steps_total);
signal.set(next);
},
"Next"
}
}
SpanHelper { "{steps_summary()}" }
}
}
}
}
div {
style: full_width_style,
Card {
CardHeader {
CardTitle { "Structural navigation" }
CardDescription { "Collapsible sidebar layout with grouped menus." }
}
CardContent {
SidebarLayout {
Sidebar {
collapsed: collapsed_state,
SidebarHeader {
div { class: "ui-sidebar-button-body",
span { class: "ui-sidebar-icon", "" }
span { class: "ui-sidebar-text",
span { class: "ui-sidebar-label", "Acme HQ" }
span { class: "ui-sidebar-description", "Operations console" }
}
}
}
SidebarContent {
SidebarGroup {
SidebarGroupLabel { "Workspace" }
SidebarGroupContent {
SidebarMenu {
SidebarMenuItem {
SidebarMenuButton {
label: "Analytics",
description: Some("Track KPIs and trends".into()),
icon: Some("📊".into()),
active: is_analytics_active,
on_click: move |_| {
let mut signal = sidebar_active_setter.clone();
signal.set("analytics".to_string());
},
}
}
SidebarMenuItem {
SidebarMenuButton {
label: "CRM",
description: Some("Manage customer pipeline".into()),
icon: Some("👥".into()),
active: is_crm_active,
on_click: move |_| {
let mut signal = sidebar_active_setter.clone();
signal.set("crm".to_string());
},
}
}
}
}
}
SidebarSeparator {}
SidebarGroup {
SidebarGroupLabel { "Reporting" }
SidebarGroupContent {
SidebarMenu {
SidebarMenuItem {
SidebarMenuButton {
label: "Billing",
description: Some("Invoices, usage, balances".into()),
icon: Some("💳".into()),
badge: Some("8".into()),
active: is_billing_active,
on_click: move |_| {
let mut signal = sidebar_active_setter.clone();
signal.set("billing".to_string());
},
}
}
SidebarMenuItem {
SidebarMenuButton {
label: "Settings",
description: Some("Themes, tokens, notifications".into()),
icon: Some("⚙️".into()),
active: is_settings_active,
on_click: move |_| {
let mut signal = sidebar_active_setter.clone();
signal.set("settings".to_string());
},
}
}
}
}
}
}
SidebarFooter {
SidebarTrigger {
collapsed: collapsed_state,
label: Some("Toggle sidebar".to_string()),
on_toggle: move |next| {
let mut signal = sidebar_collapsed_setter.clone();
signal.set(next);
},
}
}
}
SidebarInset {
class: "ui-stack",
h3 { style: "font-size: 1.2rem; font-weight: 600;", "{sidebar_title}" }
p {
style: "color: hsl(var(--muted-foreground)); max-width: 460px;",
"{sidebar_body}"
}
SpanHelper { "Use the sidebar to swap the focused surface." }
}
}
}
}
}
div {
style: single_column_style,
Card {
CardHeader {
CardTitle { "Selection controls" }
CardDescription { "Checkboxes, switches, and radio groups stay in sync with signals." }
}
CardContent {
div { class: "ui-stack",
div { class: "ui-cluster",
Checkbox {
id: Some("newsletter-opt".to_string()),
checked: newsletter_opt_in(),
on_checked_change: move |state| newsletter_opt_in_setter.clone().set(state),
}
Label { html_for: "newsletter-opt", "Subscribe to newsletter" }
}
div { class: "ui-cluster",
Label { html_for: "dark-mode", "Dark mode" }
Switch {
id: Some("dark-mode".to_string()),
checked: dark_mode(),
on_checked_change: move |state| dark_mode_setter.clone().set(state),
}
}
Separator { style: "margin: 0.75rem 0;" }
RadioGroup {
default_value: contact_method(),
on_value_change: move |value| contact_method_setter.clone().set(value),
div { class: "ui-stack",
div { class: "ui-cluster",
RadioGroupItem { id: Some("contact-email".to_string()), value: "email" }
Label { html_for: "contact-email", "Email" }
}
div { class: "ui-cluster",
RadioGroupItem { id: Some("contact-sms".to_string()), value: "sms" }
Label { html_for: "contact-sms", "SMS" }
}
div { class: "ui-cluster",
RadioGroupItem { id: Some("contact-call".to_string()), value: "call" }
Label { html_for: "contact-call", "Phone call" }
}
}
}
SpanHelper { "{contact_text()}" }
}
}
}
}
div {
style: single_column_style,
Card {
CardHeader {
CardTitle { "Command & context" }
CardDescription { "Command palette filtering and contextual menus." }
}
CardContent {
div { class: "ui-stack",
SpanHelper { "Command palette" }
CommandPalette {
items: command_items.clone(),
on_select: move |value| {
let mut signal = command_selection_setter.clone();
signal.set(format!("Command selected: {value}"));
},
}
SpanHelper { "{command_selection()}" }
}
div { class: "ui-stack",
SpanHelper { "Context menu" }
ContextMenu {
items: context_items.clone(),
on_select: move |value| {
let mut signal = context_selection_setter.clone();
signal.set(format!("Context action: {value}"));
},
div {
style: "padding: 1.5rem; border: 1px dashed hsl(var(--border)); border-radius: var(--radius); text-align: center;",
"Right click anywhere in this box"
}
}
SpanHelper { "{context_selection()}" }
}
}
}
}
div {
style: single_column_style,
Card {
CardHeader {
CardTitle { "Tabs & panels" }
CardDescription { "Tabbed navigation with content surfaces that stay in sync." }
}
CardContent {
Tabs {
default_value: "overview",
TabsList {
TabsTrigger { value: "overview", "Overview" }
TabsTrigger { value: "analytics", "Analytics" }
TabsTrigger { value: "reports", "Reports" }
}
TabsContent {
value: "overview",
div { class: "ui-stack",
Label { html_for: "overview-search", "Search" }
Input { id: "overview-search", placeholder: "Search docs..." }
SpanHelper { "Triggers share the same focus ring and sizing as the original UI kit." }
}
}
TabsContent {
value: "analytics",
div { class: "ui-stack",
SpanHelper { "Analytics aggregates live metrics and shows their progress." }
Progress { value: 64.0, max: 100.0 }
}
}
TabsContent {
value: "reports",
div { class: "ui-stack",
SpanHelper { "Generate PDF, CSV, or scheduled exports directly from here." }
Button { variant: ButtonVariant::Secondary, "Create report" }
}
}
}
}
}
}
div {
style: full_width_style,
Card {
CardHeader {
CardTitle { "Dialogs & overlays" }
CardDescription { "Popover, hover card, dialogs, sheet, and toast examples." }
}
CardContent {
div { class: "ui-cluster",
Button {
variant: ButtonVariant::Secondary,
on_click: move |_| {
let mut signal = dialog_signal.clone();
signal.set(true);
},
"Open dialog"
}
Button {
variant: ButtonVariant::Outline,
on_click: move |_| {
let mut signal = sheet_signal.clone();
signal.set(true);
},
"Open sheet"
}
Button {
variant: ButtonVariant::Ghost,
on_click: move |_| {
let mut signal = toast_signal.clone();
signal.set(true);
},
"Notify me"
}
}
div { class: "ui-stack",
SpanHelper { "Popover" }
Popover {
placement: "bottom".to_string(),
trigger: rsx! { Button { variant: ButtonVariant::Outline, size: ButtonSize::Sm, "Toggle popover" } },
content: rsx! { SpanHelper { "Choose the dialog or sheet you want to configure." } },
}
}
div { class: "ui-stack",
SpanHelper { "Hover card" }
HoverCard {
trigger: rsx! { Badge { variant: BadgeVariant::Secondary, "Hover me" } },
content: rsx! { span { style: "font-size: 0.8rem; color: hsl(var(--muted-foreground));", "Preview contextual information instantly." } },
}
}
}
}
}
div {
style: single_column_style,
Card {
CardHeader {
CardTitle { "Alerts & extras" }
CardDescription { "Feedback surfaces, accordions, and avatar fallbacks." }
}
CardContent {
div { class: "ui-stack",
Alert {
title: Some("Heads up!".to_string()),
"We just shipped async server functions to production."
}
Alert {
variant: AlertVariant::Destructive,
title: Some("Deployment failed".to_string()),
"Check the build logs and retry once the issue is resolved."
}
}
Separator { style: "margin: 1rem 0;" }
Accordion {
collapsible: true,
default_value: Some("item-1".to_string()),
AccordionItem {
value: "item-1".to_string(),
AccordionTrigger { "What is shadcn/ui?" }
AccordionContent {
"A collection of unstyled, accessible primitives built on top of Radix, ready for your design system."
}
}
AccordionItem {
value: "item-2".to_string(),
AccordionTrigger { "Does this work with Dioxus?" }
AccordionContent {
"Yes! These components mirror the shadcn/ui ergonomics using Dioxus 0.7 signals."
}
}
}
Separator { style: "margin: 1rem 0;" }
div { class: "ui-cluster",
Tooltip {
label: "Ada Lovelace",
Avatar {
alt: Some("Ada Lovelace".to_string()),
fallback: Some("AL".to_string()),
}
}
Avatar {
alt: Some("Grace Hopper".to_string()),
fallback: Some("GH".to_string()),
}
}
}
}
}
}
}
Dialog {
open: dialog_signal.clone(),
title: Some("Create project".to_string()),
description: Some("Configure the new analytics workspace.".to_string()),
div { class: "ui-stack",
Label { html_for: "dialog-name", "Project name" }
Input { id: "dialog-name", placeholder: "Analytics redesign" }
}
}
Sheet {
open: sheet_signal.clone(),
side: SheetSide::Right,
title: Some("Activity log".to_string()),
description: Some("Review the latest changes from your teammates.".to_string()),
div {
class: "ui-stack",
SpanHelper { "Today" }
ul {
style: "display: flex; flex-direction: column; gap: 0.5rem; font-size: 0.85rem;",
li { "Maria added new metrics to the dashboard." }
li { "Evan approved the Q2 launch plan." }
li { "Ada commented on revenue projections." }
}
}
}
ToastViewport {
Toast {
open: toast_open(),
title: Some("Changes saved".to_string()),
description: Some("We synced your workspace preferences.".to_string()),
on_close: move |_| {
let mut signal = toast_signal.clone();
signal.set(false);
},
}
}
}
}
}
#[component]
fn SpanHelper(children: Element) -> Element {
rsx! { span { class: "ui-field-helper", {children} } }
}