-
-
Notifications
You must be signed in to change notification settings - Fork 20
feat(components): Add basic textarea component #129
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 6 commits
8b86b31
81d6baf
bdc240d
f523dbf
1f1a2e6
0f95603
16c0302
0993141
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please follow the patterns for naming, structure, and etc of the existing examples. For this component, I think, you could follow the textbox examples. |
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Remove unused field and improve null handling The - private string _value = string.Empty;
- private string? _newValue = string.Empty;
+ private string _newValue = string.Empty; 📝 Committable suggestion
Suggested change
|
||||||||
|
||||||||
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 ) | ||
); | ||
} | ||
|
||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since all the components in this library are highly inspired on the NextUI components, I suggest to keep following their structure. |
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> |
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 ); | ||
} | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As per styles, follow the NextUI's one. They share a single Actually, this task is not so easy at first glance. |
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(); | ||
} | ||
} |
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" ); | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.