Skip to content

Conversation

@msynk
Copy link
Member

@msynk msynk commented Jan 5, 2026

closes #11925

Summary by CodeRabbit

  • New Features
    • Added a new FileInput component with support for single and multiple file selection.
    • Supports drag-and-drop file uploads and programmatic file browsing.
    • Includes file validation by size and extension with custom error messages.
    • Provides customizable templates for file item rendering and browse buttons.
    • Added public API methods for Browse, Reset, and RemoveFile operations.
    • Displays selected files with validation status and optional remove buttons.

✏️ Tip: You can customize this high-level summary in your review settings.

@msynk msynk marked this pull request as draft January 5, 2026 13:12
@coderabbitai
Copy link

coderabbitai bot commented Jan 5, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Introduces a new BitFileInput component enabling file selection via browser input or drag-and-drop, with file metadata access and validation. Includes TypeScript interop for file handling, a DTO for file metadata, UI components for rendering selections, and comprehensive demo documentation with unit tests.

Changes

Cohort / File(s) Summary
Core Component Definition
src/BlazorUI/Bit.BlazorUI/Components/Inputs/FileInput/BitFileInputInfo.cs, BitFileInput.razor, BitFileInput.razor.cs
New data class BitFileInputInfo with JSON-serializable properties for file metadata (ContentType, Name, Size, FileId, Index, Message, IsValid). New Blazor component with file selection, drag-and-drop, validation against MaxSize and AllowedExtensions, event callbacks (OnChange, OnSelectComplete), and public API (Browse, Reset, RemoveFile).
Interop & JavaScript
BitFileInput.ts, BitFileInputJsRuntimeExtensions.cs
TypeScript FileInput class managing file tracking, drag-and-drop setup, and file retrieval via JS. Extension methods for calling JS functions from .NET with proper marshalling of ElementReference and DotNetObjectReference.
Component Styling
BitFileInput.scss, src/BlazorUI/Bit.BlazorUI/Styles/components.scss
New SCSS stylesheet defining container, label, list, file item, and control styling with disabled/valid/invalid states. Added import to global components.scss bundle.
Sub-component
_BitFileInputItem.razor, _BitFileInputItem.razor.cs
New Razor component rendering individual file entries with name, size, validation message (if invalid), and optional remove button. Helper method for CSS class generation based on validation state.
Demo & Examples
BitFileInputDemo.razor, BitFileInputDemo.razor.cs, BitFileInputDemo.razor.scss
Comprehensive demo page with 10 examples covering basic selection, multiple files, auto-reset, append mode, max size validation, allowed extensions, remove functionality, event callbacks, custom templates, and public API usage. Extensive metadata definitions and styled UI for demonstration.
Navigation & Site Integration
ComponentsSection.razor, MainLayout.razor.NavItems.cs
Added FileInput link to component listing and navigation menu with routes /components/fileinput and /components/file-input.
Tests
BitFileInputTests.cs
Unit test suite with 7 test methods covering basic class presence, multiple attribute, accept attribute, label rendering, enabled/disabled states, remove button visibility, and file list hiding. Uses Bunit for component testing.

Sequence Diagram

sequenceDiagram
    participant User
    participant Browser as Browser Input
    participant JS as FileInput.ts
    participant Interop as JS Runtime
    participant Component as BitFileInput.cs
    participant Callback as OnChange Callback

    User->>Browser: Select file(s) or drag-drop
    Browser->>JS: Trigger change event
    JS->>JS: Track file(s) in _fileInputs<br/>Generate fileId & index
    JS->>Interop: setup(id, dotnetRef, inputElement, append)
    Interop->>Component: JS interop method called
    Component->>Component: Fetch files from JS via<br/>BitFileInputSetup
    Component->>Component: Validate each file:<br/>Check MaxSize & AllowedExtensions
    Component->>Component: Mark invalid files<br/>with error Message
    Component->>Callback: Fire OnChange event<br/>with accumulated file list
    Component->>Callback: Fire OnSelectComplete event
    Callback->>User: Display selected files<br/>& validation messages
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes


🐰 A rabbit's ode to BitFileInput:

With paws on files, no upload in sight,
We drag and drop with pure delight,
Validation checks and metadata clear,
A file input component sincere! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.89% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately and specifically describes the primary change: adding a new BitFileInput component to the BlazorUI library.
Linked Issues check ✅ Passed The PR fully implements the requested BitFileInput component with file access, validation, and client-side manipulation capabilities as required by #11925.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the BitFileInput component and its supporting infrastructure without introducing unrelated modifications.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Fix all issues with AI Agents 🤖
In
@src/BlazorUI/Bit.BlazorUI/Components/Inputs/FileInput/_BitFileInputItem.razor:
- Around line 18-20: The clickable remove container (div with class
"bit-fin-usi" in _BitFileInputItem.razor) is missing accessibility attributes
and keyboard support; update that element to include role="button",
tabindex="0", and an appropriate aria-label (e.g., "Remove file") while keeping
the inner icon aria-hidden="true", and wire an onkeydown handler that calls
FileInput.RemoveFile(Item) when Enter or Space is pressed so keyboard users can
activate the control.

In @src/BlazorUI/Bit.BlazorUI/Components/Inputs/FileInput/BitFileInput.razor:
- Around line 36-40: In BitFileInput.razor remove the in-render mutation
"file.Index = index" from the @for loop and instead add a helper method (e.g.,
UpdateFileIndices) that iterates Files, sets Files[i].Index = i with a null
check, and call this helper whenever the Files collection is changed (for
example inside HandleOnChange and/or the Files setter). Ensure UpdateFileIndices
is invoked after any add/remove/update operations so rendering has no side
effects.
- Line 28: The input's boolean attribute is rendered as multiple="@Multiple",
which outputs multiple="false" when false and still enables multiple selection;
update BitFileInput to conditionally render the attribute based on the Multiple
property so the attribute is omitted when false (for example, replace
multiple="@Multiple" with a conditional expression that emits
multiple="multiple" only when Multiple is true, e.g., multiple="@(Multiple ?
"multiple" : null)" or wrap the input markup in an @if (Multiple) block to add
the attribute only when needed).

In @src/BlazorUI/Bit.BlazorUI/Components/Inputs/FileInput/BitFileInput.ts:
- Around line 90-100: BitFileInputItem currently stores unused properties;
remove the redundant file and index fields and constructors that set them so the
class only holds the id. Update the BitFileInputItem declaration and its
constructor to accept and store just id, and ensure any code interacting with
_fileInputs (e.g., the filtering logic that uses id and the setup() method
returning files) continues to work unchanged—only the stored fields should be
removed. Leave references to BitFileInputItem, _fileInputs, and setup() intact.

In
@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/FileInput/BitFileInputDemo.razor:
- Line 213: In BitFileInputDemo.razor update the Templates example text that
reads "Supported file types: jpg, jpeg, png, bpm" to use the correct extension
"bmp" (so it matches the AllowedExtensions used elsewhere); locate the display
string in the Templates section of the BitFileInputDemo.razor component and
replace "bpm" with "bmp".
🧹 Nitpick comments (9)
src/BlazorUI/Bit.BlazorUI/Components/Inputs/FileInput/BitFileInput.ts (3)

5-9: Unused dotnetReference parameter.

The dotnetReference parameter is declared but never used within the setup method. If it's intended for future use (e.g., invoking .NET methods from JS), consider adding a TODO comment. Otherwise, remove it to avoid confusion.


16-16: Potential null dereference on inputElement.files.

The non-null assertion inputElement.files! could throw if files is null (possible on some older browsers or edge cases). Consider adding a guard.

🔎 Proposed defensive check
+            const inputFiles = inputElement.files;
+            if (!inputFiles || inputFiles.length === 0) {
+                return [];
+            }
-            const files = Array.from(inputElement.files!).map((file, index) => ({
+            const files = Array.from(inputFiles).map((file, index) => ({

45-50: Non-null assertions on dataTransfer and clipboardData may cause runtime errors.

Both e.dataTransfer!.files and e.clipboardData!.files use non-null assertions. While these are typically present during drag/paste events, defensive checks would improve robustness.

🔎 Proposed defensive handling
             function onDrop(e: DragEvent) {
                 e.preventDefault();
-                inputElement.files = e.dataTransfer!.files;
-                const event = new Event('change', { bubbles: true });
-                inputElement.dispatchEvent(event);
+                if (e.dataTransfer?.files?.length) {
+                    inputElement.files = e.dataTransfer.files;
+                    const event = new Event('change', { bubbles: true });
+                    inputElement.dispatchEvent(event);
+                }
             }

             function onPaste(e: ClipboardEvent) {
-                inputElement.files = e.clipboardData!.files;
-                const event = new Event('change', { bubbles: true });
-                inputElement.dispatchEvent(event);
+                if (e.clipboardData?.files?.length) {
+                    inputElement.files = e.clipboardData.files;
+                    const event = new Event('change', { bubbles: true });
+                    inputElement.dispatchEvent(event);
+                }
             }
src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/FileInput/BitFileInputDemo.razor.cs (1)

238-286: Consider reducing handler method duplication.

Ten nearly identical HandleOnChange methods exist. For a demo, this is acceptable for clarity, but could be consolidated using a dictionary or action delegate if maintainability becomes a concern.

🔎 Optional consolidation approach
private readonly Dictionary<int, List<BitFileInputInfo>> _selectedFiles = new();

private void HandleOnChange(int exampleId, BitFileInputInfo[] files)
{
    _selectedFiles[exampleId] = files.ToList();
}

// Usage in razor: OnChange="@(files => HandleOnChange(1, files))"
src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/FileInput/BitFileInputDemo.razor.scss (1)

37-54: Consider responsiveness for fixed dimensions.

The .browse-file class uses fixed width: 420px and height: 200px. On smaller screens, this may cause horizontal overflow. Consider using max-width with percentage-based fallback for better responsiveness.

🔎 Suggested responsive adjustment
 .browse-file {
     border: 1px solid #D2D2D7;
     border-radius: 2px;
     padding: 24px;
-    width: 420px;
+    width: 100%;
+    max-width: 420px;
     height: 200px;
src/BlazorUI/Bit.BlazorUI/Components/Inputs/FileInput/_BitFileInputItem.razor.cs (1)

7-8: Consider adding [EditorRequired] for required parameters.

Both FileInput and Item appear to be required for the component to function. Adding [EditorRequired] would provide compile-time warnings when these parameters are not set.

🔎 Proposed improvement
-    [Parameter] public BitFileInput FileInput { get; set; } = default!;
-    [Parameter] public BitFileInputInfo Item { get; set; } = default!;
+    [Parameter, EditorRequired] public BitFileInput FileInput { get; set; } = default!;
+    [Parameter, EditorRequired] public BitFileInputInfo Item { get; set; } = default!;
src/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Inputs/FileInput/BitFileInputTests.cs (2)

9-23: Test doesn't verify different behavior for enabled vs disabled states.

The test uses DataRow(true) and DataRow(false) for isEnabled, but only asserts that the element exists in both cases. Consider asserting distinct behavior, such as the presence or absence of a disabled-related CSS class on the root element.

🔎 Suggested improvement
 public void BitFileInputHasBasicClass(bool isEnabled)
 {
     var com = RenderComponent<BitFileInput>(parameters =>
     {
         parameters.Add(p => p.IsEnabled, isEnabled);
     });

     var bitFileInput = com.Find(".bit-fin-fi");

     Assert.IsNotNull(bitFileInput);
+    var rootElement = com.Find(".bit-fin");
+    Assert.AreEqual(!isEnabled, rootElement.ClassList.Contains("bit-dis"));
 }

77-86: Test does not verify remove button functionality.

This test only checks that the component renders without error. It should assert that a remove button element is actually rendered when ShowRemoveButton = true. However, since the remove button likely only appears when files are selected, this may require mocking file selection state.

🔎 Suggested improvement
 [TestMethod]
 public void BitFileInputShowRemoveButtonTest()
 {
     var com = RenderComponent<BitFileInput>(parameters =>
     {
         parameters.Add(p => p.ShowRemoveButton, true);
     });

-    Assert.IsNotNull(com);
+    // Verify component renders successfully with ShowRemoveButton enabled
+    Assert.IsNotNull(com);
+    // TODO: Add test with mocked file selection to verify remove button appears
+    // when files are selected and ShowRemoveButton is true
 }
src/BlazorUI/Bit.BlazorUI/Components/Inputs/FileInput/BitFileInput.razor.cs (1)

216-217: Clarify the distinction between OnChange and OnSelectComplete.

Both callbacks are invoked with identical data at the same point in execution. Based on the parameter documentation (lines 74-82), OnChange is for when selection changes and OnSelectComplete is for when files are selected and validated. Consider whether OnChange should fire before validation or include only valid files, to provide a clearer semantic distinction.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between a7fd3e7 and 97acbaf.

📒 Files selected for processing (15)
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/FileInput/BitFileInfo.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/FileInput/BitFileInput.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/FileInput/BitFileInput.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/FileInput/BitFileInput.scss
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/FileInput/BitFileInput.ts
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/FileInput/BitFileInputJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/FileInput/_BitFileInputItem.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/FileInput/_BitFileInputItem.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Styles/components.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/FileInput/BitFileInputDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/FileInput/BitFileInputDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/FileInput/BitFileInputDemo.razor.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Home/ComponentsSection.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/MainLayout.razor.NavItems.cs
  • src/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Inputs/FileInput/BitFileInputTests.cs
🧰 Additional context used
🧬 Code graph analysis (4)
src/BlazorUI/Bit.BlazorUI/Components/Inputs/FileInput/_BitFileInputItem.razor.cs (2)
src/BlazorUI/Bit.BlazorUI/Components/Inputs/FileInput/BitFileInput.ts (1)
  • FileInput (2-88)
src/BlazorUI/Bit.BlazorUI/Components/Inputs/FileInput/BitFileInfo.cs (1)
  • BitFileInputInfo (5-41)
src/BlazorUI/Bit.BlazorUI/Components/Inputs/FileInput/BitFileInput.ts (1)
src/BlazorUI/Bit.BlazorUI/Scripts/Utils.ts (1)
  • Utils (2-125)
src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/FileInput/BitFileInputDemo.razor.cs (1)
src/BlazorUI/Bit.BlazorUI/Components/Inputs/FileInput/BitFileInput.razor.cs (6)
  • BitFileInput (6-255)
  • Task (111-121)
  • Task (126-130)
  • Task (158-163)
  • Task (165-172)
  • Task (185-218)
src/BlazorUI/Bit.BlazorUI/Components/Inputs/FileInput/BitFileInput.razor.cs (3)
src/BlazorUI/Bit.BlazorUI/Components/Inputs/FileInput/BitFileInfo.cs (1)
  • BitFileInputInfo (5-41)
src/BlazorUI/Bit.BlazorUI/Extensions/StringExtensions.cs (1)
  • HasNoValue (12-17)
src/BlazorUI/Bit.BlazorUI/Components/Inputs/FileInput/BitFileInputJsRuntimeExtensions.cs (4)
  • ValueTask (17-22)
  • ValueTask (24-27)
  • ValueTask (29-32)
  • ValueTask (34-37)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build Bit.BlazorUI
🔇 Additional comments (9)
src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/FileInput/BitFileInputDemo.razor.cs (1)

1-639: LGTM - Comprehensive demo coverage.

The demo page provides thorough coverage of BitFileInput features including basic usage, multiple selection, auto-reset, append mode, max size validation, allowed extensions, remove button, events, custom templates, and public API access. The example code strings are well-documented.

src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/MainLayout.razor.NavItems.cs (1)

31-31: LGTM!

The FileInput navigation item follows the established pattern, is correctly placed in alphabetical order within the Inputs section, and includes the appropriate AdditionalUrls for the hyphenated URL variant.

src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Home/ComponentsSection.razor (1)

41-43: LGTM!

The FileInput link is correctly added to the Inputs section, follows the established BitLink pattern, and is properly placed in alphabetical order.

src/BlazorUI/Bit.BlazorUI/Styles/components.scss (1)

15-15: LGTM!

The BitFileInput.scss import is correctly added following the established pattern and alphabetical ordering.

src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Inputs/FileInput/BitFileInputDemo.razor.scss (1)

1-153: LGTM - Well-structured demo styles.

The SCSS is well-organized with logical class naming and appropriate nesting. The styles effectively support the demo UI with clear visual hierarchy and interactive states.

src/BlazorUI/Bit.BlazorUI/Components/Inputs/FileInput/_BitFileInputItem.razor.cs (1)

1-12: LGTM - Clean, focused component.

The component is well-structured with appropriate class naming that follows the bit-* convention. The GetFileElClass helper provides clear logic for validation-based styling.

src/BlazorUI/Bit.BlazorUI/Components/Inputs/FileInput/BitFileInputJsRuntimeExtensions.cs (1)

1-38: LGTM!

The JS interop extension methods are well-structured. The [DynamicDependency] attribute on line 7 correctly preserves BitFileInputInfo for AOT/trimming scenarios, ensuring JSON deserialization works in trimmed apps. The extension method pattern provides a clean API for component-level JS interop calls.

src/BlazorUI/Bit.BlazorUI/Components/Inputs/FileInput/BitFileInput.razor.cs (2)

222-254: LGTM on disposal pattern.

The disposal logic correctly handles JS interop exceptions that can occur during Blazor Server circuit disconnection or MAUI app refresh scenarios. The null checks before disposal and the JSDisconnectedException handling are appropriate.


6-15: LGTM on component structure.

The component is well-organized with clear separation between private state, injected dependencies, parameters, and public API. The use of default! for _dropZoneRef and _dotnetObj is acceptable since they're initialized in OnAfterRenderAsync before use, with null checks in DisposeAsync.

@msynk msynk marked this pull request as ready for review January 6, 2026 07:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BitFileInput Missing

2 participants