Skip to content
Merged
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
obj/
bin/
artifacts/
wwwroot/
# Ignore generated vendor assets, keep custom wwwroot source files trackable.
Lombiq.HelpfulExtensions/wwwroot/vendors/*
node_modules/
*.user
.pnpm-debug.log
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using Lombiq.Tests.UI.Extensions;
using Lombiq.Tests.UI.Services;
using OpenQA.Selenium;
using Shouldly;
using System.Threading.Tasks;

namespace Lombiq.HelpfulExtensions.Tests.UI.Extensions;

public static class LucideTestCaseUITestContextExtensions
{
/// <summary>
/// Tests the Lombiq Helpful Extensions - Lucide feature.
/// </summary>
public static async Task TestLucideFeatureAsync(this UITestContext context)
{
const string iconName = "camera";

await context.SignInDirectlyAsync();

await context.EnableFeatureDirectlyAsync(FeatureIds.Lucide);
await context.ExecuteRecipeDirectlyAsync("Lombiq.HelpfulExtensions.Tests.UI.Lucide.Tests");
await context.CreateNewContentItemAsync("LucidePickerTest", onlyIfNotAlreadyThere: false);

await context.ClickReliablyOnAsync(By.CssSelector("[data-lucide-toggle]"));
await context.ClickAndFillInWithRetriesAsync(By.CssSelector("[data-lucide-search]"), iconName);
await context.ClickReliablyOnAsync(By.CssSelector($"[data-lucide-icon='{iconName}']"));

var selectedIcon = context.ExecuteScript(
"return document.querySelector(arguments[0])?.dataset.lucideIcon ?? '';",
$"[data-lucide-icon='{iconName}'].active") as string;
selectedIcon.ShouldBe(iconName);
context.Get(By.CssSelector($"[data-lucide-preview] [data-lucide='{iconName}']"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"name": "Lombiq.HelpfulExtensions.Tests.UI.Lucide.Tests",
"displayName": "Lombiq Helpful Extensions - Lucide test recipe",
"description": "Creates a simple content type for testing the Lucide icon picker editor.",
"author": "Lombiq Technologies",
"website": "https://github.com/Lombiq/Helpful-Extensions",
"version": "1.0",
"issetuprecipe": false,
"categories": [],
"tags": [],
"steps": [
{
"name": "ContentDefinition",
"ContentTypes": [
{
"Name": "LucidePickerTest",
"DisplayName": "Lucide Picker Test",
"Settings": {
"ContentTypeSettings": {
"Creatable": true,
"Listable": true,
"Draftable": true,
"Versionable": true
}
},
"ContentTypePartDefinitionRecords": [
{
"PartName": "LucidePickerTest",
"Name": "LucidePickerTest",
"Settings": {}
}
]
}
],
"ContentParts": [
{
"Name": "LucidePickerTest",
"Settings": {},
"ContentPartFieldDefinitionRecords": [
{
"FieldName": "TextField",
"Name": "Icon",
"Settings": {
"ContentPartFieldSettings": {
"DisplayName": "Icon",
"Editor": "LucideIconPicker"
}
}
}
]
}
]
}
]
}
2 changes: 2 additions & 0 deletions Lombiq.HelpfulExtensions/Constants/ResourceNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ namespace Lombiq.HelpfulExtensions.Constants;
public static class ResourceNames
{
public const string TargetBlank = nameof(TargetBlank);
public const string Lucide = nameof(Lucide);
public const string LucideIconPicker = nameof(LucideIconPicker);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Lombiq.HelpfulLibraries.Attributes;
using Microsoft.Extensions.Options;
using OrchardCore.ResourceManagement;

namespace Lombiq.HelpfulExtensions.Extensions.Lucide;

[LibManVersions]
public partial class LucideResourceManagementOptionsConfiguration : IConfigureOptions<ResourceManagementOptions>
{
private const string ModuleRoot = "~/" + FeatureIds.Base + "/";
private const string Css = ModuleRoot + "css/";
private const string Scripts = ModuleRoot + "js/";
private const string Vendors = ModuleRoot + "vendors/";
private static readonly ResourceManifest _manifest = new();

static LucideResourceManagementOptionsConfiguration()
{
_manifest
.DefineScript(Constants.ResourceNames.Lucide)
.SetUrl(
Vendors + "lucide/dist/umd/lucide.min.js",
Vendors + "lucide/dist/umd/lucide.js")
.SetVersion(LibManVersions.Lucide);

_manifest
.DefineScript(Constants.ResourceNames.LucideIconPicker)
.SetUrl(Scripts + "lucide-icon-picker.js")
.SetDependencies(Constants.ResourceNames.Lucide);

_manifest
.DefineStyle(Constants.ResourceNames.LucideIconPicker)
.SetUrl(Css + "lucide-icon-picker.css");
}

public void Configure(ResourceManagementOptions options) => options.ResourceManifests.Add(_manifest);
}
11 changes: 11 additions & 0 deletions Lombiq.HelpfulExtensions/Extensions/Lucide/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Lucide

Adds the [Lucide](https://lucide.dev/) icon library integration.

## Lucide resource

The `Lucide` script resource registers the Lucide UMD bundle so themes and modules can render `data-lucide` icons and call `window.lucide.createIcons()`.

## Lucide Icon Picker

`LucideIconPicker` is a `TextField` editor flavor similar to Orchard Core's built-in `IconPicker`, but it selects Lucide icon names instead of Font Awesome names.
13 changes: 13 additions & 0 deletions Lombiq.HelpfulExtensions/Extensions/Lucide/Startup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OrchardCore.Modules;
using OrchardCore.ResourceManagement;

namespace Lombiq.HelpfulExtensions.Extensions.Lucide;

[Feature(FeatureIds.Lucide)]
public sealed class Startup : StartupBase
{
public override void ConfigureServices(IServiceCollection services) =>
services.AddTransient<IConfigureOptions<ResourceManagementOptions>, LucideResourceManagementOptionsConfiguration>();
}
1 change: 1 addition & 0 deletions Lombiq.HelpfulExtensions/FeatureIds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public static class FeatureIds
public const string Flows = FeatureIdPrefix + nameof(Flows);
public const string GoogleTag = FeatureIdPrefix + nameof(GoogleTag);
public const string Liquid = FeatureIdPrefix + nameof(Liquid);
public const string Lucide = FeatureIdPrefix + nameof(Lucide);
public const string OrchardRecipeMigration = FeatureIdPrefix + nameof(OrchardRecipeMigration);
public const string ResetPasswordActivity = Workflows + "." + nameof(ResetPasswordActivity);
public const string Security = FeatureIdPrefix + nameof(Security);
Expand Down
12 changes: 12 additions & 0 deletions Lombiq.HelpfulExtensions/Manifest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,15 @@
Category = "Liquid",
Description = "Adds various Liquid tags and filters."
)]

[assembly: Feature(
Id = Lucide,
Name = "Lombiq Helpful Extensions - Lucide",
Category = "Content",
Description = "Adds the Lucide icon library as a resource and a TextField editor for picking Lucide icons.",
Dependencies =
[
"OrchardCore.ContentFields",
"OrchardCore.Resources",
]
)]
Comment thread
Piedone marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
@model OrchardCore.ContentFields.ViewModels.EditTextFieldViewModel
@using OrchardCore
@using OrchardCore.ContentFields.Settings
@using OrchardCore.ContentManagement.Metadata.Models
@using static Lombiq.HelpfulExtensions.Constants.ResourceNames
@{
var settings = Model.PartFieldDefinition.GetSettings<TextFieldSettings>();
}

<style asp-name="@LucideIconPicker"></style>
<script asp-name="@LucideIconPicker" at="Foot"></script>

<div class="@Orchard.GetFieldWrapperClasses(Model.PartFieldDefinition)" id="@Html.IdFor(x => x.Text)_FieldWrapper">
<label asp-for="Text" class="@Orchard.GetLabelClasses(inputRequired: settings.Required)">@Model.PartFieldDefinition.DisplayName()</label>
<div class="@Orchard.GetEndClasses()">
<input type="hidden" asp-for="Text" data-lucide-value class="content-preview-text" />
<div class="lucide-icon-picker dropdown d-inline-block" data-lucide-icon-picker>
<div class="btn-group" role="group" aria-label="@T["Lucide icon picker"]">
<button
type="button"
class="btn btn-primary dropdown-toggle lucide-icon-picker__selected"
data-bs-toggle="dropdown"
data-bs-display="static"
data-lucide-toggle
aria-expanded="false"
aria-label="@T["Open icon picker"]">
<span class="lucide-icon-picker__preview" data-lucide-preview aria-hidden="true"></span>
<span class="visually-hidden">@T["Open icon picker"]</span>
</button>
<div class="dropdown-menu p-3 shadow lucide-icon-picker__menu" data-lucide-menu>
<div class="d-flex align-items-center gap-2 mb-2">
<input type="search" class="form-control form-control-sm" placeholder="@T["Search icons..."]" data-lucide-search />
<button type="button" class="btn btn-sm btn-outline-secondary" data-lucide-clear>@T["Clear"]</button>
</div>
<div class="lucide-icon-picker__grid" data-lucide-grid role="listbox" aria-label="@T["Lucide icons"]"></div>
<p class="lucide-icon-picker__empty d-none m-0" data-lucide-empty>@T["No icons found."]</p>
</div>
</div>
</div>

<span asp-validation-for="Text"></span>
@if (!string.IsNullOrEmpty(settings.Hint))
{
<span class="hint">@settings.Hint</span>
}
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@{
string currentEditor = Model.Editor;
}
<option value="LucideIconPicker" selected="@(currentEditor == "LucideIconPicker")">@T["Lucide icon picker"]</option>
4 changes: 4 additions & 0 deletions Lombiq.HelpfulExtensions/libman.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
{
"library": "trumbowyg@2.31.0",
"files": [ "dist/**/*", "plugins/**/*" ]
},
{
"library": "lucide@0.542.0",
"files": [ "dist/umd/lucide.js", "dist/umd/lucide.min.js" ]
}
]
}
76 changes: 76 additions & 0 deletions Lombiq.HelpfulExtensions/wwwroot/css/lucide-icon-picker.css
Comment thread
Piedone marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
.lucide-icon-picker__menu {
width: fit-content;
min-width: 24rem;
max-width: calc(100vw - 2rem);
padding: 0.75rem;
}

.lucide-icon-picker__selected {
width: auto;
min-width: auto;
}

.lucide-icon-picker__grid {
display: grid;
grid-template-columns: repeat(6, 3rem);
justify-content: start;
gap: 0.5rem;
width: fit-content;
max-width: 100%;
max-height: 18rem;
overflow-y: auto;
}

.lucide-icon-picker__option {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
min-width: 0;
aspect-ratio: 1;
min-height: 0;
padding: 0.25rem;
border: 1px solid var(--bs-border-color, #dee2e6);
border-radius: 0.375rem;
background: var(--bs-body-bg, #fff);
color: inherit;
text-align: center;
}

.lucide-icon-picker__option:hover,
.lucide-icon-picker__option:focus-visible,
.lucide-icon-picker__option.active {
border-color: var(--bs-primary, #0d6efd);
background: color-mix(in srgb, var(--bs-primary, #0d6efd) 8%, var(--bs-body-bg, #fff));
outline: 0;
}

.lucide-icon-picker__icon,
.lucide-icon-picker__preview {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 20px;
min-height: 20px;
}

.lucide-icon-picker__preview {
width: 20px;
min-width: 20px;
height: 20px;
min-height: 20px;
}

.lucide-icon-picker__icon svg {
width: 20px;
height: 20px;
}

.lucide-icon-picker__preview svg {
width: 20px;
height: 20px;
}

.lucide-icon-picker__label {
display: none;
}
Loading
Loading