< Summary

Information
Class: Chronicis.Client.Components.Articles.IconPickerButton
Assembly: Chronicis.Client
File(s): /home/runner/work/chronicis/chronicis/src/Chronicis.Client/Components/Articles/IconPickerButton.razor
Line coverage
0%
Covered lines: 0
Uncovered lines: 62
Coverable lines: 62
Total lines: 239
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 42
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
BuildRenderTree(...)0%1056320%
get_CurrentIcon()100%210%
get_OnIconChanged()100%210%
.ctor()100%210%
get_HasIcon()100%210%
get_FilteredIcons()0%7280%
TogglePicker()0%620%
OpenPicker()100%210%
ClosePicker()100%210%
SelectCategory(...)100%210%
SelectIcon()100%210%
ClearIcon()100%210%
ClearSearch()100%210%
OnSearchKeyUp(...)100%210%
GetIconDisplayName(...)100%210%

File(s)

/home/runner/work/chronicis/chronicis/src/Chronicis.Client/Components/Articles/IconPickerButton.razor

#LineLine coverage
 1@inject IJSRuntime JSRuntime
 2
 3<div class="chronicis-icon-picker-container" @onclick:stopPropagation="true">
 4    <!-- Icon Button -->
 5    <button type="button"
 6            class="chronicis-icon-button @(HasIcon ? "has-icon" : "")"
 7            @onclick="TogglePicker"
 8            title="@(HasIcon ? "Change icon" : "Add icon")">
 09        @if (HasIcon)
 10        {
 11            <i class="@CurrentIcon"></i>
 12        }
 13        else
 14        {
 15            <i class="fa-solid fa-plus placeholder-icon"></i>
 16        }
 17    </button>
 18
 19    <!-- Clear button (shown when icon exists) -->
 020    @if (HasIcon)
 21    {
 22        <button type="button"
 23                class="chronicis-icon-clear-button"
 24                @onclick="ClearIcon"
 25                @onclick:stopPropagation="true"
 26                title="Remove icon">
 27            <i class="fa-solid fa-xmark"></i>
 28        </button>
 29    }
 30
 31    <!-- Picker Dropdown -->
 032    @if (_isPickerOpen)
 33    {
 34        <div class="chronicis-icon-picker-dropdown">
 35            <!-- Search Box -->
 36            <div class="chronicis-icon-picker-search">
 37                <i class="fa-solid fa-magnifying-glass search-icon"></i>
 38                <input type="text"
 39                       @bind="_searchQuery"
 40                       @bind:event="oninput"
 41                       @onkeyup="OnSearchKeyUp"
 42                       placeholder="Search icons..."
 43                       class="chronicis-icon-search-input" />
 044                @if (!string.IsNullOrEmpty(_searchQuery))
 45                {
 46                    <button type="button" class="clear-search" @onclick="ClearSearch">
 47                        <i class="fa-solid fa-xmark"></i>
 48                    </button>
 49                }
 50            </div>
 51
 52            <!-- Category Tabs -->
 53            <div class="chronicis-icon-picker-categories">
 54                <button type="button"
 55                        class="category-tab @(_selectedCategory == null ? "active" : "")"
 056                        @onclick="@(() => SelectCategory(null))"
 57                        title="All Icons">
 58                    <i class="fa-solid fa-border-all"></i>
 59                </button>
 060                @foreach (var category in FontAwesomeIcons.Categories)
 61                {
 62                    <button type="button"
 63                            class="category-tab @(_selectedCategory == category.Name ? "active" : "")"
 064                            @onclick="@(() => SelectCategory(category.Name))"
 65                            title="@category.Name">
 66                        <i class="@category.Icon"></i>
 67                    </button>
 68                }
 69            </div>
 70
 71            <!-- Category Label -->
 72            <div class="chronicis-icon-picker-category-label">
 073                @if (!string.IsNullOrEmpty(_searchQuery))
 74                {
 075                    <span>Search results for "@_searchQuery" (@FilteredIcons.Count)</span>
 76                }
 077                else if (_selectedCategory != null)
 78                {
 079                    <span>@_selectedCategory (@FilteredIcons.Count)</span>
 80                }
 81                else
 82                {
 083                    <span>All Icons (@FilteredIcons.Count)</span>
 84                }
 85            </div>
 86
 87            <!-- Icon Grid -->
 88            <div class="chronicis-icon-picker-grid">
 089                @if (FilteredIcons.Count == 0)
 90                {
 91                    <div class="no-results">
 92                        <i class="fa-solid fa-face-meh"></i>
 93                        <span>No icons found</span>
 94                    </div>
 95                }
 96                else
 97                {
 098                    @foreach (var icon in FilteredIcons.Take(200))
 99                    {
 100                        <button type="button"
 101                                class="icon-option @(icon == CurrentIcon ? "selected" : "")"
 0102                                @onclick="@(() => SelectIcon(icon))"
 103                                title="@GetIconDisplayName(icon)">
 104                            <i class="@icon"></i>
 105                        </button>
 106                    }
 0107                    @if (FilteredIcons.Count > 200)
 108                    {
 109                        <div class="more-results">
 0110                            <span>+@(FilteredIcons.Count - 200) more - refine your search</span>
 111                        </div>
 112                    }
 113                }
 114            </div>
 115        </div>
 116    }
 117</div>
 118
 119@* Click-outside handler *@
 0120@if (_isPickerOpen)
 121{
 122    <div class="chronicis-icon-picker-backdrop"
 123         @onclick="ClosePicker"></div>
 124}
 125
 126@code {
 127    /// <summary>
 128    /// The current icon class (e.g., "fa-solid fa-dragon")
 129    /// </summary>
 130    [Parameter]
 0131    public string? CurrentIcon { get; set; }
 132
 133    /// <summary>
 134    /// Callback when an icon is selected or cleared
 135    /// </summary>
 136    [Parameter]
 0137    public EventCallback<string?> OnIconChanged { get; set; }
 138
 139    private bool _isPickerOpen = false;
 0140    private string _searchQuery = string.Empty;
 141    private string? _selectedCategory = null;
 142
 0143    private bool HasIcon => !string.IsNullOrEmpty(CurrentIcon);
 144
 145    private List<string> FilteredIcons
 146    {
 147        get
 148        {
 149            IEnumerable<string> icons;
 150
 151            // Start with category filter or all icons
 0152            if (_selectedCategory != null)
 153            {
 0154                var category = FontAwesomeIcons.Categories.FirstOrDefault(c => c.Name == _selectedCategory);
 0155                icons = category?.Icons ?? Array.Empty<string>();
 156            }
 157            else
 158            {
 0159                icons = FontAwesomeIcons.GetAllIcons();
 160            }
 161
 162            // Apply search filter
 0163            if (!string.IsNullOrWhiteSpace(_searchQuery))
 164            {
 0165                var searchTerms = _searchQuery.ToLowerInvariant().Split(' ', StringSplitOptions.RemoveEmptyEntries);
 0166                icons = icons.Where(icon =>
 0167                {
 0168                    var iconName = icon.Replace("fa-solid fa-", "").Replace("-", " ");
 0169                    return searchTerms.All(term => iconName.Contains(term));
 0170                });
 171            }
 172
 0173            return icons.ToList();
 174        }
 175    }
 176
 177    private void TogglePicker()
 178    {
 0179        if (_isPickerOpen)
 180        {
 0181            ClosePicker();
 182        }
 183        else
 184        {
 0185            OpenPicker();
 186        }
 0187    }
 188
 189    private void OpenPicker()
 190    {
 0191        _isPickerOpen = true;
 0192        _searchQuery = string.Empty;
 0193        _selectedCategory = null;
 0194        StateHasChanged();
 0195    }
 196
 197    private void ClosePicker()
 198    {
 0199        _isPickerOpen = false;
 0200        StateHasChanged();
 0201    }
 202
 203    private void SelectCategory(string? category)
 204    {
 0205        _selectedCategory = category;
 0206        StateHasChanged();
 0207    }
 208
 209    private async Task SelectIcon(string icon)
 210    {
 0211        ClosePicker();
 0212        await OnIconChanged.InvokeAsync(icon);
 0213    }
 214
 215    private async Task ClearIcon()
 216    {
 0217        await OnIconChanged.InvokeAsync(null);
 0218    }
 219
 220    private void ClearSearch()
 221    {
 0222        _searchQuery = string.Empty;
 0223        StateHasChanged();
 0224    }
 225
 226    private void OnSearchKeyUp(KeyboardEventArgs e)
 227    {
 228        // Just trigger re-render for filtering
 0229        StateHasChanged();
 0230    }
 231
 232    private string GetIconDisplayName(string icon)
 233    {
 234        // Convert "fa-solid fa-dragon" to "Dragon"
 0235        var name = icon.Replace("fa-solid fa-", "").Replace("fa-regular fa-", "").Replace("fa-brands fa-", "");
 0236        return string.Join(" ", name.Split('-').Select(word =>
 0237            char.ToUpper(word[0]) + word.Substring(1)));
 238    }
 239}