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
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public class NavigationStore
.Add( new( nameof( LumexRadioGroup<T>), ComponentStatus.New ) )
.Add( new( nameof( LumexSelect<T> ), ComponentStatus.New ) )
.Add( new( nameof( LumexSwitch ) ) )
.Add( new( nameof( LumexTextArea ), ComponentStatus.Preview ) )
.Add( new( nameof( LumexTextbox ) ) );

private static NavigationCategory ComponentsApiCategory =>
Expand Down Expand Up @@ -78,6 +79,7 @@ public class NavigationStore
.Add( new( nameof( LumexSelect<T> ) ) )
.Add( new( nameof( LumexSelectItem<T> ) ) )
.Add( new( nameof( LumexSwitch ) ) )
.Add( new( nameof( LumexTextArea ) ) )
.Add( new( nameof( LumexTextbox ) ) )
.Add( new( nameof( LumexThemeProvider ) ) );

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<div class="w-full flex flex-col flex-wrap gap-4 md:flex-nowrap">
<LumexTextArea Placeholder="Enter your email" ValueChanged="@OnValueChanged" />
<div class="flex flex-col items-center text-sm">
<span>Changed Value:</span>
<span>@_newValue</span>
</div>
</div>

@code {

private string _value = string.Empty;
private string? _newValue = string.Empty;
Comment on lines +11 to +12
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove unused field and improve null handling

The _value field is never used, and the null handling could be improved.

-	private string _value = string.Empty;
-	private string? _newValue = string.Empty;
+	private string _newValue = string.Empty;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private string _value = string.Empty;
private string? _newValue = string.Empty;
private string _newValue = string.Empty;


private void OnValueChanged( string? value )
{
_newValue = value;
StateHasChanged();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<div class="w-full flex flex-wrap gap-4 md:flex-nowrap">
<LumexTextArea Disabled />
<LumexTextArea Disabled Placeholder="Enter your email" />
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div class="w-full flex flex-wrap gap-4 md:flex-nowrap">
<LumexTextArea ReadOnly Value="Lorem ipsum..." />
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<div class="w-full flex flex-wrap gap-4 md:flex-nowrap">
<LumexTextArea Value="LumexUI Textarea sample text..." />
<LumexTextArea Placeholder="Enter your email" />
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@rendermode InteractiveWebAssembly

<PreviewCode Code="@new(name: null, snippet: "TextArea.Code.Controlled")">
<LumexUI.Docs.Client.Pages.Components.TextArea.Examples.Controlled />
</PreviewCode>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@rendermode InteractiveWebAssembly

<PreviewCode Code="@new(name: null, snippet: "TextArea.Code.Disabled")">
<LumexUI.Docs.Client.Pages.Components.TextArea.Examples.Disabled />
</PreviewCode>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@rendermode InteractiveWebAssembly

<PreviewCode Code="@new(name: null, snippet: "TextArea.Code.ReadOnly")">
<LumexUI.Docs.Client.Pages.Components.TextArea.Examples.ReadOnly />
</PreviewCode>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@rendermode InteractiveWebAssembly

<PreviewCode Code="@new(name: null, snippet: "TextArea.Code.Usage")">
<LumexUI.Docs.Client.Pages.Components.TextArea.Examples.Usage />
</PreviewCode>
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
@page "/docs/components/text-area"
@layout DocsContentLayout

@using LumexUI.Docs.Client.Pages.Components.TextArea.PreviewCodes

<DocsSection Title="Usage">
<p>The textarea component provides a simple way to input and edit multiline text data.</p>
<Usage />

<DocsSection Title="Disabled">
<p>
You can disable a textarea to prevent user interaction.
A disabled textarea is faded and does not respond to user clicks.
</p>

<Disabled />
</DocsSection>

<DocsSection Title="ReadOnly">
<p>
You can make a textarea's value read-only to prevent changes.
</p>

<ReadOnly />
</DocsSection>

<DocsSection Title="Controlled">
<p>
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.
</p>

<Controlled />
</DocsSection>
</DocsSection>

@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 )
);
}

}
18 changes: 18 additions & 0 deletions src/LumexUI/Components/TextArea/LumexTextArea.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
@namespace LumexUI
@inherits LumexComponentBase

<textarea
class="@RootClass"
style="@RootStyle"
disabled="@Disabled"
readonly="@ReadOnly"
aria-disabled="@Disabled"
aria-readonly="@ReadOnly"
placeholder="@Placeholder"
required="@Required"
data-disabled="@Disabled.ToAttributeValue()"
@bind="Value"
@bind:event="@(Behavior == InputBehavior.OnInput ? "oninput" : "onchange")"
@bind:after="OnValueChanged"
@attributes="@AdditionalAttributes" >
</textarea>
71 changes: 71 additions & 0 deletions src/LumexUI/Components/TextArea/LumexTextArea.razor.cs
Copy link
Contributor

Choose a reason for hiding this comment

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

As per functionality, follow the official Blazor InputTextArea

Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using LumexUI.Common;
using LumexUI.Styles;

using Microsoft.AspNetCore.Components;

namespace LumexUI;

public partial class LumexTextArea : LumexComponentBase
{
/// <summary>
/// Gets or sets a value indicating whether the textarea is disabled.
/// </summary>
[Parameter] public bool Disabled { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the textareas value is readonly.
/// </summary>
[Parameter] public bool ReadOnly { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the textareas value is required.
/// </summary>
[Parameter] public bool Required { get; set; }

/// <summary>
/// /// Gets or sets a value indicating whether the ValueChanged event will be fired on input or change. <see cref="InputBehavior"/>
/// </summary>
[Parameter] public InputBehavior Behavior { get; set; } = InputBehavior.OnChange;

/// <summary>
/// Gets or sets a value indicating whether the textarea has full width.
/// </summary>
[Parameter] public bool FullWidth { get; set; }

/// <summary>
/// Gets or sets the text value.
/// </summary>
[Parameter] public string? Value { get; set; }

/// <summary>
/// Gets or sets the placeholder to show if the value is empty.
/// </summary>
[Parameter] public string? Placeholder { get; set; }

/// <summary>
/// Gets or sets Value changed event.
/// </summary>
[Parameter] public EventCallback<string> ValueChanged { get; set; }

/// <summary>
/// Get the merged style of this instance.
/// </summary>
private protected override string? RootClass =>
TwMerge.Merge( Textarea.GetStyles( this ) );

/// <summary>
/// Initializes a new instance of the <see cref="LumexTextArea"/>.
/// </summary>
public LumexTextArea()
{
As = "textarea";
}

/// <summary>
/// Event wrapper function.
/// </summary>
private void OnValueChanged()
{
ValueChanged.InvokeAsync( Value );
}
}
50 changes: 50 additions & 0 deletions src/LumexUI/Styles/Textarea.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
89 changes: 89 additions & 0 deletions tests/LumexUI.Tests/Components/Textarea/TextAreaTests.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
@namespace LumexUI.Tests.Components
@inherits TestContext
@using LumexUI.Common
@using TailwindMerge
@using Microsoft.Extensions.DependencyInjection

@code {
public TextAreaTests()
{
Services.AddSingleton<TwMerge>();
var module = JSInterop.SetupModule( "./_content/LumexUI/js/components/input.js" );
module.Setup<string>( "input.getValidationMessage", _ => true );
}

[Fact]
public void ShouldRenderCorrectly()
{
var action = () => Render(@<LumexTextArea Placeholder="Test" />);

action.Should().NotThrow();
}

[Fact]
public void ShouldRenderWithPlaceholder()
{
var cut = Render(@<LumexTextArea Placeholder="Test" />);

var input = cut.Find( "textarea" );

input.HasAttribute( "placeholder" ).Should().BeTrue();
input.GetAttribute( "placeholder" ).Should().Be( "Test" );
}

[Fact]
public void ShouldRenderWithPlaceholderButShowsValue()
{
var cut = Render(@<LumexTextArea Placeholder="Test" Value="Hello World!" />);

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(@<LumexTextArea Placeholder="Test" Value="Hello World!" ReadOnly />);

var input = cut.Find( "textarea" );

input.HasAttribute( "readonly" ).Should().BeTrue();
}

[Fact]
public void ShouldRenderDisabled()
{
var cut = Render(@<LumexTextArea Placeholder="Test" Value="Hello World!" Disabled />);

var input = cut.Find( "textarea" );

input.HasAttribute( "disabled" ).Should().BeTrue();
}

[Fact]
public void ShouldChangeValueOnInput()
{
var cut = Render<LumexTextArea>( @<LumexTextArea Value="Test" Behavior="@InputBehavior.OnInput" />);

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<LumexTextArea>( @<LumexTextArea Value="Test" Behavior="@InputBehavior.OnInput" ValueChanged="@(e => text = e )" />);

var input = cut.Find( "textarea" );

input.Input( "test 2" );
text.Should().Be( "test 2" );
}
}