Skip to content

Commit

Permalink
[Rating] New rating component (#2258)
Browse files Browse the repository at this point in the history
* Add rating component

* Fix IconVariant

* Add IconColor

* Fix page name

* Add support disabled

* Add support label

* Remove area not overable

* Imprve icona name

* Add icon color

* Add support arrowup/arrowdown

* Improve sample

* Add event HoveredValueChanged

* Add IconCustomColor

* Fix aria-readonly

* Fix LabelTemplate

* Improve code style

* Improve icon name

* Add IconWidth

* Fix label documentation

* Fix default value

* Fix HoveredValueChanged to OnPointerOver

* Add AllowReset and rewrite HandleKeyDownAsync

* Add AllowReset

* Rewrite key down event

* Fix Style and Class

* Component explanation

* Improve code

* Add Unit test

* Fix IconWidth like React Component

---------

Co-authored-by: Vincent Baaij <[email protected]>
  • Loading branch information
franklupo and vnbaaij authored Jun 27, 2024
1 parent 65db3c4 commit fd201df
Show file tree
Hide file tree
Showing 12 changed files with 520 additions and 1 deletion.
49 changes: 49 additions & 0 deletions examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7559,6 +7559,55 @@
<member name="M:Microsoft.FluentUI.AspNetCore.Components.FluentRadioGroup`1.TryParseValueFromString(System.String,`0@,System.String@)">
<inheritdoc />
</member>
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentRating.MaxValue">
<summary>
Gets or sets the maximum value.
</summary>
</member>
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentRating.IconColor">
<summary>
Gets or sets the icon drawing and fill color.
Value comes from the <see cref="T:Microsoft.FluentUI.AspNetCore.Components.Color"/> enumeration. Defaults to Accent.
</summary>
</member>
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentRating.IconCustomColor">
<summary>
Gets or sets the icon drawing and fill color to a custom value.
Needs to be formatted as an HTML hex color string (#rrggbb or #rgb) or CSS variable.
⚠️ Only available when Color is set to Color.Custom.
</summary>
</member>
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentRating.IconWidth">
<summary>
The icon width.
</summary>
</member>
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentRating.IconFilled">
<summary>
The icon to display when the rating value is greater than or equal to the item's value.
</summary>
</member>
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentRating.IconOutline">
<summary>
The icon to display when the rating value is less than the item's value.
</summary>
</member>
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentRating.AllowReset">
<summary>
Gets or sets a value that whether to allow clear when click again.
</summary>
</member>
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentRating.OnPointerOver">
<summary>
Fires when hovered value changes. Value will be null if no rating item is hovered.
</summary>
</member>
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentRating.ClassValue">
<summary />
</member>
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentRating.StyleValue">
<summary />
</member>
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentSearch.JSRuntime">
<summary />
</member>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

<FluentRating MaxValue="10" Value="5" Label="Test"/>
38 changes: 38 additions & 0 deletions examples/Demo/Shared/Pages/Rating/Examples/RatingEvent.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<h4>Event</h4>

<FluentStack Orientation="Orientation.Vertical">
<FluentRating MaxValue="10"
OnPointerOver="OnPointerOver"
@bind-Value="@_value" />

<div>Value: @_value</div>
<div>Hovered value: @_overedValue</div>
<div>Hovered text value: @_overedTextValue</div>
</FluentStack>

@code
{
int _value = 2;
int? _overedValue;
string _overedTextValue = default!;

private void OnPointerOver(int? value)
{
_overedValue = value;
_overedTextValue = value.HasValue
? new string[]
{
"Very bad",
"Bad",
"Sufficient -2",
"Sufficient -1",
"Sufficient",
"Good -4",
"Good -3",
"Good -2" ,
"Good -1",
"Good"
}[value.Value - 1]
: string.Empty;
}
}
69 changes: 69 additions & 0 deletions examples/Demo/Shared/Pages/Rating/Examples/RatingExample.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<h4>Example</h4>

<FluentStack Orientation="Orientation.Vertical">
<FluentCheckbox Label="Read Only" @bind-Value="@_readOnly" />

<FluentCheckbox Label="Allow reset" @bind-Value="@_allowReset" />

<FluentCheckbox Label="Disabled" @bind-Value="@_disabled" />

<FluentNumberField TValue="int" Label="Max Value" @bind-Value="@_maxValue" />

<FluentSelect Label="Color"
@bind-SelectedOption="@_iconColor"
Style="min-width: 100px;"
Items="@(Enum.GetValues<Color>().Where(i => i != Color.Custom))"
OptionValue="@(i => i.ToAttributeValue())" />

<FluentSelect TOption="string"
Label="Icons Full/Empty"
SelectedOptionChanged="SelectedOptionChanged"
Style="min-width: 100px;"
Items="_icons" />

<FluentRating MaxValue="@_maxValue"
@bind-Value="@_value"
IconFilled="@_iconFilled"
IconOutline="@_iconOutline"
ReadOnly="@_readOnly"
IconColor="@_iconColor"
Disabled="@_disabled" ,
AllowReset="@_allowReset" />

<div>Value: @_value</div>
</FluentStack>

@code
{
bool _readOnly;
bool _disabled;
bool _allowReset;
int _maxValue = 10;
int _value = 2;
Color _iconColor = Color.Accent;

Icon _iconFilled = new Icons.Filled.Size20.Star();
Icon _iconOutline = new Icons.Regular.Size20.Star();
List<string> _icons = ["Star", "Heart", "Alert", "PersonCircle"];

private void SelectedOptionChanged(string name) => SetIcon(_icons.IndexOf(name));

private void SetIcon(int index)
{
_iconFilled = new Icon[]
{ new Icons.Filled.Size20.Star(),
new Icons.Filled.Size20.Heart(),
new Icons.Filled.Size20.Alert(),
new Icons.Filled.Size20.PersonCircle(),
}[index];

_iconOutline = new Icon[]
{ new Icons.Regular.Size20.Star(),
new Icons.Regular.Size20.Heart(),
new Icons.Regular.Size20.Alert(),
new Icons.Regular.Size20.PersonCircle(),
}[index];
}

protected override void OnParametersSet() => SetIcon(0);
}
30 changes: 30 additions & 0 deletions examples/Demo/Shared/Pages/Rating/RatingPage.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
@page "/Rating"
@using FluentUI.Demo.Shared.Pages.Rating.Examples

<PageTitle>@App.PageTitle("Rating")</PageTitle>

<h1>Rating</h1>

<p>
A <code>&lt;FluentRating&gt;</code> component allows users to provide a rating for a particular item.
<code>&lt;FluentRating&gt;</code> allows customers to determine a sense of value of a good or a service.
By default, the rating is selected out of 5 stars, but the number and symbol used can be customized.
</p>

<h2 id="example">Examples</h2>

<DemoSection Title="Default" Component="@typeof(RatingDefault)"></DemoSection>

<DemoSection Title="Example" Component="@typeof(RatingExample)"></DemoSection>


<h2 id="a11y">Accessibility</h2>
<p>
You can use the <kbd>arrow</kbd> keys to increase or decrease the value. Pressing <kbd>Shift+arrow</kbd> changes the value to 0 or the maximum value.
</p>

<DemoSection Title="Event" Component="@typeof(RatingEvent)"></DemoSection>

<h2 id="documentation">Documentation</h2>

<ApiDocumentation Component="typeof(FluentRating)" />
5 changes: 5 additions & 0 deletions examples/Demo/Shared/Shared/DemoNavProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,11 @@ public DemoNavProvider()
icon: new Icons.Regular.Size20.RadioButton(),
title: "Radio Group"
),
new NavLink(
href: "/Rating",
icon: new Icons.Regular.Size20.Star(),
title: "Rating"
),
new NavLink(
href: "/Search",
icon: new Icons.Regular.Size20.SearchSquare(),
Expand Down
4 changes: 3 additions & 1 deletion src/Core/Components/Icons/CoreIcons.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ public class Info : Icon { public Info() : base("Info", IconVariant.Filled, Icon
public class Warning : Icon { public Warning() : base("Warning", IconVariant.Filled, IconSize.Size20, "<path d=\"M8.68 2.79a1.5 1.5 0 0 1 2.64 0l6.5 12A1.5 1.5 0 0 1 16.5 17h-13a1.5 1.5 0 0 1-1.32-2.21l6.5-12ZM10.5 7.5a.5.5 0 0 0-1 0v4a.5.5 0 0 0 1 0v-4Zm.25 6.25a.75.75 0 1 0-1.5 0 .75.75 0 0 0 1.5 0Z\"/>") { } }
public class CheckboxChecked : Icon { public CheckboxChecked() : base("CheckboxChecked", IconVariant.Filled, IconSize.Size20, "<path d=\"M6 3a3 3 0 0 0-3 3v8a3 3 0 0 0 3 3h8a3 3 0 0 0 3-3V6a3 3 0 0 0-3-3H6Zm7.85 4.85-5 5a.5.5 0 0 1-.7 0l-2-2a.5.5 0 0 1 .7-.7l1.65 1.64 4.65-4.64a.5.5 0 0 1 .7.7Z\" />") { } }
public class CheckboxIndeterminate : Icon { public CheckboxIndeterminate() : base("CheckboxIndeterminate", IconVariant.Filled, IconSize.Size20, "<path d=\"M6 3a3 3 0 0 0-3 3v8a3 3 0 0 0 3 3h8a3 3 0 0 0 3-3V6a3 3 0 0 0-3-3H6ZM4.5 6c0-.83.67-1.5 1.5-1.5h8c.83 0 1.5.67 1.5 1.5v8c0 .83-.67 1.5-1.5 1.5H6A1.5 1.5 0 0 1 4.5 14V6ZM7 6a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V7a1 1 0 0 0-1-1H7Z\"></path><path d=\"M6 3a3 3 0 0 0-3 3v8a3 3 0 0 0 3 3h8a3 3 0 0 0 3-3V6a3 3 0 0 0-3-3H6ZM4.5 6c0-.83.67-1.5 1.5-1.5h8c.83 0 1.5.67 1.5 1.5v8c0 .83-.67 1.5-1.5 1.5H6A1.5 1.5 0 0 1 4.5 14V6ZM7 6a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V7a1 1 0 0 0-1-1H7Z\"></path>") { } }
public class RadioButton : Icon { public RadioButton() : base("RadioButton", IconVariant.Filled, IconSize.Size20, "<path d=\"M10 15a5 5 0 1 0 0-10 5 5 0 0 0 0 10Zm0-13a8 8 0 1 0 0 16 8 8 0 0 0 0-16Zm-7 8a7 7 0 1 1 14 0 7 7 0 0 1-14 0Z\" />") { } };
public class RadioButton : Icon { public RadioButton() : base("RadioButton", IconVariant.Regular, IconSize.Size20, "<path d=\"M10 15a5 5 0 1 0 0-10 5 5 0 0 0 0 10Zm0-13a8 8 0 1 0 0 16 8 8 0 0 0 0-16Zm-7 8a7 7 0 1 1 14 0 7 7 0 0 1-14 0Z\" />") { } };
public class Star : Icon { public Star() : base("Star", IconVariant.Filled, IconSize.Size20, "<path d=\"M9.1 2.9a1 1 0 0 1 1.8 0l1.93 3.91 4.31.63a1 1 0 0 1 .56 1.7l-3.12 3.05.73 4.3a1 1 0 0 1-1.45 1.05L10 15.51l-3.86 2.03a1 1 0 0 1-1.45-1.05l.74-4.3L2.3 9.14a1 1 0 0 1 .56-1.7l4.31-.63L9.1 2.9Z\"></path>") { } };
}
}

Expand Down Expand Up @@ -99,6 +100,7 @@ public class Dismiss : Icon { public Dismiss() : base("Dismiss", IconVariant.Reg
public class DismissCircle : Icon { public DismissCircle() : base("DismissCircle", IconVariant.Regular, IconSize.Size20, "<path d=\"M10 2a8 8 0 1 1 0 16 8 8 0 0 1 0-16Zm0 1a7 7 0 1 0 0 14 7 7 0 0 0 0-14ZM7.8 7.11l.08.06L10 9.3l2.12-2.12a.5.5 0 0 1 .64-.06l.07.06c.17.18.2.44.06.64l-.06.07L10.7 10l2.12 2.12c.17.17.2.44.06.64l-.06.07a.5.5 0 0 1-.64.06l-.07-.06L10 10.7l-2.12 2.12a.5.5 0 0 1-.64.06l-.07-.06a.5.5 0 0 1-.06-.64l.06-.07L9.3 10 7.17 7.88a.5.5 0 0 1-.06-.64l.06-.07a.5.5 0 0 1 .64-.06Z\"/>") { } }
public class CheckboxUnchecked : Icon { public CheckboxUnchecked() : base("CheckboxUnchecked", IconVariant.Regular, IconSize.Size20, "<path d=\"M3 6a3 3 0 0 1 3-3h8a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V6Zm3-2a2 2 0 0 0-2 2v8c0 1.1.9 2 2 2h8a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H6Z\" />") { } }
public class RadioButton : Icon { public RadioButton() : base("RadioButton", IconVariant.Regular, IconSize.Size20, "<path d=\"M10 3a7 7 0 1 0 0 14 7 7 0 0 0 0-14Zm-8 7a8 8 0 1 1 16 0 8 8 0 0 1-16 0Z\" />") { } };
public class Star : Icon { public Star() : base("Star", IconVariant.Regular, IconSize.Size20, "<path d=\"M9.1 2.9a1 1 0 0 1 1.8 0l1.93 3.91 4.31.63a1 1 0 0 1 .56 1.7l-3.12 3.05.73 4.3a1 1 0 0 1-1.45 1.05L10 15.51l-3.86 2.03a1 1 0 0 1-1.45-1.05l.74-4.3L2.3 9.14a1 1 0 0 1 .56-1.7l4.31-.63L9.1 2.9Zm.9.44L8.07 7.25a1 1 0 0 1-.75.55L3 8.43l3.12 3.04a1 1 0 0 1 .3.89l-.75 4.3 3.87-2.03a1 1 0 0 1 .93 0l3.86 2.03-.74-4.3a1 1 0 0 1 .29-.89L17 8.43l-4.32-.63a1 1 0 0 1-.75-.55L10 3.35Z\"></path>") { } };
}
}

Expand Down
43 changes: 43 additions & 0 deletions src/Core/Components/Rating/FluentRating.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
@namespace Microsoft.FluentUI.AspNetCore.Components
@inherits FluentInputBase<int>

@if (!ReadOnly && !Disabled)
{
<FluentKeyCode Anchor="@Id"
PreventDefaultOnly="new[] { KeyCode.Left, KeyCode.Right, KeyCode.Up, KeyCode.Down, KeyCode.Shift }"
StopPropagation="@true"
OnKeyDown="@HandleKeyDownAsync" />
}

<FluentInputLabel ForId="@Id" Label="@Label" AriaLabel="@AriaLabel" Required="@Required">@LabelTemplate</FluentInputLabel>

<FluentStack Id="@Id"
Orientation="Orientation.Horizontal"
Class="@ClassValue"
Style="@StyleValue"
role="radiogroup"
tabindex="@(Disabled ? -1 : 0)"
aria-readonly="@ReadOnly.ToAttributeValue()"
HorizontalGap="0">
@for (int i = 1; i <= MaxValue; i++)
{
var currentValue = i;
@if (ReadOnly || Disabled)
{
<FluentIcon Value="@GetIcon(currentValue)"
Width="@IconWidth"
Color="@(Disabled && IconColor != Color.Custom ? Color.Disabled : IconColor)"
CustomColor="@IconCustomColor" />
}
else
{
<FluentIcon Value="@GetIcon(currentValue)"
Width="@IconWidth"
Color="IconColor"
CustomColor="@IconCustomColor"
OnClick="@(() => OnClickAsync(currentValue))"
@onpointerover="@(async () => await OnPointerOverAsync(currentValue))"
@onpointerout="@(async () => await OnPointerOutAsync())" />
}
}
</FluentStack>
Loading

0 comments on commit fd201df

Please sign in to comment.