chore: first version
This commit is contained in:
@@ -0,0 +1,536 @@
|
||||
# Common Adaptive Card Form Patterns
|
||||
|
||||
Reusable template JSON and handler code for the most common form types in Command Palette extensions.
|
||||
|
||||
---
|
||||
|
||||
## Simple Text Input Form
|
||||
|
||||
A basic form with one or two text fields and a submit button.
|
||||
|
||||
### TemplateJson
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
||||
"type": "AdaptiveCard",
|
||||
"version": "1.6",
|
||||
"body": [
|
||||
{
|
||||
"type": "Input.Text",
|
||||
"id": "FirstName",
|
||||
"label": "First Name",
|
||||
"placeholder": "Enter your first name",
|
||||
"isRequired": true,
|
||||
"errorMessage": "First name is required"
|
||||
},
|
||||
{
|
||||
"type": "Input.Text",
|
||||
"id": "Email",
|
||||
"label": "Email Address",
|
||||
"placeholder": "user@example.com",
|
||||
"style": "Email",
|
||||
"isRequired": true,
|
||||
"errorMessage": "A valid email is required"
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"type": "Action.Submit",
|
||||
"title": "Submit"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### SubmitForm Handler
|
||||
|
||||
```csharp
|
||||
public override CommandResult SubmitForm(string payload)
|
||||
{
|
||||
var input = JsonNode.Parse(payload)?.AsObject();
|
||||
if (input == null) return CommandResult.GoHome();
|
||||
|
||||
var firstName = input["FirstName"]?.ToString() ?? "";
|
||||
var email = input["Email"]?.ToString() ?? "";
|
||||
|
||||
return CommandResult.ShowToast($"Registered {firstName} ({email})");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Toggle/Checkbox Form
|
||||
|
||||
Use `Input.Toggle` for boolean on/off settings. Combine with `DataJson` for dynamic defaults.
|
||||
|
||||
### TemplateJson
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
||||
"type": "AdaptiveCard",
|
||||
"version": "1.6",
|
||||
"body": [
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"text": "Preferences",
|
||||
"weight": "Bolder",
|
||||
"size": "Medium"
|
||||
},
|
||||
{
|
||||
"type": "Input.Toggle",
|
||||
"id": "AcceptsTerms",
|
||||
"title": "I accept the terms and conditions",
|
||||
"valueOn": "true",
|
||||
"valueOff": "false",
|
||||
"value": "false"
|
||||
},
|
||||
{
|
||||
"type": "Input.Toggle",
|
||||
"id": "EnableNotifications",
|
||||
"title": "Enable notifications",
|
||||
"valueOn": "true",
|
||||
"valueOff": "false",
|
||||
"value": "${notificationsDefault}"
|
||||
},
|
||||
{
|
||||
"type": "Input.Toggle",
|
||||
"id": "DarkMode",
|
||||
"title": "Use dark mode",
|
||||
"valueOn": "true",
|
||||
"valueOff": "false",
|
||||
"value": "${darkModeDefault}"
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"type": "Action.Submit",
|
||||
"title": "Save Preferences"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### DataJson (Dynamic Defaults)
|
||||
|
||||
```csharp
|
||||
DataJson = """
|
||||
{
|
||||
"notificationsDefault": "true",
|
||||
"darkModeDefault": "false"
|
||||
}
|
||||
""";
|
||||
```
|
||||
|
||||
### SubmitForm Handler
|
||||
|
||||
```csharp
|
||||
public override CommandResult SubmitForm(string payload)
|
||||
{
|
||||
var input = JsonNode.Parse(payload)?.AsObject();
|
||||
if (input == null) return CommandResult.GoHome();
|
||||
|
||||
var accepted = input["AcceptsTerms"]?.ToString() == "true";
|
||||
var notifications = input["EnableNotifications"]?.ToString() == "true";
|
||||
var darkMode = input["DarkMode"]?.ToString() == "true";
|
||||
|
||||
if (!accepted)
|
||||
{
|
||||
return CommandResult.ShowToast("You must accept the terms to continue.");
|
||||
}
|
||||
|
||||
// Save preferences...
|
||||
return CommandResult.ShowToast("Preferences saved!");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Choice Set (Dropdown/Radio) Form
|
||||
|
||||
Use `Input.ChoiceSet` for single-select dropdowns or radio buttons.
|
||||
|
||||
### Compact Style (Dropdown)
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
||||
"type": "AdaptiveCard",
|
||||
"version": "1.6",
|
||||
"body": [
|
||||
{
|
||||
"type": "Input.ChoiceSet",
|
||||
"id": "Priority",
|
||||
"label": "Priority Level",
|
||||
"style": "compact",
|
||||
"value": "medium",
|
||||
"choices": [
|
||||
{ "title": "Low", "value": "low" },
|
||||
{ "title": "Medium", "value": "medium" },
|
||||
{ "title": "High", "value": "high" },
|
||||
{ "title": "Critical", "value": "critical" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Input.ChoiceSet",
|
||||
"id": "Category",
|
||||
"label": "Category",
|
||||
"style": "compact",
|
||||
"choices": [
|
||||
{ "title": "Bug Report", "value": "bug" },
|
||||
{ "title": "Feature Request", "value": "feature" },
|
||||
{ "title": "Documentation", "value": "docs" },
|
||||
{ "title": "Question", "value": "question" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"type": "Action.Submit",
|
||||
"title": "Create Issue"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Expanded Style (Radio Buttons)
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
||||
"type": "AdaptiveCard",
|
||||
"version": "1.6",
|
||||
"body": [
|
||||
{
|
||||
"type": "Input.ChoiceSet",
|
||||
"id": "Theme",
|
||||
"label": "Select a theme",
|
||||
"style": "expanded",
|
||||
"value": "system",
|
||||
"choices": [
|
||||
{ "title": "Light", "value": "light" },
|
||||
{ "title": "Dark", "value": "dark" },
|
||||
{ "title": "System Default", "value": "system" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"type": "Action.Submit",
|
||||
"title": "Apply"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Multi-Section Form
|
||||
|
||||
Combine multiple input types with TextBlock headers to create organized, multi-section forms. Use `Action.ShowCard` for progressive disclosure of optional sections.
|
||||
|
||||
### TemplateJson
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
||||
"type": "AdaptiveCard",
|
||||
"version": "1.6",
|
||||
"body": [
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"text": "Personal Information",
|
||||
"weight": "Bolder",
|
||||
"size": "Medium",
|
||||
"separator": true
|
||||
},
|
||||
{
|
||||
"type": "Input.Text",
|
||||
"id": "FullName",
|
||||
"label": "Full Name",
|
||||
"placeholder": "Enter your full name",
|
||||
"isRequired": true,
|
||||
"errorMessage": "Name is required"
|
||||
},
|
||||
{
|
||||
"type": "Input.Text",
|
||||
"id": "Email",
|
||||
"label": "Email",
|
||||
"placeholder": "user@example.com",
|
||||
"style": "Email"
|
||||
},
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"text": "Preferences",
|
||||
"weight": "Bolder",
|
||||
"size": "Medium",
|
||||
"separator": true,
|
||||
"spacing": "Large"
|
||||
},
|
||||
{
|
||||
"type": "Input.ChoiceSet",
|
||||
"id": "Language",
|
||||
"label": "Preferred Language",
|
||||
"style": "compact",
|
||||
"value": "en",
|
||||
"choices": [
|
||||
{ "title": "English", "value": "en" },
|
||||
{ "title": "Spanish", "value": "es" },
|
||||
{ "title": "French", "value": "fr" },
|
||||
{ "title": "German", "value": "de" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Input.Toggle",
|
||||
"id": "Newsletter",
|
||||
"title": "Subscribe to newsletter",
|
||||
"valueOn": "true",
|
||||
"valueOff": "false",
|
||||
"value": "true"
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"type": "Action.Submit",
|
||||
"title": "Save Profile"
|
||||
},
|
||||
{
|
||||
"type": "Action.ShowCard",
|
||||
"title": "Advanced Options",
|
||||
"card": {
|
||||
"type": "AdaptiveCard",
|
||||
"body": [
|
||||
{
|
||||
"type": "Input.Text",
|
||||
"id": "ApiKey",
|
||||
"label": "API Key (optional)",
|
||||
"placeholder": "Enter your API key"
|
||||
},
|
||||
{
|
||||
"type": "Input.Toggle",
|
||||
"id": "DebugMode",
|
||||
"title": "Enable debug mode",
|
||||
"valueOn": "true",
|
||||
"valueOff": "false",
|
||||
"value": "false"
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"type": "Action.Submit",
|
||||
"title": "Save All"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Feedback Form
|
||||
|
||||
A common pattern for collecting user feedback with a multiline text area and a rating.
|
||||
|
||||
### TemplateJson
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
||||
"type": "AdaptiveCard",
|
||||
"version": "1.6",
|
||||
"body": [
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"text": "We'd love your feedback!",
|
||||
"weight": "Bolder",
|
||||
"size": "Medium"
|
||||
},
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"text": "Tell us what you think and how we can improve.",
|
||||
"wrap": true,
|
||||
"spacing": "Small"
|
||||
},
|
||||
{
|
||||
"type": "Input.ChoiceSet",
|
||||
"id": "Rating",
|
||||
"label": "How would you rate your experience?",
|
||||
"style": "expanded",
|
||||
"isRequired": true,
|
||||
"errorMessage": "Please select a rating",
|
||||
"choices": [
|
||||
{ "title": "⭐ Poor", "value": "1" },
|
||||
{ "title": "⭐⭐ Fair", "value": "2" },
|
||||
{ "title": "⭐⭐⭐ Good", "value": "3" },
|
||||
{ "title": "⭐⭐⭐⭐ Great", "value": "4" },
|
||||
{ "title": "⭐⭐⭐⭐⭐ Excellent", "value": "5" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Input.Text",
|
||||
"id": "Comments",
|
||||
"label": "Comments",
|
||||
"placeholder": "Share your thoughts...",
|
||||
"isMultiline": true,
|
||||
"maxLength": 500
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"type": "Action.Submit",
|
||||
"title": "Send Feedback"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### SubmitForm Handler with Confirmation Dialog
|
||||
|
||||
```csharp
|
||||
public override CommandResult SubmitForm(string payload)
|
||||
{
|
||||
var input = JsonNode.Parse(payload)?.AsObject();
|
||||
if (input == null) return CommandResult.GoHome();
|
||||
|
||||
var rating = input["Rating"]?.ToString() ?? "0";
|
||||
var comments = input["Comments"]?.ToString() ?? "";
|
||||
|
||||
return CommandResult.Confirm(new ConfirmationArgs
|
||||
{
|
||||
Title = "Submit feedback?",
|
||||
Description = $"Rating: {rating}/5\n\n{(string.IsNullOrEmpty(comments) ? "No comments" : comments)}",
|
||||
PrimaryCommand = new AnonymousCommand(() =>
|
||||
{
|
||||
// Process and store feedback
|
||||
new ToastStatusMessage("Thank you for your feedback!").Show();
|
||||
})
|
||||
{
|
||||
Name = "Submit",
|
||||
Result = CommandResult.Dismiss(),
|
||||
},
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tree Content with Forms (Comment/Reply Pattern)
|
||||
|
||||
Use `TreeContent` to create nested, threaded discussions where each node can contain a form for replies.
|
||||
|
||||
### Post Content (Tree Node)
|
||||
|
||||
```csharp
|
||||
internal sealed partial class PostContent : TreeContent
|
||||
{
|
||||
private readonly string _author;
|
||||
private readonly string _body;
|
||||
private readonly PostReplyForm _replyForm;
|
||||
private readonly List<PostContent> _childPosts = [];
|
||||
|
||||
public PostContent(string author, string body)
|
||||
{
|
||||
_author = author;
|
||||
_body = body;
|
||||
_replyForm = new PostReplyForm(this);
|
||||
|
||||
TemplateJson = """
|
||||
{
|
||||
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
||||
"type": "AdaptiveCard",
|
||||
"version": "1.6",
|
||||
"body": [
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"text": "${author}",
|
||||
"weight": "Bolder"
|
||||
},
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"text": "${body}",
|
||||
"wrap": true
|
||||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
DataJson = $$"""{ "author": "{{_author}}", "body": "{{_body}}" }""";
|
||||
}
|
||||
|
||||
public override IContent[] GetChildren() => [_replyForm, .. _childPosts];
|
||||
|
||||
public void AddReply(PostContent reply) => _childPosts.Add(reply);
|
||||
}
|
||||
```
|
||||
|
||||
### Reply Form (Child of Tree Node)
|
||||
|
||||
```csharp
|
||||
internal sealed partial class PostReplyForm : FormContent
|
||||
{
|
||||
private readonly PostContent _parent;
|
||||
|
||||
public PostReplyForm(PostContent parent)
|
||||
{
|
||||
_parent = parent;
|
||||
TemplateJson = """
|
||||
{
|
||||
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
||||
"type": "AdaptiveCard",
|
||||
"version": "1.6",
|
||||
"body": [
|
||||
{
|
||||
"type": "Input.Text",
|
||||
"id": "ReplyText",
|
||||
"placeholder": "Write a reply...",
|
||||
"isMultiline": true
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"type": "Action.Submit",
|
||||
"title": "Reply"
|
||||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
}
|
||||
|
||||
public override CommandResult SubmitForm(string payload)
|
||||
{
|
||||
var input = JsonNode.Parse(payload)?.AsObject();
|
||||
if (input == null) return CommandResult.GoHome();
|
||||
|
||||
var replyText = input["ReplyText"]?.ToString();
|
||||
if (!string.IsNullOrWhiteSpace(replyText))
|
||||
{
|
||||
_parent.AddReply(new PostContent("You", replyText));
|
||||
}
|
||||
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Hosting the Thread on a ContentPage
|
||||
|
||||
```csharp
|
||||
internal sealed partial class ThreadPage : ContentPage
|
||||
{
|
||||
private readonly PostContent _rootPost;
|
||||
|
||||
public ThreadPage()
|
||||
{
|
||||
Name = "Discussion";
|
||||
Title = "Discussion Thread";
|
||||
Icon = new IconInfo("\uE90A");
|
||||
|
||||
_rootPost = new PostContent("Alice", "Has anyone tried the new API?");
|
||||
_rootPost.AddReply(new PostContent("Bob", "Yes! It works great."));
|
||||
}
|
||||
|
||||
public override IContent[] GetContent() => [_rootPost];
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user