Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
456 changes: 456 additions & 0 deletions docs/specs/shell-route-templates.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/Controls/samples/Controls.Sample.Sandbox/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public App()
protected override Window CreateWindow(IActivationState? activationState)
{
// To test shell scenarios, change this to true
bool useShell = false;
bool useShell = true;

if (!useShell)
{
Expand Down
46 changes: 45 additions & 1 deletion src/Controls/samples/Controls.Sample.Sandbox/MainPage.xaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,49 @@
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.MainPage"
xmlns:local="clr-namespace:Maui.Controls.Sample">
xmlns:local="clr-namespace:Maui.Controls.Sample"
Title="Product Catalog">
<ScrollView>
<VerticalStackLayout Padding="20" Spacing="15">
<Label Text="🌱 Product Catalog"
FontSize="28"
FontAttributes="Bold" />
<Label Text="Tap a product to navigate using {sku} path parameter."
FontSize="14"
TextColor="Gray" />

<BoxView HeightRequest="1" Color="LightGray" />

<Button Text="🍅 Seed Tomato"
AutomationId="ProductSeedTomato"
Clicked="OnProductTapped"
CommandParameter="seed-tomato" />

<Button Text="🌿 Herb Basil"
AutomationId="ProductHerbBasil"
Clicked="OnProductTapped"
CommandParameter="herb-basil" />

<Button Text="🥕 Root Carrot"
AutomationId="ProductRootCarrot"
Clicked="OnProductTapped"
CommandParameter="root-carrot" />

<BoxView HeightRequest="1" Color="LightGray" />

<Label Text="Multi-step navigation (product → review)"
FontSize="14"
FontAttributes="Bold" />

<Button Text="🍅 Seed Tomato → Review (default ⭐5)"
AutomationId="ProductSeedTomatoReview"
Clicked="OnProductReviewTapped"
CommandParameter="seed-tomato" />

<Button Text="🌿 Herb Basil → Review (⭐3)"
AutomationId="ProductHerbBasilReview3"
Clicked="OnProductReviewWithStars"
CommandParameter="herb-basil" />
</VerticalStackLayout>
</ScrollView>
</ContentPage>
18 changes: 18 additions & 0 deletions src/Controls/samples/Controls.Sample.Sandbox/MainPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,22 @@ public MainPage()
{
InitializeComponent();
}

async void OnProductTapped(object sender, EventArgs e)
{
if (sender is Button btn && btn.CommandParameter is string sku)
await Shell.Current.GoToAsync($"//products/product/{sku}");
}

async void OnProductReviewTapped(object sender, EventArgs e)
{
if (sender is Button btn && btn.CommandParameter is string sku)
await Shell.Current.GoToAsync($"//products/product/{sku}/review");
}

async void OnProductReviewWithStars(object sender, EventArgs e)
{
if (sender is Button btn && btn.CommandParameter is string sku)
await Shell.Current.GoToAsync($"//products/product/{sku}/review/3");
}
}
17 changes: 17 additions & 0 deletions src/Controls/samples/Controls.Sample.Sandbox/OrderDetailPage.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.OrderDetailPage"
Title="Order Detail">
<VerticalStackLayout Padding="20" Spacing="15">
<Label Text="📋 Order Detail"
FontSize="24"
FontAttributes="Bold" />
<Label x:Name="OrderIdLabel"
Text="Order ID: (from {orderId:int} path parameter)"
FontSize="18" />
<Label Text="The orderId parameter uses {:int} constraint — only numeric values are accepted."
FontSize="14"
TextColor="Gray" />
</VerticalStackLayout>
</ContentPage>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace Maui.Controls.Sample;

[QueryProperty(nameof(OrderId), "orderId")]
public partial class OrderDetailPage : ContentPage
{
public OrderDetailPage()
{
InitializeComponent();
}

public string? OrderId
{
get => _orderId;
set
{
_orderId = value;
OnPropertyChanged();
if (OrderIdLabel is not null)
OrderIdLabel.Text = $"Order ID: #{value}";
}
}
string? _orderId;
}
33 changes: 33 additions & 0 deletions src/Controls/samples/Controls.Sample.Sandbox/OrdersPage.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.OrdersPage"
Title="Orders">
<ScrollView>
<VerticalStackLayout Padding="20" Spacing="15">
<Label Text="📦 Orders"
FontSize="28"
FontAttributes="Bold" />
<Label Text="Tap an order to navigate using {orderId:int} constrained path parameter."
FontSize="14"
TextColor="Gray" />

<BoxView HeightRequest="1" Color="LightGray" />

<Button Text="Order #1001"
AutomationId="Order1001"
Clicked="OnOrderTapped"
CommandParameter="1001" />

<Button Text="Order #1002"
AutomationId="Order1002"
Clicked="OnOrderTapped"
CommandParameter="1002" />

<Button Text="Order #1003"
AutomationId="Order1003"
Clicked="OnOrderTapped"
CommandParameter="1003" />
</VerticalStackLayout>
</ScrollView>
</ContentPage>
15 changes: 15 additions & 0 deletions src/Controls/samples/Controls.Sample.Sandbox/OrdersPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Maui.Controls.Sample;

public partial class OrdersPage : ContentPage
{
public OrdersPage()
{
InitializeComponent();
}

async void OnOrderTapped(object sender, EventArgs e)
{
if (sender is Button btn && btn.CommandParameter is string orderId)
await Shell.Current.GoToAsync($"//orders/order/{orderId}");
}
}
17 changes: 17 additions & 0 deletions src/Controls/samples/Controls.Sample.Sandbox/ProductPage.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.ProductPage"
Title="Product Detail">
<VerticalStackLayout Padding="20" Spacing="15">
<Label Text="Product Detail Page"
FontSize="24"
FontAttributes="Bold" />
<Label x:Name="SkuLabel"
Text="SKU: (waiting for parameter)"
FontSize="18" />
<Button Text="Go to Review →"
Clicked="OnGoToReview"
x:Name="ReviewButton" />
</VerticalStackLayout>
</ContentPage>
30 changes: 30 additions & 0 deletions src/Controls/samples/Controls.Sample.Sandbox/ProductPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
namespace Maui.Controls.Sample;

[QueryProperty(nameof(Sku), "sku")]
public partial class ProductPage : ContentPage
{
public ProductPage()
{
InitializeComponent();
}

public string? Sku
{
get => _sku;
set
{
_sku = value;
OnPropertyChanged();
if (SkuLabel is not null)
SkuLabel.Text = $"SKU: {value}";
if (ReviewButton is not null)
ReviewButton.IsVisible = !string.IsNullOrEmpty(value);
}
}
string? _sku;

async void OnGoToReview(object sender, EventArgs e)
{
await Shell.Current.GoToAsync("review");
}
}
20 changes: 20 additions & 0 deletions src/Controls/samples/Controls.Sample.Sandbox/ReviewPage.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.ReviewPage"
Title="Review">
<VerticalStackLayout Padding="20" Spacing="15">
<Label Text="📝 Write a Review"
FontSize="24"
FontAttributes="Bold" />
<Label x:Name="SkuLabel"
Text="Product: (inherited from parent route)"
FontSize="18" />
<Label x:Name="StarsLabel"
Text="Rating: (from {stars=5} default)"
FontSize="18" />
<Label Text="The stars parameter uses {stars=5} — if you navigate without specifying stars, you get the default of 5."
FontSize="14"
TextColor="Gray" />
</VerticalStackLayout>
</ContentPage>
40 changes: 40 additions & 0 deletions src/Controls/samples/Controls.Sample.Sandbox/ReviewPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
namespace Maui.Controls.Sample;

[QueryProperty(nameof(Sku), "sku")]
[QueryProperty(nameof(Stars), "stars")]
public partial class ReviewPage : ContentPage
{
public ReviewPage()
{
InitializeComponent();
}

public string? Sku
{
get => _sku;
set
{
_sku = value;
OnPropertyChanged();
if (SkuLabel is not null)
SkuLabel.Text = $"Product: {value}";
}
}
string? _sku;

public string? Stars
{
get => _stars;
set
{
_stars = value;
OnPropertyChanged();
if (StarsLabel is not null)
{
var count = int.TryParse(value, out var n) ? n : 0;
StarsLabel.Text = $"Rating: {new string('⭐', count)} ({value})";
}
}
}
string? _stars;
}
23 changes: 11 additions & 12 deletions src/Controls/samples/Controls.Sample.Sandbox/SandboxShell.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,18 @@
x:Class="Maui.Controls.Sample.SandboxShell"
xmlns:local="clr-namespace:Maui.Controls.Sample"
x:Name="shell">
<TabBar Shell.TabBarForegroundColor="Green"
Shell.TabBarUnselectedColor="Red">
<Tab Title="MainPage1" Icon="groceries.png">
<ShellContent Icon="groceries.png"
Title="Home"
ContentTemplate="{DataTemplate local:MainPage}"
Route="MainPage" />
<TabBar>
<Tab Title="Products" Icon="groceries.png">
<ShellContent
Title="Products"
ContentTemplate="{DataTemplate local:MainPage}"
Route="products" />
</Tab>
<Tab Title="MainPage2" Icon="dotnet_bot.png">
<ShellContent Icon="dotnet_bot.png"
Title="Home"
ContentTemplate="{DataTemplate local:MainPage}"
Route="MainPage2" />
<Tab Title="Orders" Icon="dotnet_bot.png">
<ShellContent
Title="Orders"
ContentTemplate="{DataTemplate local:OrdersPage}"
Route="orders" />
</Tab>
</TabBar>
</Shell>
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,13 @@ public partial class SandboxShell : Shell
public SandboxShell()
{
InitializeComponent();

// Product routes: product/{sku} with review child that has
// a default stars value via {stars=5}
Routing.RegisterRoute("product/{sku}", typeof(ProductPage));
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] PR scoping — The Sandbox sample is being repurposed into a route-template demo (product/{sku}, review/{stars=5}, order/{orderId:int}, plus 5 new *Page.xaml files and a rewritten MainPage). Sandbox is a shared developer test bed used by everyone working on MAUI; permanently dedicating it to one feature's demo is unusual. Consider either (a) moving these pages into a dedicated sample under src/Controls/samples/Controls.Sample or Controls.Sample.Maui so other developers' sandbox state isn't disrupted, or (b) confirming with the team that this repurposing is intentional. At minimum, ensure App.xaml.cs change at line 2 is intentional and not leftover scratch state.

Routing.RegisterRoute("review/{stars=5}", typeof(ReviewPage));

// Order routes: order/{orderId:int}
Routing.RegisterRoute("order/{orderId:int}", typeof(OrderDetailPage));
}
}
Loading
Loading