diff --git a/docs/LumexUI.Docs.Client/Common/Navigation/NavigationStore.cs b/docs/LumexUI.Docs.Client/Common/Navigation/NavigationStore.cs index 22047e54..10fae974 100644 --- a/docs/LumexUI.Docs.Client/Common/Navigation/NavigationStore.cs +++ b/docs/LumexUI.Docs.Client/Common/Navigation/NavigationStore.cs @@ -39,6 +39,7 @@ public class NavigationStore .Add( new( nameof( LumexRadioGroup), ComponentStatus.New ) ) .Add( new( nameof( LumexSelect ), ComponentStatus.New ) ) .Add( new( nameof( LumexSwitch ) ) ) + .Add( new( nameof( LumexTextArea ), ComponentStatus.Preview ) ) .Add( new( nameof( LumexTextbox ) ) ); private static NavigationCategory ComponentsApiCategory => @@ -78,6 +79,7 @@ public class NavigationStore .Add( new( nameof( LumexSelect ) ) ) .Add( new( nameof( LumexSelectItem ) ) ) .Add( new( nameof( LumexSwitch ) ) ) + .Add( new( nameof( LumexTextArea ) ) ) .Add( new( nameof( LumexTextbox ) ) ) .Add( new( nameof( LumexThemeProvider ) ) ); diff --git a/docs/LumexUI.Docs.Client/Pages/Components/TextArea/Examples/Controlled.razor b/docs/LumexUI.Docs.Client/Pages/Components/TextArea/Examples/Controlled.razor new file mode 100644 index 00000000..f2f79b6d --- /dev/null +++ b/docs/LumexUI.Docs.Client/Pages/Components/TextArea/Examples/Controlled.razor @@ -0,0 +1,19 @@ +
+ +
+ Changed Value: + @_newValue +
+
+ +@code { + + private string _value = string.Empty; + private string? _newValue = string.Empty; + + private void OnValueChanged( string? value ) + { + _newValue = value; + StateHasChanged(); + } +} \ No newline at end of file diff --git a/docs/LumexUI.Docs.Client/Pages/Components/TextArea/Examples/Disabled.razor b/docs/LumexUI.Docs.Client/Pages/Components/TextArea/Examples/Disabled.razor new file mode 100644 index 00000000..83cba2a7 --- /dev/null +++ b/docs/LumexUI.Docs.Client/Pages/Components/TextArea/Examples/Disabled.razor @@ -0,0 +1,4 @@ +
+ + +
diff --git a/docs/LumexUI.Docs.Client/Pages/Components/TextArea/Examples/ReadOnly.razor b/docs/LumexUI.Docs.Client/Pages/Components/TextArea/Examples/ReadOnly.razor new file mode 100644 index 00000000..d8c657ef --- /dev/null +++ b/docs/LumexUI.Docs.Client/Pages/Components/TextArea/Examples/ReadOnly.razor @@ -0,0 +1,3 @@ +
+ +
diff --git a/docs/LumexUI.Docs.Client/Pages/Components/TextArea/Examples/Usage.razor b/docs/LumexUI.Docs.Client/Pages/Components/TextArea/Examples/Usage.razor new file mode 100644 index 00000000..53f03e99 --- /dev/null +++ b/docs/LumexUI.Docs.Client/Pages/Components/TextArea/Examples/Usage.razor @@ -0,0 +1,4 @@ +
+ + +
diff --git a/docs/LumexUI.Docs.Client/Pages/Components/TextArea/PreviewCodes/Controlled.razor b/docs/LumexUI.Docs.Client/Pages/Components/TextArea/PreviewCodes/Controlled.razor new file mode 100644 index 00000000..14b46fae --- /dev/null +++ b/docs/LumexUI.Docs.Client/Pages/Components/TextArea/PreviewCodes/Controlled.razor @@ -0,0 +1,5 @@ +@rendermode InteractiveWebAssembly + + + + diff --git a/docs/LumexUI.Docs.Client/Pages/Components/TextArea/PreviewCodes/Disabled.razor b/docs/LumexUI.Docs.Client/Pages/Components/TextArea/PreviewCodes/Disabled.razor new file mode 100644 index 00000000..7ce526f4 --- /dev/null +++ b/docs/LumexUI.Docs.Client/Pages/Components/TextArea/PreviewCodes/Disabled.razor @@ -0,0 +1,5 @@ +@rendermode InteractiveWebAssembly + + + + diff --git a/docs/LumexUI.Docs.Client/Pages/Components/TextArea/PreviewCodes/ReadOnly.razor b/docs/LumexUI.Docs.Client/Pages/Components/TextArea/PreviewCodes/ReadOnly.razor new file mode 100644 index 00000000..8ac2971d --- /dev/null +++ b/docs/LumexUI.Docs.Client/Pages/Components/TextArea/PreviewCodes/ReadOnly.razor @@ -0,0 +1,5 @@ +@rendermode InteractiveWebAssembly + + + + diff --git a/docs/LumexUI.Docs.Client/Pages/Components/TextArea/PreviewCodes/Usage.razor b/docs/LumexUI.Docs.Client/Pages/Components/TextArea/PreviewCodes/Usage.razor new file mode 100644 index 00000000..e80122fd --- /dev/null +++ b/docs/LumexUI.Docs.Client/Pages/Components/TextArea/PreviewCodes/Usage.razor @@ -0,0 +1,5 @@ +@rendermode InteractiveWebAssembly + + + + diff --git a/docs/LumexUI.Docs.Client/Pages/Components/TextArea/TextArea.razor b/docs/LumexUI.Docs.Client/Pages/Components/TextArea/TextArea.razor new file mode 100644 index 00000000..8b4e399b --- /dev/null +++ b/docs/LumexUI.Docs.Client/Pages/Components/TextArea/TextArea.razor @@ -0,0 +1,63 @@ +@page "/docs/components/text-area" +@layout DocsContentLayout + +@using LumexUI.Docs.Client.Pages.Components.TextArea.PreviewCodes + + +

The textarea component provides a simple way to input and edit multiline text data.

+ + + +

+ You can disable a textarea to prevent user interaction. + A disabled textarea is faded and does not respond to user clicks. +

+ + +
+ + +

+ You can make a textarea's value read-only to prevent changes. +

+ + +
+ + +

+ It is possible to get an event after changing the value. If you set the Intermediate + flag, the event will be called on every input. Otherwise the event will be called after + leaving the textarea. +

+ + +
+
+ +@code { + [CascadingParameter] private DocsContentLayout Layout { get; set; } = default!; + + private readonly Heading[] _headings = + [ + new("Usage", [ + new("Disabled"), + new("ReadOnly"), + new("Controlled"), + ]), + new("Custom Styles"), + new("API") + ]; + + protected override void OnInitialized() + { + Layout.Initialize( + title: "TextArea", + category: "Components", + description: "TextArea allows users to input multiline text data.", + headings: _headings, + linksProps: new ComponentLinksProps( "TextArea", isServer: false ) + ); + } + +} \ No newline at end of file diff --git a/src/LumexUI/Components/TextArea/LumexTextArea.razor b/src/LumexUI/Components/TextArea/LumexTextArea.razor new file mode 100644 index 00000000..7b636c26 --- /dev/null +++ b/src/LumexUI/Components/TextArea/LumexTextArea.razor @@ -0,0 +1,18 @@ +@namespace LumexUI +@inherits LumexComponentBase + + diff --git a/src/LumexUI/Components/TextArea/LumexTextArea.razor.cs b/src/LumexUI/Components/TextArea/LumexTextArea.razor.cs new file mode 100644 index 00000000..dd1d557a --- /dev/null +++ b/src/LumexUI/Components/TextArea/LumexTextArea.razor.cs @@ -0,0 +1,71 @@ +using LumexUI.Common; +using LumexUI.Styles; + +using Microsoft.AspNetCore.Components; + +namespace LumexUI; + +public partial class LumexTextArea : LumexComponentBase +{ + /// + /// Gets or sets a value indicating whether the textarea is disabled. + /// + [Parameter] public bool Disabled { get; set; } + + /// + /// Gets or sets a value indicating whether the textareas value is readonly. + /// + [Parameter] public bool ReadOnly { get; set; } + + /// + /// Gets or sets a value indicating whether the textareas value is required. + /// + [Parameter] public bool Required { get; set; } + + /// + /// /// Gets or sets a value indicating whether the ValueChanged event will be fired on input or change. + /// + [Parameter] public InputBehavior Behavior { get; set; } = InputBehavior.OnChange; + + /// + /// Gets or sets a value indicating whether the textarea has full width. + /// + [Parameter] public bool FullWidth { get; set; } + + /// + /// Gets or sets the text value. + /// + [Parameter] public string? Value { get; set; } + + /// + /// Gets or sets the placeholder to show if the value is empty. + /// + [Parameter] public string? Placeholder { get; set; } + + /// + /// Gets or sets Value changed event. + /// + [Parameter] public EventCallback ValueChanged { get; set; } + + /// + /// Get the merged style of this instance. + /// + private protected override string? RootClass => + TwMerge.Merge( Textarea.GetStyles( this ) ); + + /// + /// Initializes a new instance of the . + /// + public LumexTextArea() + { + As = "textarea"; + } + + /// + /// Event wrapper function. + /// + private void OnValueChanged() + { + ValueChanged.InvokeAsync( Value ); + } +} \ No newline at end of file diff --git a/src/LumexUI/Styles/Textarea.cs b/src/LumexUI/Styles/Textarea.cs new file mode 100644 index 00000000..c48ae0c5 --- /dev/null +++ b/src/LumexUI/Styles/Textarea.cs @@ -0,0 +1,50 @@ +// Copyright (c) LumexUI 2024 +// LumexUI licenses this file to you under the MIT license +// See the license here https://github.com/LumexUI/lumexui/blob/main/LICENSE + +using LumexUI.Utilities; + +namespace LumexUI.Styles; + +internal class Textarea +{ + private readonly static string _base = ElementClass.Empty() + .Add( "flex" ) + .Add( "min-h-[60px]" ) + .Add( "rounded-medium" ) + .Add( "border" ) + .Add( "border-input" ) + .Add( "bg-default-100" ) + .Add( "px-3" ) + .Add( "py-2" ) + .Add( "text-sm" ) + .Add( "text-default-600" ) + .Add( "placeholder:text-muted-foreground" ) + .ToString(); + + private readonly static string _focus = ElementClass.Empty() + .Add( "focus-visible:outline-none" ) + .Add( "focus-visible:ring-0" ) + .ToString(); + + private readonly static string _disabled = ElementClass.Empty() + .Add( "opacity-disabled" ) + .Add( "opacity-50" ) + .Add( "cursor-not-allowed" ) + .Add( "pointer-events-none" ) + .ToString(); + + private readonly static string _fullWidth = ElementClass.Empty() + .Add( "w-full" ) + .ToString(); + + public static string GetStyles( LumexTextArea textarea ) + { + return ElementClass.Empty() + .Add( _base ) + .Add( _focus ) + .Add( _disabled, when: textarea.Disabled ) + .Add( _fullWidth, when: textarea.FullWidth ) + .ToString(); + } +} \ No newline at end of file diff --git a/tests/LumexUI.Tests/Components/Textarea/TextAreaTests.razor b/tests/LumexUI.Tests/Components/Textarea/TextAreaTests.razor new file mode 100644 index 00000000..5287c2ab --- /dev/null +++ b/tests/LumexUI.Tests/Components/Textarea/TextAreaTests.razor @@ -0,0 +1,89 @@ +@namespace LumexUI.Tests.Components +@inherits TestContext +@using LumexUI.Common +@using TailwindMerge +@using Microsoft.Extensions.DependencyInjection + +@code { + public TextAreaTests() + { + Services.AddSingleton(); + var module = JSInterop.SetupModule( "./_content/LumexUI/js/components/input.js" ); + module.Setup( "input.getValidationMessage", _ => true ); + } + + [Fact] + public void ShouldRenderCorrectly() + { + var action = () => Render(@); + + action.Should().NotThrow(); + } + + [Fact] + public void ShouldRenderWithPlaceholder() + { + var cut = Render(@); + + var input = cut.Find( "textarea" ); + + input.HasAttribute( "placeholder" ).Should().BeTrue(); + input.GetAttribute( "placeholder" ).Should().Be( "Test" ); + } + + [Fact] + public void ShouldRenderWithPlaceholderButShowsValue() + { + var cut = Render(@); + + var input = cut.Find( "textarea" ); + + input.HasAttribute( "placeholder" ).Should().BeTrue(); + input.GetAttribute( "placeholder" ).Should().Be( "Test" ); + input.HasAttribute( "value" ).Should().BeTrue(); + input.GetAttribute( "value" ).Should().Be( "Hello World!" ); + } + + [Fact] + public void ShouldRenderReadOnly() + { + var cut = Render(@); + + var input = cut.Find( "textarea" ); + + input.HasAttribute( "readonly" ).Should().BeTrue(); + } + + [Fact] + public void ShouldRenderDisabled() + { + var cut = Render(@); + + var input = cut.Find( "textarea" ); + + input.HasAttribute( "disabled" ).Should().BeTrue(); + } + + [Fact] + public void ShouldChangeValueOnInput() + { + var cut = Render( @); + + var input = cut.Find( "textarea" ); + + input.Input( "test 2" ); + cut.Instance.Value.Should().Be( "test 2" ); + } + + [Fact] + public void ShouldChangeValueOnValueChangedEvent() + { + var text = string.Empty; + var cut = Render( @); + + var input = cut.Find( "textarea" ); + + input.Input( "test 2" ); + text.Should().Be( "test 2" ); + } +}