Skip to content

Commit 04b9445

Browse files
committed
Added MdSelect and its demo page.
1 parent 2752989 commit 04b9445

File tree

9 files changed

+387
-39
lines changed

9 files changed

+387
-39
lines changed
Lines changed: 141 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
@page "/select"
22

3+
@inject IJSRuntime Js;
4+
35
<ComponentDemoPage Name="Select"
46
DesignUrl="@(null)"
57
ComponentUrl="https://github.com/material-components/material-web/blob/main/docs/components/select.md"
@@ -10,23 +12,154 @@
1012
Select menus display a list of choices on temporary surfaces and display the currently selected menu item above the menu.
1113
</Description>
1214
<ComponentDemo>
13-
<div class="alert alert-warning">
14-
This component is still under development by Material Web.
15-
It is not working yet.
15+
<div class="row">
16+
<div class="col">
17+
<MdSelect @ref="cboNum" @bind-Value="selectedValue" @bind-SelectedIndex="selectedIndexes[0]"
18+
class="w-100"
19+
Required="required"
20+
Quick="quick"
21+
Disabled="disabled"
22+
Error="hasError"
23+
ErrorText="@(hasError ? "Did you divide by 0?" : null)"
24+
Label="@(hasLabel ? "Select a constant" : null)"
25+
SupportingText="@(hasSupportingText ? "Custom validity only accepts values smaller than 4" : null)"
26+
HasLeadingIcon="leadingIcon">
27+
@if (leadingIcon)
28+
{
29+
<MdIcon slot="@(MdSelect.LeadingIconSlot)">function</MdIcon>
30+
}
31+
32+
@foreach (var (value, text, symbol, desc, url) in Values)
33+
{
34+
<MdSelectOption Value="@(value)">
35+
<div slot="@(MdSelectOption.HeadlineSlot)">@(text)</div>
36+
<div slot="@(MdSelectOption.StartSlot)">@(symbol)</div>
37+
<div slot="@(MdSelectOption.OverlineSlot)">@(value)</div>
38+
<div slot="@(MdSelectOption.SupportingTextSlot)">@(desc)</div>
39+
40+
<MdIconButton slot="@(MdSelectOption.EndSlot)" Href="@(url)" Target="_blank">
41+
<MdIcon>open_in_new</MdIcon>
42+
</MdIconButton>
43+
</MdSelectOption>
44+
}
45+
</MdSelect>
46+
47+
<p>Selected value: @(selectedValue), selected index: @(selectedIndexes[0])</p>
48+
</div>
49+
<div class="col">
50+
<MdSelect @ref="cbo" @bind-Value="selectedStrValue" @bind-SelectedIndex="selectedIndexes[1]"
51+
class="w-100"
52+
SelectStyle="MdSelectStyle.Filled"
53+
Required="required"
54+
Quick="quick"
55+
Disabled="disabled"
56+
Error="hasError"
57+
ErrorText="@(hasError ? "Oops, someone doesn't like fruit" : null)"
58+
Label="@(hasLabel ? "Select a fruit" : null)"
59+
SupportingText="@(hasSupportingText ? "May be empty" : null)"
60+
HasLeadingIcon="leadingIcon">
61+
62+
@if (leadingIcon)
63+
{
64+
<MdIcon slot="@(MdSelect.LeadingIconSlot)">nutrition</MdIcon>
65+
}
66+
67+
<MdSelectOption Value="@("")">No thanks</MdSelectOption>
68+
<MdDivider />
69+
@for (int i = 0; i < StrValues.Length; i++)
70+
{
71+
var z = i;
72+
var value = StrValues[z];
73+
74+
<MdSelectOption Value="@(value)" @bind-Selected="strSelected[z]">
75+
<div slot="@(MdSelectOption.HeadlineSlot)">@(value)</div>
76+
</MdSelectOption>
77+
}
78+
</MdSelect>
79+
80+
<p>Selected String value: @(selectedStrValue), selected index: @(selectedIndexes[1])</p>
81+
<p><code>MdSelectOption</code> selected: @(string.Join(", ", strSelected))</p>
82+
</div>
1683
</div>
1784
18-
<MdSelect SelectStyle="style">
19-
<MdSelectOption Headline="Apple" Selected />
20-
<MdSelectOption Headline="Apricot" />
21-
</MdSelect>
2285
</ComponentDemo>
2386
<Tweaks>
87+
<div class="hstack gap-3 flex-wrap">
88+
<LabeledSwitch @bind-Selected="required" Label="Required" />
89+
<LabeledSwitch @bind-Selected="quick" Label="Quick (skip animation)" />
90+
<LabeledSwitch @bind-Selected="disabled" Label="Disabled" />
91+
<LabeledSwitch @bind-Selected="hasError" Label="Error" />
92+
<LabeledSwitch @bind-Selected="hasLabel" Label="Label" />
93+
<LabeledSwitch @bind-Selected="hasSupportingText" Label="Supporting text" />
94+
<LabeledSwitch @bind-Selected="leadingIcon" Label="Leading icon" />
95+
</div>
2496
97+
<div class="hstack gap-3">
98+
<MdButton @onclick="() => cbo.ReportValidityAsync()">Report validity</MdButton>
99+
<MdButton @onclick="SetCustomValidityAsync">Set custom validity</MdButton>
100+
<MdButton @onclick="PrintOptionsAsync">Print options</MdButton>
101+
<MdButton @onclick="PrintSelectedOptionsAsync">Print selected options</MdButton>
102+
</div>
25103
</Tweaks>
26104
</ComponentDemoPage>
27105
28106
@code {
107+
bool required = true;
108+
bool quick;
109+
bool disabled;
110+
bool hasError;
111+
bool hasLabel = true;
112+
bool hasSupportingText = true;
113+
bool leadingIcon = true;
114+
115+
int[] selectedIndexes = new int[2];
116+
117+
double selectedValue = Values[0].Value;
118+
static readonly (double Value, string Text, string Symbol, string Desc, string url)[] Values =
119+
{
120+
(
121+
Math.PI, "Pi", "π",
122+
"The ratio of a circle's circumference to its diameter",
123+
"https://en.wikipedia.org/wiki/Pi"
124+
),
125+
(
126+
Math.E, "Euler's number", "e",
127+
"The base of natural logarithms",
128+
"https://en.wikipedia.org/wiki/E_(mathematical_constant)"
129+
),
130+
(
131+
Math.Tau, "Tau", "τ",
132+
"Ratio of a circle's circumference to its radius",
133+
"https://en.wikipedia.org/wiki/Turn_(angle)#Tau_proposals"
134+
),
135+
};
136+
137+
string? selectedStrValue = StrValues[0];
138+
static readonly string[] StrValues = { "Apple", "Banana", "Cucumber", };
139+
bool[] strSelected = { true, false, false };
140+
141+
MdSelect<double> cboNum = null!;
142+
MdSelect<string?> cbo = null!;
143+
144+
async Task PrintOptionsAsync()
145+
{
146+
var options = await cbo.GetOptionsAsync();
147+
await Js.InvokeVoidAsync("console.log", options);
148+
}
149+
150+
async Task PrintSelectedOptionsAsync()
151+
{
152+
var options = await cbo.GetSelectedOptionsAsync();
153+
await Js.InvokeVoidAsync("console.log", options);
154+
}
155+
156+
async Task SetCustomValidityAsync()
157+
{
158+
await cboNum.SetCustomValidityAsync(selectedValue < 4 ?
159+
"" : "Please pick a constant smaller than 4");
29160
30-
MdSelectStyle style = MdSelectStyle.Outlined;
161+
// Must call this. See https://developer.mozilla.org/en-US/docs/Web/API/HTMLObjectElement/setCustomValidity
162+
await cboNum.ReportValidityAsync();
163+
}
31164
32165
}

BlazorMaterialWeb/EventHandlers.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,18 @@
3737
[EventHandler("onmenuopened", typeof(EventArgs), enableStopPropagation: true, enablePreventDefault: true)]
3838
[EventHandler("onmenuclosing", typeof(EventArgs), enableStopPropagation: true, enablePreventDefault: true)]
3939
[EventHandler("onmenuclosed", typeof(EventArgs), enableStopPropagation: true, enablePreventDefault: true)]
40+
41+
// Select
42+
[EventHandler("onselectinput", typeof(MdSelectChangeEventArgs), enableStopPropagation: true, enablePreventDefault: true)]
43+
[EventHandler("onselectchange", typeof(MdSelectChangeEventArgs), enableStopPropagation: true, enablePreventDefault: true)]
44+
[EventHandler("onselectopening", typeof(EventArgs), enableStopPropagation: true, enablePreventDefault: true)]
45+
[EventHandler("onselectopened", typeof(EventArgs), enableStopPropagation: true, enablePreventDefault: true)]
46+
[EventHandler("onselectclosing", typeof(EventArgs), enableStopPropagation: true, enablePreventDefault: true)]
47+
[EventHandler("onselectclosed", typeof(EventArgs), enableStopPropagation: true, enablePreventDefault: true)]
48+
49+
[EventHandler("onselectitemclosemenu", typeof(EventArgs), enableStopPropagation: true, enablePreventDefault: true)]
50+
[EventHandler("onselectitemrequestselection", typeof(EventArgs), enableStopPropagation: true, enablePreventDefault: true)]
51+
[EventHandler("onselectitemrequestdeselection", typeof(EventArgs), enableStopPropagation: true, enablePreventDefault: true)]
4052
public static class EventHandlers { }
4153

4254
public class MdCheckedEventArgs : EventArgs

BlazorMaterialWeb/Select/MdSelect.razor

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
@namespace BlazorMaterialWeb
2+
3+
@typeparam TValue where TValue: IConvertible, IEquatable<TValue>;
24
@inherits DefaultMdComponent
35

6+
@inject IJSRuntime Js;
7+
48
<DynamicTag Tag="@(TagName)" @bind-DynamicElementReference="el" @attributes="AdditionalAttributes"
5-
quick="@(Quick)"
6-
required="@(Required)"
79
value="@(Value)"
8-
selected-index="@(SelectedIndex)"
10+
quick="@(Quick)"
11+
required="@(Required)"
912
disabled="@(Disabled)"
1013
error-text="@(ErrorText)"
1114
label="@(Label)"
1215
supporting-text="@(SupportingText)"
1316
error="@(Error)"
14-
menu-fixed="@(MenuFixed)"
17+
menu-positioning="@(MenuPositioning?.ToString().ToLowerInvariant())"
1518
typeahead-delay="@(TypeaheadDelay)"
1619
has-leading-icon="@(HasLeadingIcon)"
1720
display-text="@(DisplayText)"
21+
selected-index="@(SelectedIndex)"
1822
@onselectchange="OnChangeAsync"
1923
@onselectinput="OnInput">
2024
@(ChildContent)

BlazorMaterialWeb/Select/MdSelect.razor.cs

Lines changed: 91 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,35 @@
11
namespace BlazorMaterialWeb;
22

3-
partial class MdSelect
3+
public class MdSelect
44
{
5-
[Parameter]
6-
public MdSelectStyle SelectStyle { get; set; } = MdSelectStyle.Outlined;
75

8-
[Parameter]
9-
public bool Quick { get; set; }
6+
public const string StartSlot = "start";
7+
public const string EndSlot = "end";
8+
public const string LeadingIconSlot = "leading-icon";
9+
10+
}
11+
12+
/// <summary>
13+
/// Select menus display a list of choices on temporary surfaces and display the currently selected menu item above the menu.
14+
/// <a href="https://material-web.dev/components/select/">Component</a>
15+
/// </summary>
16+
partial class MdSelect<TValue>
17+
{
1018

1119
[Parameter]
12-
public bool Required { get; set; }
20+
public MdSelectStyle SelectStyle { get; set; } = MdSelectStyle.Outlined;
1321

1422
[Parameter]
15-
public string? Value { get; set; }
23+
public TValue? Value { get; set; }
1624

1725
[Parameter]
18-
public EventCallback<string?> ValueChanged { get; set; }
26+
public EventCallback<TValue?> ValueChanged { get; set; }
1927

2028
[Parameter]
21-
public int SelectedIndex { get; set; }
29+
public bool Quick { get; set; }
2230

2331
[Parameter]
24-
public EventCallback<int> SelectedIndexChanged { get; set; }
32+
public bool Required { get; set; }
2533

2634
[Parameter]
2735
public bool Disabled { get; set; }
@@ -39,7 +47,7 @@ partial class MdSelect
3947
public bool Error { get; set; }
4048

4149
[Parameter]
42-
public bool MenuFixed { get; set; }
50+
public MdMenuPositioning? MenuPositioning { get; set; }
4351

4452
[Parameter]
4553
public int? TypeaheadDelay { get; set; }
@@ -50,18 +58,51 @@ partial class MdSelect
5058
[Parameter]
5159
public string? DisplayText { get; set; }
5260

61+
[Parameter]
62+
public int SelectedIndex { get; set; }
63+
64+
[Parameter]
65+
public EventCallback<int> SelectedIndexChanged { get; set; }
66+
5367
[Parameter]
5468
public EventCallback<MdSelectChangeEventArgs> OnChange { get; set; }
5569

5670
[Parameter]
5771
public EventCallback<MdSelectChangeEventArgs> OnInput { get; set; }
5872

73+
[Parameter]
74+
public EventCallback OnOpening { get; set; }
75+
76+
[Parameter]
77+
public EventCallback OnOpened { get; set; }
78+
79+
[Parameter]
80+
public EventCallback OnClosing { get; set; }
81+
82+
[Parameter]
83+
public EventCallback OnClosed { get; set; }
84+
5985
async Task OnChangeAsync(MdSelectChangeEventArgs e)
6086
{
61-
if (e.Value != Value)
87+
TValue? value;
88+
try
89+
{
90+
value = (TValue?)Convert.ChangeType(e.Value, typeof(TValue));
91+
}
92+
catch (Exception ex)
93+
{
94+
if (ex is FormatException || ex is InvalidCastException)
95+
{
96+
return;
97+
}
98+
99+
throw;
100+
}
101+
102+
if (value?.Equals(Value) != true)
62103
{
63-
Value = e.Value;
64-
await ValueChanged.InvokeAsync(Value);
104+
Value = value;
105+
await ValueChanged.InvokeAsync(value);
65106
}
66107

67108
if (e.SelectedIndex != SelectedIndex)
@@ -73,6 +114,30 @@ async Task OnChangeAsync(MdSelectChangeEventArgs e)
73114
await OnChange.InvokeAsync(e);
74115
}
75116

117+
public async Task<IJSObjectReference> GetOptionsAsync() =>
118+
await Js.GetElementPropertyAsync<IJSObjectReference>(el, "options");
119+
120+
public async Task<IJSObjectReference> GetSelectedOptionsAsync() =>
121+
await Js.GetElementPropertyAsync<IJSObjectReference>(el, "selectedOptions");
122+
123+
public async Task ResetAsync() =>
124+
await InvokeElAsync("reset");
125+
126+
public async Task<bool> CheckValidityAsync() =>
127+
await InvokeElAsync<bool>("checkValidity");
128+
129+
public async Task ReportValidityAsync() =>
130+
await InvokeElAsync("reportValidity");
131+
132+
public async Task SetCustomValidityAsync(string message) =>
133+
await InvokeElAsync("setCustomValidity", message);
134+
135+
async Task InvokeElAsync(string name, params object[] args) =>
136+
await Js.InvokeElementMethodAsync(el, name, args);
137+
138+
async Task<T> InvokeElAsync<T>(string name, params object[] args) =>
139+
await Js.InvokeElementMethodAsync<T>(el, name, args);
140+
76141
string TagName => SelectStyle switch
77142
{
78143
MdSelectStyle.Outlined => "md-outlined-select",
@@ -81,8 +146,20 @@ async Task OnChangeAsync(MdSelectChangeEventArgs e)
81146
};
82147
}
83148

149+
/// <summary>
150+
/// Provide by Blazor to merge two styles
151+
/// </summary>
84152
public enum MdSelectStyle
85153
{
86154
Outlined,
87155
Filled,
156+
}
157+
158+
/// <summary>
159+
/// From select.ts
160+
/// </summary>
161+
public enum MdSelectPositioning
162+
{
163+
Absolute,
164+
Fixed,
88165
}

BlazorMaterialWeb/Select/MdSelectOption.cs

Lines changed: 0 additions & 11 deletions
This file was deleted.

0 commit comments

Comments
 (0)